[关闭]
@TryLoveCatch 2022-05-13T03:24:21.000000Z 字数 4230 阅读 360

Java知识体系之泛型

Java知识体系


Java 泛型(generics) 是 JDK 5 中引入的一个新特性,泛型的本质是参数化类型,即给类型指定一个参数,然后在使用时再指定此参数具体的值,那样这个类型就可以在使用时决定了。
泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。

泛型使用方式

泛型一般有三种使用方式: 泛型类、泛型接口、泛型方法。

泛型类

  1. //此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
  2. //在实例化泛型类时,必须指定T的具体类型
  3. public class Generic<T> {
  4. private T key;
  5. public Generic(T key) {
  6. this.key = key;
  7. }
  8. public T getKey() {
  9. return key;
  10. }
  11. }
  12. // 使用
  13. Generic<Integer> genericInteger = new Generic<Integer>(123456);

泛型接口

  1. public interface Generator<T> {
  2. public T method();
  3. }
  4. // 实现泛型接口,不指定类型
  5. class GeneratorImpl<T> implements Generator<T>{
  6. @Override
  7. public T method() {
  8. return null;
  9. }
  10. }
  11. // 实现泛型接口,指定类型:
  12. class GeneratorImpl2 implements Generator<String>{
  13. @Override
  14. public String method() {
  15. return "hello";
  16. }
  17. }

泛型方法

  1. public static <E> void printArray(E[] inputArray) {
  2. for (E element : inputArray) {
  3. System.out.printf("%s ", element);
  4. }
  5. System.out.println();
  6. }
  7. // 创建不同类型数组: Integer, String
  8. Integer[] intArray = { 1, 2, 3 };
  9. String[] stringArray = { "Hello", "World" };
  10. printArray(intArray);
  11. printArray(stringArray);

通配符

  1. // 使用 ? extends Number 表示元素的上限是 Number
  2. // 我们无法确定具体是哪种类型的元素可以存入
  3. // 可以确定是取出的元素类型是兼容 Number 类型的
  4. List<? extends Number> list = new ArrayList<>();
  5. // 编译报错
  6. // list.add(123);
  7. // list.add(new Object());
  8. list.add(null);
  9. // 只能 get, get 的对象一定是 Number 或者 Number 的子类, 那么使用 Number 是类型兼容的, 所以编译不报错
  10. Number number = list.get(0);
  11. // 使用 ? super Number 表示元素的下限是 Number
  12. // 我们不能确定取出元素的类型是 Number 或 Number 的哪个父类
  13. // 我们可以确定的是存入的元素类型是兼容 Number 或者 Number 的子类
  14. List<? super Number> list2 = new ArrayList<>();
  15. // Number 或者 Number 的父类无法确定具体是哪个
  16. // 添加元素时加入 Number 的子类, 一定是类型兼容的, 所以编译不报错
  17. list2.add(100);
  18. list2.add(100L);
  19. list2.add(100.0d);
  20. // 取出元素数据类型是 Object 类, 即所有对象的父类
  21. Object object = list2.get(0);

PECS原则

PECS 即 producer extends, Consumer super

为什么有泛型?

泛型问题

泛型类中的静态方法和静态变量不可以使用泛型类所声明的泛型类型参数,例如下面的代码编译失败

  1. public class Test<T> {
  2. public static T one; //编译错误
  3. public static void show(T one){ //编译错误
  4. return null;
  5. }
  6. }

泛型原理

协变方法 逆变方法

编译器,字节码里面并没有

泛型擦除

Java 的泛型是伪泛型,只在编译期做泛型检查,运行期泛型就会消失,我们把这称为“泛型擦除”。

  1. List<Integer> list = new ArrayList<>();
  2. list.add(12);
  3. //这里直接添加会报错
  4. list.add("a");
  5. Class<? extends List> clazz = list.getClass();
  6. Method add = clazz.getDeclaredMethod("add", Object.class);
  7. //但是通过反射添加是可以的
  8. //这就说明在运行期间所有的泛型信息都会被擦掉
  9. add.invoke(list, "kl");
  10. System.out.println(list);

这就说明在运行期间所有的泛型信息都会被擦掉。

还有一个经典的例子:

  1. List<String> stringList = new ArrayList<>();
  2. List<Integer> intList = new ArrayList<>();
  3. System.out.println(stringList.class == intList.class);// true

Java编译器编译泛型的步骤:

  1. // 擦除之前
  2. class Pair<T> {
  3. private T value;
  4. public T getValue() {
  5. return value;
  6. }
  7. public void setValue(T value) {
  8. this.value = value;
  9. }
  10. }
  11. // 擦除之后
  12. class Pair {
  13. private Object value;
  14. public Object getValue() {
  15. return value;
  16. }
  17. public void setValue(Object value) {
  18. this.value = value;
  19. }
  20. }

桥方法

  1. // 泛型接口
  2. public interface Generic<T> {
  3. void test(T t);
  4. }
  5. // 实现类
  6. public class SubGeneric implements Generic<String> {
  7. @Override
  8. public void test(String s) {
  9. System.out.println(s);
  10. }
  11. }
  12. // 使用
  13. public class Test {
  14. public static void main(String[] args) {
  15. Generic subGeneric = new SubGeneric();
  16. subGeneric.test("1");
  17. }
  18. }
  1. public void test(Object object);
  1. subGeneric.test(1);

程序可以编译成功,但是会报ClassCastException:java.lang.Integer cannot be case to java.long.String。

目前为止,这里面有两个问题:

编译器会帮我们生成一个test(Object s)的桥接方法,并且是通过强转直接调用的test(String s),这样就能解释上面的两个问题了。

  1. public class SubGeneric implements Generic {
  2. public void test(String s) {
  3. System.out.println(s);
  4. }
  5. public void test(Object s) {
  6. test((String)s);
  7. }
  8. }

怎么破坏泛型

由于反射是运行时生效,而泛型类型的检查是在编译时,如果未定义上界,那么将会擦除为Object,即可以接受任意类型。所以利用反射,我们可以注入其他类型的值。

类似于上面泛型擦出的那个例子:

  1. List<Integer> list = new ArrayList<>();
  2. list.add(12);
  3. //这里直接添加会报错
  4. list.add("a");
  5. Class<? extends List> clazz = list.getClass();
  6. Method add = clazz.getDeclaredMethod("add", Object.class);
  7. //但是通过反射添加是可以的
  8. //这就说明在运行期间所有的泛型信息都会被擦掉
  9. add.invoke(list, "kl");
  10. System.out.println(list);

参考

https://javaguide.cn/java/basis/java-basic-questions-03.html#%E6%B3%9B%E5%9E%8B
https://segmentfault.com/a/1190000039835272
https://zhuanlan.zhihu.com/p/64585072
https://blog.csdn.net/csdn_mrsongyang/article/details/121753591
http://www.wjhsh.net/wt88-p-9624350.html

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注