Java 集合进阶使用(List Map Set 异常)

风希落 / 2024-04-20 / 原文

Collection

image

Collection 是其子集的父类,所以可以使用多态的规矩,比如:创建一个 ArrayList 对象,用 Collection 接收

Collection<Integer> collection = new ArrayList<>();

注意:Collection 为接口,不能直接创建对象,但可以利用其子类,使用 Collection 方法,就如上方代码一样

Collection 方法基本使用

Collection<Integer> collection = new ArrayList<>();
collection.add(1); // 添加一个数据
ollection.add(2);
System.out.println(collection); // [1, 2]

collection.clear(); // 清空集合中的所有值
System.out.println(collection); // []

collection.add(1);
collection.add(2);
collection.remove(2);  // 删除值为 2 的数据
System.out.println(collection); // [1]

boolean contains = collection.contains(1);  // 是否包含值为 1 的数据
System.out.println(contains);  // true

boolean empty = collection.isEmpty(); // 验证是否为空
System.out.println(empty); // false

int size = collection.size(); // 集合长度
System.out.println(size); // 1

注意:

  • add 方法是有一个返回值为 boolean,List系列始终为 trueSet 系列由于不能添加重复数据,可能为 false
  • contains 方法如果查询是否包含某个对象,比如 Student 类的对象,contains 默认对比的是地址值,若要对比的是数据,需要重写 Student 类的 equals 方法

Collection 遍历

Intrator 迭代器遍历

可以把 Intrator 看作一个箭头,默认指向第 0 索引,调用一次 next(),箭头就会向后移动一位。

  • 如果后一位没有值,还要强行 next() 访问,就会报异常
  • hasNext() 方法,判断是否有下一个元素值
  • 迭代器遍历期间,不能使用添加删除等改变集合的方法
Collection<String> collection = new ArrayList<>();
collection.add("a");
collection.add("b");
collection.add("c");
collection.add("d");

Iterator<String> iterator = collection.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next()); // a
    System.out.println(iterator.next()); // b
    System.out.println(iterator.next()); // c
    System.out.println(iterator.next()); // d
    System.out.println(iterator.next()); // 如果 next 已经没有值,强行访问会报异常 NoSuchElementException
}

增强 for 遍历

List 系列和数组可以使用,快捷键 对象名.for

image

Lambda 表达式遍历

和 js 的 forEach 相似,不过 java 中的 forEach 没有第二个参数 i

collection.forEach(v->{
    System.out.println(v);
});

List

CollectionList 的父类接口,而 List 又是 ArrayList 的父类接口,其用法和 Collection 差不多,
迭代器,增强 for,lambda 都可以用

LikedList

双向链表:查询慢,守卫操作增删改的速度极快

image

泛型

基本写法

和 TS 中的泛型类似

泛型类

image

泛型方法

image

泛型接口:和泛型类的用法相似

image

泛型通配符和泛型类型约束

  • ? 表示不确定的类型,可以不传泛型

image

  • extends E 表示可以传入的泛型为 E 类以及基于 E 的子类

image

  • super E 表示可以传入的泛型为 E 类以及其所有的父类,和 extends 刚好相反

Set

Set 不能使用 for 循环遍历,无序也就不能通过索引获取元素
Set 的 API 和 Collection 基本保持一致

  • HashSet:无序、不重复、无索引
  • LinkedHashSet:有序、不重复、无索引
  • TreeSet:可排序、不重复、无索引

HashSet

HashSet 基本使用

image

由图上结果可知,重复的对象并未被添加到 HashSet 中,因为重写了 hashCode 方法,两个相同对象值拥有相同的 hashCode,如果不重写 hashCode,对象值一样也不会重复。

image

LinkedHashSet

当需要存有序数据的时候用 LinkedHashSet

TreeSet

对于数值类型:整数和小数,默认都采用从小到大排序
对于字符和字符串:默认采用 ASCII 码表中的字母代表的数字进行排序

复杂类型排序

比如对 Person 类创建的对象排序,如果直接向 TreeSet 中添加对象,则会报错,因为 TreeSet 无法识别对象的默认排序规则

image

解决方法一:对指定的 javabean 类实现 Comparable 接口,制定排序规则

image

解决方法二:在创建 TreeSet 对象的时候直接通过 Comparator 指定排序规则

image

