java 泛型
迪丽瓦拉
2024-02-24 23:49:09
0

今天来聊聊java中的泛型,泛型主要对类型进行限定。

下面看一段代码

代码

/*** 图形*/
interface Shape {void draw();
}/*** 圆形*/
class Circle implements Shape {@Overridepublic void draw() {}
}/*** 正方形*/
class Square implements Shape {@Overridepublic void draw() {}
}/*** 颜色*/
class Color{}

没有指定泛型,默认list是可以添加任意Object类型。这就存在一定的风险,取数据的时候对类型进行转换需要避免类型不一致的问题。

public class Test {public static void main(String[] args) {//没有指定泛型List list = new ArrayList<>();list.add(new Circle());list.add(new Square());//需要类型强制转换Shape shape = (Shape) list.get(0);}
}

泛型的用法是添加<>里边对类型进行限定,下边的例子ArrayList后边的<>无需添加类型,因为编译器会做类型推断。

public class Test {public static void main(String[] args) {//指定泛型List list = new ArrayList<>();list.add(new Circle());list.add(new Square());//编译失败list.add(new Color());//无需类型强制转换Shape shape =  list.get(0);}
}

类型擦除

泛型里边有一个很重要的概念就是类型擦除,泛型的类型信息只存留在编译阶段,运行的时候是没有我们所设定的泛型信息的。

使用 javap -c Test查看编译后的字节码信息

示例1
public class Test {public static void main(String[] args) {//没有指定泛型List list = new ArrayList<>();list.add(new Circle());list.add(new Square());//需要类型强制转换Shape shape = (Shape) list.get(0);}
}

上面代码编译完成后,查看字节码内容如下:

Compiled from "Test.java"
public class Test {public Test();Code:0: aload_01: invokespecial #1                  // Method java/lang/Object."":()V4: returnpublic static void main(java.lang.String[]);Code:0: new           #2                  // class java/util/ArrayList3: dup4: invokespecial #3                  // Method java/util/ArrayList."":()V7: astore_18: aload_19: new           #4                  // class Circle12: dup13: invokespecial #5                  // Method Circle."":()V16: invokeinterface #6,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z21: pop22: aload_123: new           #7                  // class Square26: dup27: invokespecial #8                  // Method Square."":()V30: invokeinterface #6,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z35: pop36: aload_137: iconst_038: invokeinterface #9,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;43: checkcast     #10                 // class Shape46: astore_247: return
}
示例2
public class Test {public static void main(String[] args) {//指定泛型List list = new ArrayList<>();list.add(new Circle());list.add(new Square());//无需类型强制转换Shape shape =  list.get(0);}
}

上面代码编译完成后,查看字节码内容如下:

Compiled from "Test.java"
public class Test {public Test();Code:0: aload_01: invokespecial #1                  // Method java/lang/Object."":()V4: returnpublic static void main(java.lang.String[]);Code:0: new           #2                  // class java/util/ArrayList3: dup4: invokespecial #3                  // Method java/util/ArrayList."":()V7: astore_18: aload_19: new           #4                  // class Circle12: dup13: invokespecial #5                  // Method Circle."":()V16: invokeinterface #6,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z21: pop22: aload_123: new           #7                  // class Square26: dup27: invokespecial #8                  // Method Square."":()V30: invokeinterface #6,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z35: pop36: aload_137: iconst_038: invokeinterface #9,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;43: checkcast     #10                 // class Shape46: astore_247: return
}

上面的两个示例,字节码内容完全一样。特别主要 38行,不管泛型指定什么类型,最终都会按照 Object 类型来处理。

既然运行时,泛型所指定的类型信息已经被擦除,那运行时怎样实例信息呢?

1.可以在构造函数传入Class信息

2.利用反射

//使用spring 提供的现成方法来获取第一个泛型值Class信息
ResolvableType resolvableType = ResolvableType.forClass(this.getClass());
Class aClass =  resolvableType.getSuperType().getGenerics()[0].resolve();

通配符

表示无界通配符,可以支持所有类型,和原始类型有点相似。既然这样,那和不声明泛型又有什么区别呢?

声明无界通配符,表示接收一个泛型类型。

通配符?后只允许出现一个边界。

通配符只允许出现在引用中(普通变量引用、形参),一般是用作或者。相对地,比如通配符不允许出现在泛型定义中(泛型类、泛型接口、泛型方法的< >里),class one {}这样是不允许的,类定义时继承泛型类时的< >里也不可以出现。在泛型类或泛型方法的{ }里还有泛型方法的形参上,配合占位符,甚至可以使用? extends T或者? super T这种形式来用作引用。

new泛型类的时候也不可以使用通配符,比如new ArrayList()。泛型方法的显式类型说明也不可以使用通配符。

通配符上界

上界通配符声明方式,表示接收的类型只能是指定类型自身及子类。俗称协变,用在读数据场景。

//声明接收类型只能是Shape 及其子类
class Function {}
public class Test {public static void main(String[] args) {Function function = new Function();function = new Function();//编译异常function = new Function();}
}
List list = new ArrayList<>();
//编译异常
list.add(1);List list2 = new ArrayList<>();
//编译异常
list2.add(1);

上面这种写法编译错误,因为指定了通配符编译器并不知道确切类型是什么,所以传入任何类型都有问题。

通配符下界

下界通配符声明方式,表示接收的类型只能是指定类型自身及父类。俗称逆变,用在写数据场景。

class Function {public void get(List list){//添加元素类型是自身及其子类list.add(new Circle());}
}
public class Test {public static  void main(String[] args) {Function function = new Function();function.get(new ArrayList());}
}

参考:

1.《java编程思想》2. 

相关内容