泛型,JDK1.5添加的特性,这都0202年了,还没整明白?今天我们就来再回顾下,本篇主要介绍以下几个方面:
希望能给你带来一点点帮助或者欢乐~
public interface GenericInterface<T>
{
public T get(T t);
}
public class GenericClass<T>
{
public T add(T t)
{
return t;
}
/**
* 泛型方法,单独的泛型声明擦除为Object
*/
public static <V> V add(V v1, V v2)
{
return v1;
}
/**
* 有继承上限说明的擦除为父类类型
*/
public <A extends Number> void get(A a)
{}
}
// 泛型接口
public <V> V add(V v);
我们知道Java泛型是伪泛型,原因在于在编译期所有的泛型都会被擦除,在生成的字节码中只保留最基本的原始类型,那么如何定义最基本的原始类型呢?
单独的泛型声明擦除为Object:< T > -> Object
有继承上限说明的擦除为父类类型:< T extends Number> -> Number
下面通过javap -s 获取的字节码充分说明了这个问题:
Compiled from "GenericClass.java"
public class com.kevin.genericity.GenericClass<T> {
public com.kevin.genericity.GenericClass();
descriptor: ()V
public T add(T);
//单独的泛型声明擦除为Object: -> Object
descriptor: (Ljava/lang/Object;)Ljava/lang/Object;
public static <V> V add(V, V);
descriptor: (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
public <A extends java.lang.Number> void get(A);
//有继承上限说明的擦除为父类类型: -> Number
descriptor: (Ljava/lang/Number;)V
}
以上是编译后的类型擦除,而编译前是需要进行检查的,检查的规则又是怎么样的?
// 这两个参数都是Integer,所以V为Integer
int res = GenericClass.add(1, 2);
// 一个参数为Integer,一个参数为Double 返回类型取同一父类Number
Number res2 = GenericClass.add(1, 2.0d);
// 这两个参数一个是Integer,一个是String,所以取同一父类,为Object
Object res3 = GenericClass.add(1, "2");
以上只是说明,能通过编译检查了,那真正到编译器后类型消除后又是什么样子呢?
Compiled from "GenericClass.java"
public class com.kevin.genericity.GenericClass<T> {
public com.kevin.genericity.GenericClass();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."":()V
4: return
public T add(T);
Code:
0: aload_1
1: areturn
public static <V> V add(V, V);
Code:
0: aload_0
1: areturn
public <A extends java.lang.Number> void get(A);
Code:
0: return
public static void main(java.lang.String[]);
Code:
0: iconst_1
1: invokestatic #37 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: iconst_2
5: invokestatic #37 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
8: invokestatic #43 // Method add:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
11: checkcast #38 // class java/lang/Integer
14: invokevirtual #45 // Method java/lang/Integer.intValue:()I
17: istore_1
18: iconst_1
19: invokestatic #37 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
22: ldc2_w #49 // double 2.0d
25: invokestatic #51 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
28: invokestatic #43 // Method add:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
31: checkcast #56 // class java/lang/Number
34: astore_2
35: iconst_1
36: invokestatic #37 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
39: ldc #58 // String 2
41: invokestatic #43 // Method add:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
44: astore_3
45: return
}
我们发现全部都是Object类型的,那就很疑问了,如果都是Object为啥编译前还要进行类型检查呢? 再次顺着思路的猜测是这样的:编译前的类型检查时为了运行时能将Object类型强转为正确的类型,编译期的检查保证了代码的正常运行,避免了编译期不报错,运行时却各种错误的情况。
进而我们可以推断出,类型检查是针对引用的,针对引用的目的是Object类型能正确强转为引用类型,let`s try it~
ArrayList<String> list1 = new ArrayList<String>();
list1.add("1"); //编译通过
list1.add(1); //编译错误
String str1 = list1.get(0); //返回类型为String
ArrayList list2 = new ArrayList<String>();
list2.add("1"); //编译通过
list2.add(1); //编译通过
Object object = list2.get(0); //返回类型为Object
new ArrayList<String>().add("11"); //String 编译通过
new ArrayList<>().add(22); //Object 编译通过
new ArrayList<String>().add(11); //String 编译错误
amusing !!! 简直太好玩了。
对于代码:
public class GenericClass<T>
{
public T add(T t)
{
return t;
}
}
public class SubGenericClass extends GenericClass<String>
{
@Override
public String add(String t)
{
return t;
}
}
我们知道,子类SubGenericClass 重写了父类add方法,一点问题都没有,不管是理解还是运行,和我们的理解都没有任何冲突。那编译后的字节码又是怎样的呢?
public com.kevin.genericity.SubGenericClass();
Code:
0: aload_0
1: invokespecial #8 // Method com/kevin/genericity/GenericClass."":()V
4: return
public java.lang.String add(java.lang.String);
Code:
0: aload_1
1: areturn
public java.lang.Object add(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: checkcast #20 // class java/lang/String
5: invokevirtual #22 // Method add:(Ljava/lang/String;)Ljava/lang/String;
8: areturn
}
哦吼~ 怎么有两个add方法呢? Why?
Jvm大喊:臣妾做不到啊!
对于父类GenericClass的泛型参数T被子类确定为String,jvm是知道的,但是它做不到,泛型消除后,父类的T成为了Object,那对于子类来说,我本来是重写一个参数为String的方法,你却让我重载了一个参数为Object类型的方法,嗯?掉我包?
为了解决这个问题,Jvm不得不生成一个中间方法public Object add(Object);来调用你重写的public String add(String); 说实话jvm也是挺不容易的,不过问题总归解决了。对表层用户来说一点感觉都没有,世界观也不会崩塌。
谢天谢地,世界还在。
public void get(GenericClass<?> t)
{
t.get();
}
public <U> void get(GenericClass<U> t)
{
t.get();
}
觉得还行的大哥三连一波,小弟博客有点惨淡,写文不易,你的点赞就是我最大的动力!✨