当然,该代码也可以简写为 Lambda 格式

image

应用场景中,ArrayList 使用最多,涉及到去重,HashSet 使用最多

Map

Map 是一个接口,所以也不能直接 new 创建 Map 对象,需要利用多态,间接使用 Map 的方法
Map 的使用和 js 中的 map 相似

image

Map<String, String> map = new HashMap<>();

map.put("张三", "男");
map.put("李四", "女");
map.put("王五", "人妖");
System.out.println("put: " + map); // {李四=女, 张三=男, 王五=人妖}

map.remove("王五");
System.out.println("remove: " + map); // {李四=女, 张三=男}

map.clear();
System.out.println("clear: " + map); // {}

map.put("张三", "男");
map.put("李四", "女");
map.put("王五", "人妖");

boolean containsKey = map.containsKey("张"); // containsKey 是精确匹配
System.out.println("containsKey: " + containsKey); //false

boolean containsValue = map.containsValue("男");
System.out.println("containsValue: " + containsValue); // true

boolean empty = map.isEmpty();
System.out.println("isEmpty: " + empty); // false

int size = map.size();
System.out.println("size: " + size); // 3

Mapget 方法就是根据键获取对应的值

Map 遍历

keySet

通过 keySet 方法获取到 Map 对象的键的集合,类似 js 中的 Object.keys,然后可以通过键找值

Map<String, String> map = new HashMap<>();

map.put("张三", "男");
map.put("李四", "女");
map.put("王五", "人妖");

Set<String> strings = map.keySet();
strings.forEach(s -> System.out.println(s));  // 循环三次,分别打印 李四 张三 王五

entrySet

entrySet 会生成一个键值对数组(Entry 数组),然后遍历数组,从每一个键值对(Entry)上调用 getKey 方法获取键,getValue 方法获取值

Map<String, String> map = new HashMap<>();

map.put("张三", "男");
map.put("李四", "女");
map.put("王五", "人妖");

Set<Map.Entry<String, String>> entries = map.entrySet();

entries.forEach(entry -> {
    String key = entry.getKey();
    String value = entry.getValue();
    System.out.println(key + "=" + value);
    // 李四=女
    // 张三=男
    // 王五=人妖
});

BiConsumer和Lambda简写

image

# HashMap

HashMap 是基于 Map 的实现类,和 Map 的用法一样。
依赖 hashCodeequals 保证 键的唯一
键如果是自定义对象,需要重写 hashCodeequals 来保证唯一的键

LinkedHashMap

Map 不同的是:存储的 Map 是是有序的

  • 正常存储 Map 是无序的
  • LinkedHashMap 就是在 Map 的基础上增加了有序的功能

TreeMap

TreeSet 用法基本一致

可变参数

可变参数本质就是一个数组,可以在形参中接收多个不固定参数,和 js 中的 args 是一个效果

image

Stream 流写法

在 js 中,filter 过滤数组是可以直接使用的,但是 java 中必须使用 stream 流才行

image

Stream 流中的方法

image

Stream 流中修改数据,均不会影响到原有数据

image

image

可以将 toArray 理解成,将Stream 流转换成集合数组。
也可以使用 collect 直接收集转成数组集合,使用上比 toArray 更方便

image

方法引用

类似的写法在js 中也有,不同的是

  • js 中,可以直接使用函数,参数可以自动传入
  • java 中,必须通过 类名::方法 的写法来使用方法引用

image

异常

try catchthrowtry catch 是异常捕获,throw 是主动抛出异常

  • try catch 捕获到的异常,会走进 catch 中,且之后的代码不会执行
  • catch 中若只打印异常 e.printStackTrace();,是不会影响后续代码的运行
  • 异常中 Exception 是范围最广的异常,所有异常都可以用 Exception 捕获
  • 如果没有进行 try catch 却发生了异常,则交给 jvm 自动处理该异常

自定义异常

常见的异常分为 RuntimeException 运行时异常 和 Exception 编译时异常

  1. 继承异常类 RuntimeException 或者 Exception 或者其它异常类
  2. alt+insert 对父类的构造方法进行重写(根据业务进行重写构造,也可以直接使用父类构造)
// 最简单的自定义异常
public class MyException extends RuntimeException{

    public MyException() {
    }

    public MyException(String message) {
        super(message);
    }
}