泛型
①泛型是Java5的新特性,属于编译阶段的功能。
②泛型可以让开发者在编写代码时指定集合中存储的数据类型
③泛型作用:
1.类型安全:指定了集合中元素的类型之后,编译器会在编译时进行类型检查,如果尝试将错误类型的元素添加到集合中,就会在编译时报错,避免了在运行时出现类型错误的问题。
2.代码简洁:使用泛型可以简化代码,避免了繁琐的类型转换操作。比如,在没有泛型的时候,需要使用 Object 类型来保存集合中的元素,并在使用时强制类型转换成实际类型,而有了泛型之后,只需要在定义集合时指定类型即可。
④在集合中使用泛型
Collection<String> strs = new ArrayList<String>();
这就表示该集合只能存储字符串,存储其它类型时编译器报错。
并且以上代码使用泛型后,避免了繁琐的类型转换,集合中的元素可以直接调用String类特有的方法。
⑤Java7的新特性:钻石表达式
Collection<String> strs = new ArrayList<>();
1. 泛型的定义
泛型(Generic)是程序设计语言的一种特性,允许在定义类、接口或方法时使用类型参数(即不确定的类型),在使用时再指定具体类型。
它的核心思想是 “将类型的确定推迟到使用时”,通过参数化类型实现代码的复用和类型安全。
例如,Java 中定义泛型类:
// T是类型参数,代表不确定的类型,使用时指定具体类型(如String、Integer等)
public class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
// 使用时指定具体类型
Box<String> stringBox = new Box<>();
stringBox.setValue("hello"); // 只能传入String类型
String str = stringBox.getValue(); // 无需强转,直接得到String类型
2. 为什么需要泛型?
在泛型出现前(如 JDK 1.4 及之前),集合类只能存储Object
类型,存在两大问题:
- 类型不安全:可以向集合中添加任意类型的元素,编译时不会报错,但运行时可能因类型不匹配抛出
ClassCastException
。 - 冗余的强转:从集合中取元素时,必须手动强转为目标类型,代码繁琐且易出错。
泛型的出现解决了这些问题:
- 编译时类型检查:在定义集合时指定类型(如
List<String>
),编译器会自动检查添加的元素是否符合类型,不符合则编译报错,避免运行时异常。 - 消除手动强转:从集合中取元素时,编译器会自动根据泛型类型进行转换,无需手动强转,简化代码。
例如,没有泛型的问题:
// JDK 1.4风格(无泛型)
List list = new ArrayList();
list.add("hello");
list.add(123); // 编译不报错,埋下隐患
// 取元素时必须强转,且可能报错
String str = (String) list.get(1); // 运行时抛出ClassCastException(123是Integer类型)
有泛型后:
// 有泛型
List<String> list = new ArrayList<>();
list.add("hello");
list.add(123); // 编译直接报错,阻止错误
String str = list.get(0); // 无需强转,安全便捷
总结:泛型通过参数化类型实现了代码复用与类型安全,是编译时的技术,核心价值在于 “提前发现类型错误,减少冗余代码”。
在 Java 中,类型擦除(Type Erasure) 是泛型实现的一种机制,简单来说,就是编译器在编译时会移除泛型的类型信息,使得泛型在运行时不保留具体的类型参数,以此保证与 Java 早期版本的兼容性。
类型擦除的核心表现
-
编译时擦除类型参数
定义泛型类、接口或方法时指定的类型参数(如T
、List<String>
中的String
),在编译后会被替换为原始类型(Raw Type)。- 例如,
List<String>
和List<Integer>
在编译后都会变成List
(原始类型)。 - 对于无限制的类型参数(如
T
),会被替换为Object
;有上下限的则替换为对应的边界类型(如T extends Number
会被替换为Number
)。
- 例如,
-
运行时无法获取泛型类型信息
由于类型信息在编译时被擦除,运行时无法通过getClass()
、instanceof
等方式判断泛型的具体类型参数。- 例如,
list instanceof List<String>
是非法的,编译会报错。
- 例如,
为什么需要类型擦除?
Java 引入泛型时(JDK 5),为了保证旧版本代码(没有泛型)能正常运行,采用了类型擦除的妥协方案,让泛型成为 “编译期语法糖”,不改变 Java 虚拟机的运行时结构。
类型擦除的影响
- 泛型数组限制:无法直接创建泛型数组(如
new List<String>[10]
),因为数组在运行时需要知道元素类型,而泛型类型已被擦除。 - 重载限制:由于类型擦除,
void method(List<String>)
和void method(List<Integer>)
会被视为同一个方法,无法重载。 - 需要显式类型转换:泛型方法的返回值在编译后会被擦除为原始类型,编译器会自动插入类型转换代码,保证类型安全。
泛型的擦除与补偿
- 泛型的核心作用:提高编译时安全性,对添加的数据进行类型检查,避免程序运行时抛出类型转换异常,本质是编译时期的技术,服务于编译器。
- 泛型擦除:类加载时,泛型会被擦除,擦除后的类型为 Object 类型。
- 泛型擦除的原因:为实现 JDK1.4 与 JDK1.5 对同一类加载器的兼容。JDK1.5 编译时检查确保集合元素类型统一后,擦除泛型为 Object 类型,使代码与 JDK1.4 兼容。
- 泛型补偿:程序运行时,虚拟机会根据元素实际类型进行强转(向下转型),恢复元素实际类型,无需手动执行强制转换,从本质上避免类型转换异常。这解释了为何泛型擦除后取出的元素仍是实际添加的类型。
①泛型的出现提高了编译时的安全性,正因为编译时对添加的数据做了检查,则程序运行时才不会抛出类型转换异常。因此泛型本质上是编译时期的技术,是专门给编译器用的。加载类的时候,会将泛型擦除掉(擦除之后的类型为Object类型),这个称为泛型擦除。
②为什么要有泛型擦除呢?其本质是为了让JDK1.4和JDK1.5能够兼容同一个类加载器。在JDK1.5版本中,程序编译时期会对集合添加的元素进行安全检查,如果检查完是安全的、没有错误的,那么就意味着添加的元素都属于同一种数据类型,则加载类时就可以把这个泛型擦除掉,将泛型擦除后的类型就是Object类,这样擦除之后的代码就与JDK1.4的代码一致。
③由于加载类的时候,会默认将类中的泛型擦除为Object类型,所以添加的元素就被转化为Object类型,同时取出的元素也默认为Object类型。而我们获得集合中的元素时,按理说取出的元素应该是Object类型,为什么取出的元素却是实际添加的元素类型呢
④这里又做了一个默认的操作,我们称之为泛型的补偿。在程序运行时,通过获取元素的实际类型进行强转,这就叫做泛型补偿(不必手动实现强制转换)。获得集合中的元素时,虚拟机会根据获得元素的实际类型进行向下转型,也就是会恢复获得元素的实际类型,因此我们就无需手动执行向下转型操作,从本质上避免了抛出类型转换异常。