Java 泛型是一种在编译时期进行类型检查和类型安全的机制。它允许我们在编写代码时指定类、接口或方法可以操作的数据类型,从而提供更强大、更灵活的代码重用和类型安全性。
泛型的主要目的是在编译时捕获类型错误,并在编译时发出警告或错误,而不是在运行时抛出异常。这样可以提前检测和解决类型相关的问题,减少运行时错误的可能性。
下面给出 ArrayList 类的部分实现代码:
// 泛型类 public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { // 泛型方法 public <T> T[] toArray(T[] a) { //... } public E get(int index) { //... } public E set(int index, E element) { //... } public boolean add(E e) { //... } public E remove(int index) { //... } //... }
上述代码中,使用 <> 定义了一个泛型参数 E,参数 E 用来表示存入 ArrayList 中的元素类型,例如:
List<String> list = new ArrayList<String>(); list.add("Hello"); list.add("World");
上面示例中,list 仅接收 String 类型的数据,这是因为在创建 ArrayList 时指定泛型参数 E 为 String。如果向 list 中添加其他类型的数据,编译程序时就会抛出错误信息。
JDK 1.5 增加泛型支持在很大程度上都是为了让集合能记住其元素的数据类型。在没有泛型之前,一旦把一个对象 “丢进” Java集合中,集合就会忘记对象的类型,把所有的对象当成 Object 类型处理。当程序从集合中取出对象后,就需要进行强制类型转换,这种强制类型转换不仅使代码臃肿,而且容易引起 ClassCastExeception 异常。例如:
import java.util.ArrayList; import java.util.List; public class Demo { public static void main(String[] args) throws CustomException { List list = new ArrayList(); list.add("Hello"); list.add("World"); list.add(100L); for(Object obj : list) { System.out.println((String)obj); } } }
运行示例,输出如下:
Hello World Exception in thread "main" java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.String at com.hxstrive.demo.Demo.main(Demo.java:15)
增加了泛型支持后的集合,完全可以记住集合中元素的类型,并可以在编译时检查集合中元素的类型,如果试图向集合中添加不满足类型要求的对象,编译器就会提示错误。增加泛型后的集合,可以让代码更加简洁,程序更加健壮 (Java 泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生 ClassCastException 异常)。除此之外,Java 泛型还增强了枚举类、反射等方面的功能,泛型在反射中的用法。例如:
import java.util.ArrayList; import java.util.List; public class Demo { public static void main(String[] args) throws CustomException { List<String> list = new ArrayList<String>(); list.add("Hello"); list.add("World"); // list.add(100L); for(Object obj : list) { System.out.println((String)obj); } } }
运行示例,输出如下:
Hello World
注意,上面代码中,当我们向 list 集合添加数字时将直接提示错误,如下图:
总的说来,Java 泛型有以下一些优点:
(1)类型安全:Java 泛型提供了编译时的类型检查机制,可以在编译时捕获类型错误。这可以避免在运行时出现类型转换错误和类型相关的异常,提高代码的可靠性和稳定性。
(2)代码重用:使用泛型可以编写通用的代码,可以适用于多种类型的数据。这样可以减少代码的重复编写,并提高代码的可维护性和可读性。
(3)避免类型转换:使用泛型可以避免手动进行类型转换操作,减少了代码的冗余和错误的可能性。编译器会自动插入类型转换代码,使代码更简洁和易于理解。
(4)更好的性能:泛型使用的是编译时类型检查,而不是运行时类型检查。这意味着在运行时不需要进行额外的类型检查操作,可以提高代码的执行效率。
(5)可读性和可维护性:使用泛型可以使代码更加清晰和易于理解。泛型类型参数可以提供有意义的名称,使代码的意图更加明确,减少了阅读和理解代码的难度。
(6)类型推断:在调用泛型方法时,可以根据参数的类型进行类型推断,无需显式指定类型参数。这简化了代码的书写,并提高了代码的简洁性和可读性。
(7)集合框架的增强:Java 集合框架中的类和接口都使用了泛型,使得集合操作更加类型安全和方便。通过使用泛型,可以在编译时捕获集合操作中的类型错误,并提供更好的类型推断和类型安全性。
Java 泛型虽然有很多优点,但也存在一些缺点。以下是 Java 泛型的一些缺点:
(1)类型擦除:Java 泛型使用类型擦除机制,即在编译时擦除泛型类型信息,将泛型类型参数替换为其边界类型或 Object 类型。这意味着在运行时无法获取泛型类型的具体信息。这种类型擦除机制可以导致一些限制,例如无法在运行时确定泛型类型,无法创建泛型类型的数组等。
(2)无法使用基本类型作为类型参数:Java 泛型只能使用引用类型作为类型参数,无法直接使用基本类型(如 int、char 等)。这导致在使用基本类型时需要进行自动装箱和拆箱操作,增加了额外的性能开销。
(3)通配符限制:使用通配符(?)作为泛型类型参数时,无法在方法中使用该类型参数进行具体的操作。通配符只能用于接收数据,无法进行添加、修改或删除等操作。
(4)泛型数组的限制:由于类型擦除机制,无法直接创建泛型类型的数组。例如,无法直接创建 List<String>[] 类型的数组,只能创建 List[] 类型的数组。
(5)泛型类型参数的限制:泛型类型参数不能是基本类型,也不能是静态字段或方法的类型。例如,无法创建 List<int> 类型的泛型对象,也无法在静态方法中使用泛型类型参数。
(6)泛型类型的兼容性:Java 泛型中的类型参数是不变的,即使两个泛型类型具有相同的类型参数,它们之间也被视为不同的类型。这可能导致一些兼容性问题,例如无法将 List<String> 赋值给 List<Object>。