泛型
- 泛型是⼀种强类型约束机制,可以在编译期间检查类型安全性,并且可以提⾼代码的复⽤性和可读性
- 类型参数化
泛型的本质是参数化类型,也就是说,在定义类、接⼝或⽅法时,可以使⽤⼀个或多个类型参数来表示参数化类型
public class Box<T> {
private T value;
public Box(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
----------------------------------------
Box<Integer> intBox = new Box<>(123);
Box<String> strBox = new Box<>("Hello, world!");
- 类型擦除
泛型在编译时会将泛型类型擦除,将泛型类型替换成 Object 类型。这是为了向后兼容,避免对原有的 Java 代码造成影响。
List<Integer> intList = new ArrayList<>();
intList.add(123);
int value = intList.get(0);
-----------------------------------------------------
List intList = new ArrayList();
intList.add(Integer.valueOf(123));
int value = (Integer) intList.get(0);
在编译时,Java 编译器会将泛型类型 List 替换成 List ,将 get ⽅法的返回值类型 Integer 替换成 Object,Java 泛型只在编译时起作⽤,运⾏时并不会保留泛型类型信息
- 通配符
⽤于表示某种未知的类型,例如 List<?> 表示⼀个可以存储任何类型对象的 List,但是不能对其中的元素进⾏添加操作。通配符可以⽤来解决类型不确定的情况,例如在⽅法参数或返回值中使⽤。
使⽤通配符可以使⽅法更加通⽤,同时保证类型安全。
public static void printList(List<?> list) {
for (Object obj : list) {
System.out.print(obj + " ");
}
System.out.println();
}
- 上限通配符
泛型还提供了上限通配符 <? extends T> ,表示通配符只能接受 T 或 T 的⼦类。使⽤上限通配符可以提⾼程序的类型安全性。
public static void printNumberList(List<? extends Number> list) {
for (Number num : list) {
System.out.print(num + " ");
}
System.out.println();
}
- 下限通配符
下限通配符(Lower Bounded Wildcards)⽤ super 关键字来声明,其语法形式为 <? super T> ,其中 T 表示类型参数。它表示的是该类型参数必须是某个指定类的超类(包括该类本身)。
当我们需要往⼀个泛型集合中添加元素时,如果使⽤的是上限通配符,集合中的元素类型可能会被限制,从⽽⽆法添加某些类型的元素。但是,如果我们使⽤下限通配符,可以将指定类型的⼦类型添加到集合中,保证了元素的完整性。
需要注意,虽然使⽤下限通配符可以添加某些⼦类型元素,但是在读取元素时,我们只能确保其是 Object 类型的,⽆法确保其是指定类型或其⽗类型。因此,在读取元素时需要进⾏类型转换 - PECS原则(Producer Extends, Consumer Super)
iterator和iterable
- list三种遍历方式for循环、迭代器、for-each(语法糖,默认通过intertor()方法遍历)
- JDK 1.8 时,Iterable 接⼝中新增了 forEach ⽅法。该⽅法接受⼀个 Consumer 对象作为参数,⽤于对集合中的每个元素执⾏指定的操作。该⽅法的实现⽅式是使⽤ for-each 循环遍历集合中的元素,对于每个元素,调⽤ Consumer 对象的 accept ⽅法执⾏指定的操作。
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
list.forEach(integer -> System.out.println(integer));
----------------------------------------------------------------------
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
list.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});
- itrator接口 extends itrable接口,itrable接口含有iterator().
- itrator实现迭代功能(hasnext、next、remove),iterable支持迭代.
- 不直接把迭代功能放到interable中,集合可以实现不同的遍历方式实现了Iterable的类可以再实现多个Iterator内部类,例如LinkedList中的ListItr和DescendingIterator两个内部类,就分别实现了双向遍历和逆序遍历。通过返回不同的Iterator实现不同的遍历方式,这样更加灵活。如果把两个接口合并,就没法返回不同的Iterator实现类。
for-Each问题
为什么不能在foreach⾥执⾏删除操作?
因为 foreach 循环是基于迭代器实现的,⽽迭代器在遍历集合时会维护⼀个 expectedModCount属性来记录集合被修改的次数。如果在 foreach 循环中执⾏删除操作会导致 expectedModCount属性值与实际的 modCount 属性值不⼀致,从⽽导致迭代器的 hasNext() 和 next() ⽅法抛出ConcurrentModificationException 异常。
为了避免这种情况,应该使⽤迭代器的 remove() ⽅法来删除元素,该⽅法会在删除元素后更新迭代器状态,确保循环的正确性。如果需要在循环中删除元素,应该使⽤迭代器的 remove() ⽅法,⽽不是集合⾃身的 remove() ⽅法。
for循环没问题是调用迭代器,没有checkmod。
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
Iterator<String> itr = list.iterator();
while (itr.hasNext()) {
String str = itr.next();
if ("1".equals(str)) {
itr.remove();
}
}
---------------------------------------------------------------------------------
List<String> list = new ArrayList<>(Arrays.asList("1","2","3"));
list.stream().filter(i - !i.equals("1")).collect(Collectors.toList());
- fail-fast机制
优先考虑错误,检测到错误马上抛出,方便上级程序做相应处理。避免 程序继续执行引起更大的问题。