Java泛型是一种强大的编程概念,允许在编译时指定操作的数据类型,从而提高代码的重用性和类型安全性。通过泛型,可以编写更通用、类型安全和可读性更高的代码。
第一部分:泛型基础
1. 为什么需要泛型?
在没有泛型的情况下,如果你需要编写一个通用的容器类(如集合),就需要为不同类型的对象创建多个容器类。使用泛型,你可以在一种容器类中存储不同类型的对象。
2. 泛型类和泛型方法
泛型类:允许在类级别上指定类型参数。例如,ArrayList<E>,Map<K, V>。
泛型方法:允许在方法级别上指定类型参数。例如,Collections.<String>sort(list)。
3. 类型参数和实际类型参数
类型参数:在泛型类或方法定义中使用的占位符类型。例如,ArrayList<E>中的E。
实际类型参数:在使用泛型类或方法时传递的实际类型。例如,ArrayList<String>中的String。
4. 泛型的类型擦除
Java中的泛型在编译时执行类型擦除,编译器会将泛型类型转换为原始类型(非泛型)进行编译。这使得泛型的类型信息在运行时不可用。
5. 使用泛型类
创建泛型对象:在创建对象时,可以指定实际类型参数。
ArrayList<String> list = new ArrayList<>();
添加元素:在泛型对象中添加指定类型的元素。
list.add("Hello");
获取元素:从泛型对象中获取指定类型的元素。
String element = list.get(0);
第二部分:泛型高级用法和类型边界
1. 类型边界
泛型上界:通过使用extends关键字,可以限制泛型类型参数必须是指定类的子类或实现类。
public class Box<T extends Number> { ... }
泛型下界:通过使用super关键字,可以限制泛型类型参数必须是指定类的父类。
public void addToList(List<? super Integer> list) { ... }
2. 通配符
通配符用于灵活地处理不同类型的泛型数据。常见的通配符是?,表示未知类型。
无限通配符:<?>,表示可以匹配任何类型。
public void printList(List<?> list) { ... }
上限通配符:<? extends T>,表示匹配T类型及其子类型
public double sumList(List<? extends Number> list) { ... }
3. 泛型方法
在泛型类中,可以定义泛型方法,这些方法的类型参数独立于泛型类的类型参数。
泛型方法允许你在调用方法时指定类型参数,与泛型类的类型参数无关。
例如,Collections类的<T> void sort(List<T> list)就是一个泛型方法。
4. 泛型和继承
泛型的类型参数不具有继承关系,即使A是B的子类,List<A>也不是List<B>的子类。
5. 泛型的类型转换
在泛型中,编译器会在必要时执行类型转换,但是某些类型之间的转换可能会导致编译错误或运行时异常。
第三部分:泛型的高级应用和类型擦除
1. 泛型和反射
Java的反射机制可以在运行时获取类的信息,但是由于泛型的类型擦除,无法直接获取泛型的类型参数信息。
可以使用Class<T>类的getGenericSuperclass()和getGenericInterfaces()方法获取包含泛型信息的类型。
2. 泛型数组
不能创建泛型数组,例如,不能直接创建List<String>[]。
可以创建通配符数组,例如,List<?>[]是合法的。
3. 泛型和虚拟机
Java泛型的类型信息在运行时是不可用的,因为类型擦除会导致泛型类型变成原始类型。
编译器在编译时会插入类型转换和检查,以确保类型安全性。
4. 自定义泛型类和方法
可以创建自定义的泛型类,用于适应特定的数据结构或需求。
自定义泛型方法允许在方法级别上指定类型参数,增加代码的通用性和可读性。
5. 泛型接口
可以创建泛型接口,它可以被实现类指定具体的类型参数。
泛型接口在定义数据结构和操作时非常有用。
6. 通配符捕获
通配符捕获是一种通过中间变量来捕获通配符的类型,以避免类型推断的歧义。
通配符捕获通常在泛型方法中使用,可以使编译器更好地理解类型信息。
7. 泛型的限制和注意事项
使用泛型时需要注意类型的转换和类型安全性。
泛型的灵活性和通用性可能会导致某些编译器警告。