35.Java 中的泛型是什么
35.Java 中的泛型是什么
泛型就是将类型参数化,在编译时再确定类型。可以实现在类,接口,方法中。
-
泛型类
-
public class Box<T> {private T value;public void set(T value) {this.value = value;}public T get() {return value;} }
-
Box<String> stringBox = new Box<>(); stringBox.set("Hello"); String str = stringBox.get(); // 直接获取String类型,无需强制转换Box<Integer> intBox = new Box<>(); intBox.set(123); int num = intBox.get(); // 直接获取Integer类型
-
-
泛型接口
-
public interface Repository<T, ID> {T findById(ID id);void save(T entity); }
-
public class UserRepository implements Repository<User, Long> {@Overridepublic User findById(Long id) {// 根据ID查找User}@Overridepublic void save(User user) {// 保存User} }
-
-
泛型方法
-
public class ArrayUtils {public static <T> void printArray(T[] array) {for (T element : array) {System.out.println(element);}} }
-
String[] names = {"Tom", "Jerry"}; ArrayUtils.printArray(names); // 处理String数组Integer[] numbers = {1, 2, 3}; ArrayUtils.printArray(numbers); // 处理Integer数组
-
使用泛型的好处是什么
在java1.4版本,是没有泛型概念的;只能用Object来实现。Object有两个缺点
- 每次使用的时候必须要进行强制转换
- 在编译时编译器不知道类型转换是否正常,在运行时就会报出异常
sun公司为了Java更加安全,引入了泛型
- 类型安全
- 在编译的时候就可以检查出类型转换的异常
- 消除强制类型转换
- 使用的时候可以直接得到想要的数据类型
- 提升性能
- 在编译时直接完成
泛型是一种语法糖,基本原理就是进行类型擦除;在编译之后,就会擦去类型参数。
public class Caculate<T> {private T num;
}
我们定义了一个泛型类,定义了一个属性成员,该成员的类型是一个泛型类型,这个 T 具体是什么类型,我们也不知道,它只是用于限定类型的。反编译一下这个 Caculate 类:
public class Caculate{public Caculate(){}private Object num;
}
发现编译器擦除 Caculate 类后面的两个尖括号,并且将 num 的类型定义为 Object 类型。
那么是不是所有的泛型类型都以 Object 进行擦除呢?大部分情况下,泛型类型都会以 Object 进行替换,而有一种情况则不是。那就是使用到了extends和super语法的有界类型,如:
public class Caculate<T extends String> {private T num;
}
这种情况的泛型类型,num 会被替换为 String 而不再是 Object。这是一个类型限定的语法,它限定 T 是 String 或者 String 的子类,也就是你构建 Caculate 实例的时候只能限定 T 为 String 或者 String的子类,所以无论你限定 T 为什么类型,String 都是父类,不会出现类型不匹配的问题,于是可以使用String 进行类型擦除。
实际上编译器会正常的将使用泛型的地方编译并进行类型擦除,然后返回实例。但是除此之外的是,如果构建泛型实例时使用了泛型语法,那么编译器将标记该实例并关注该实例后续所有方法的调用,每次调用前都进行安全检查,非指定类型的方法都不能调用成功。
实际上编译器不仅关注一个泛型方法的调用,它还会为某些返回值为限定的泛型类型的方法进行强制类型转换,由于类型擦除,返回值为泛型类型的方法都会擦除成 Object 类型,当这些方法被调用后,编译器会额外插入一行 checkcast 指令用于强制类型转换。这一个过程就叫做『泛型翻译』。
什么是泛型中的限定通配符和非限定通配符
- 限定通配符
- <? extends T> 可以是T或者是T的子类;上界通配符
- <? super T> 可以是T或者是T的父类;下界通配符
- 非限定通配符
- <?> 可以匹配任意类型,
List<? extends T>和List <? super T>之间有什么区别?
-
List<? extends T> 上界通配符,只可以读取,不能写入。
-
List<? extends Number> list = new ArrayList<Integer>(); Number number = list.get(0); // ✅ 可读 list.add(10); // ❌ 编译报错:无法添加
-
List <? super T> 下界通配符,只可以写入,不能读取
-
List<? super Integer> list = new ArrayList<Number>(); list.add(10); // ✅ 可写入 Integer Number num = (Number) list.get(0); // ✅ 可读,但需要强制转换
-
判断ArrayList与ArrayList是否相等?
ArrayList<String> a = new ArrayList<String>();
ArrayList<Integer> b = new ArrayList<Integer>();
Class c1 = a.getClass();
Class c2 = b.getClass();
System.out.println(c1 == c2);
输出的结果是 true。因为无论对于 ArrayList 还是 ArrayList,它们的 Class 类型都是一直的,都是ArrayList.class。
原因:
- Java 的泛型在运行时会进行 类型擦除(Type Erasure)。无论泛型参数是什么,最终的类对象都是
ArrayList.class
。 - 因此,
ArrayList<String>
和ArrayList<Integer>
在运行时都被视为ArrayList
,它们的类对象是相同的。
那它们声明时指定的 String 和 Integer 到底体现在哪里呢?答案是体现在类编译的时候。当 JVM 进行类编译时,会进行泛型检查,如果一个集合被声明为 String类型,那么它往该集合存取数据的时候就会对数据进行判断,从而避免存入或取出错误的数据。
Array中可以用泛型吗?
不可以,因为 List可以提供编译期的类型安全保证,而 Array 却不能。