java 泛型
今天来聊聊java中的泛型,泛型主要对类型进行限定。
下面看一段代码
代码
/**
* 图形
*/
interface Shape {
void draw();
}
/**
* 圆形
*/
class Circle implements Shape {
@Override
public void draw() {
}
}
/**
* 正方形
*/
class Square implements Shape {
@Override
public 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<Shape> 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_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: aload_1
9: new #4 // class Circle
12: dup
13: invokespecial #5 // Method Circle."<init>":()V
16: invokeinterface #6, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
21: pop
22: aload_1
23: new #7 // class Square
26: dup
27: invokespecial #8 // Method Square."<init>":()V
30: invokeinterface #6, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
35: pop
36: aload_1
37: iconst_0
38: invokeinterface #9, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
43: checkcast #10 // class Shape
46: astore_2
47: return
}
示例2
public class Test {
public static void main(String[] args) {
//指定泛型
List<Shape> 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_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: aload_1
9: new #4 // class Circle
12: dup
13: invokespecial #5 // Method Circle."<init>":()V
16: invokeinterface #6, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
21: pop
22: aload_1
23: new #7 // class Square
26: dup
27: invokespecial #8 // Method Square."<init>":()V
30: invokeinterface #6, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
35: pop
36: aload_1
37: iconst_0
38: invokeinterface #9, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
43: checkcast #10 // class Shape
46: astore_2
47: return
}
上面的两个示例,字节码内容完全一样。特别主要 38
行,不管泛型指定什么类型,最终都会按照 Object
类型来处理。
既然运行时,泛型所指定的类型信息已经被擦除,那运行时怎样实例信息呢?
1.可以在构造函数传入Class信息
2.利用反射
//使用spring 提供的现成方法来获取第一个泛型值Class信息
ResolvableType resolvableType = ResolvableType.forClass(this.getClass());
Class<?> aClass = resolvableType.getSuperType().getGenerics()[0].resolve();
通配符
<?>
表示无界通配符,可以支持所有类型,和原始类型有点相似。既然这样,那和不声明泛型又有什么区别呢?
声明无界通配符,表示接收一个泛型类型。
通配符?
后只允许出现一个边界。
通配符只允许出现在引用中(普通变量引用、形参),一般是用作<? extends 具体类型>
或者<? super 具体类型>
。相对地,比如通配符不允许出现在泛型定义中(泛型类、泛型接口、泛型方法的< >
里),class one<? extends Integer> {}
这样是不允许的,类定义时继承泛型类时的< >
里也不可以出现。在泛型类或泛型方法的{ }
里还有泛型方法的形参上,配合占位符,甚至可以使用? extends T
或者? super T
这种形式来用作引用。
在new
泛型类的时候也不可以使用通配符,比如new ArrayList<?>()
。泛型方法的显式类型说明也不可以使用通配符。
通配符上界
<? extends 类型>
上界通配符声明方式,表示接收的类型只能是指定类型自身及子类。俗称协变,用在读数据场景。
//声明接收类型只能是Shape 及其子类
class Function<T extends Shape> {
}
public class Test {
public static void main(String[] args) {
Function function = new Function<Shape>();
function = new Function<Circle>();
//编译异常
function = new Function<Color>();
}
}
List<?> list = new ArrayList<>();
//编译异常
list.add(1);
List<? extends Number> list2 = new ArrayList<>();
//编译异常
list2.add(1);
上面这种写法编译错误,因为指定了通配符编译器并不知道确切类型是什么,所以传入任何类型都有问题。
通配符下界
<? super 类型>
下界通配符声明方式,表示接收的类型只能是指定类型自身及父类。俗称逆变,用在写数据场景。
class Function {
public void get(List<? super Circle> list){
//添加元素类型是自身及其子类
list.add(new Circle());
}
}
public class Test {
public static void main(String[] args) {
Function function = new Function();
function.get(new ArrayList<Shape>());
}
}
参考:
1.《java编程思想》
2. <https://blog.csdn.net/anlian523/article/details/100865538>