1.java里的泛型是伪泛型?
答案:是的,java泛型在编译的时候会把泛型换成object,这种规则称为类型擦除,你不信?
那好,我们来验证一下看下面一段代码
List <Integer> list = new ArrayList<>();
List <String> list1 = new ArrayList<>();
System.out.println(list1.getClass()==list.getClass());//true
答案是true,很奇怪,为啥明明两个泛型不一样的,但他们getclass返回的一样呢?这就是因为java的泛型在编译的时候会将数据类型转化为Object类型,所以这次判断的时候就显示的是true
2.java是所有泛型都是把各种类型转为Object吗?
答案:不是我们来看下面一段代码
public class Test {
public static void main(String[] args) {
/**不指定泛型的时候*/
int i = Test.add(1, 2); //这两个参数都是Integer,所以T为Integer类型
Number f = Test.add(1, 1.2); //这两个参数一个是Integer,以风格是Float,所以取同一父类的最小级,为Number
Object o = Test.add(1, "asd"); //这两个参数一个是Integer,以风格是String,所以取同一父类的最小级,为Object
/**指定泛型的时候*/
int a = Test.<Integer>add(1, 2); //指定了Integer,所以只能为Integer类型或者其子类
int b = Test.<Integer>add(1, 2.2); //编译错误,指定了Integer,不能为Float
Number c = Test.<Number>add(1, 2.2); //指定为Number,所以可以为Integer和Float
}
//这是一个简单的泛型方法
public static <T> T add(T x,T y){
return y;
}
}
所以说就是说如果没有指定泛型的时候他会动态的变化,但是如果指定了那么就只能是他的子类和他自身,但是如果是这样呢?
不难发现 本身没有报错的报错了这又是为啥?看我第二个红框,发现啥不同的吗?就是说这个T类型是继承Number的所以自然就是这个T只能是Number本身和他的子类,这也叫做泛型的上界
3.那么我想问你一个问题:如果我定义一个类他的所有属性多是T类型的那么如果继承他的时候我定义这个T为Date那么当我在写他的子类的set get方法时会报错吗,先看父类代码
class dog <T>{
T name;
public T getName() {
return name;
}
public void setName(T name) {
this.name = name;
}
}
按照我们之前的理论当然会报错,因为,你的T在编译时会变为Object那么他的子类继承的就是一个属性都是Object类型的父类,那么子类的set get 方法为了覆盖父类的方法到底是写成Object还是Date呢?还是说都行?
答案是只有Date可以,这是为为啥?我们一步一步分析,其实父类的T确实是转为了Object,那么我们在以前的认知里这不就不是方法的重载了吗?而不是重写了不是吗?但是我我们从你一个角度来看
public static void main(String[] args) throws ClassNotFoundException {
HotDog hotDog = new HotDog();
hotDog.setName(new Date());
hotDog.setName(new Object());//编译错误
}
如果是重载的话,那么就Hotdog就应该有来自父类的Object类型的setname但是没有,这就说明了这是重写,但是其实都不是,jvm为我们提供了一种自动调整,叫做桥方法
class com.tao.test.DateInter extends com.tao.test.Pair<java.util.Date> {
com.tao.test.DateInter();
Code:
0: aload_0
1: invokespecial #8 // Method com/tao/test/Pair."<init>":()V
4: return
public void setValue(java.util.Date); //我们重写的setValue方法
Code:
0: aload_0
1: aload_1
2: invokespecial #16 // Method com/tao/test/Pair.setValue:(Ljava/lang/Object;)V
5: return
public java.util.Date getValue(); //我们重写的getValue方法
Code:
0: aload_0
1: invokespecial #23 // Method com/tao/test/Pair.getValue:()Ljava/lang/Object;
4: checkcast #26 // class java/util/Date
7: areturn
public java.lang.Object getValue(); //编译时由编译器生成的巧方法
Code:
0: aload_0
1: invokevirtual #28 // Method getValue:()Ljava/util/Date 去调用我们重写的getValue方法;
4: areturn
public void setValue(java.lang.Object); //编译时由编译器生成的巧方法
Code:
0: aload_0
1: aload_1
2: checkcast #26 // class java/util/Date
5: invokevirtual #30 // Method setValue:(Ljava/util/Date; 去调用我们重写的setValue方法)V
8: return
}
但是又有一个问题就是既然有桥方法的存在那么为啥编译报错呢?其实这个桥方法只是调用了我们重写的方法而已,所以才会报错,但是桥方法完美的解决了类型消除和动态带来的矛盾。
并且,还有一点也许会有疑问,子类中的巧方法Object getValue()
和Date getValue()
是同时存在的,可是如果是常规的两个方法,他们的方法签名是一样的,也就是说虚拟机根本不能分别这两个方法。如果是我们自己编写Java代码,这样的代码是无法通过编译器的检查的,但是虚拟机却是允许这样做的,因为虚拟机通过参数类型和返回类型来确定一个方法,所以编译器为了实现泛型的多态允许自己做这个看起来“不合法”的事情,然后交给虚拟器去区别。
4.到这了,我再问一个问题就是为啥泛型不能是基础类型,自己想把,答案我放在评论区。
5.讲到这了,我好像多没有解释为啥下面这段代码可以,虽然我们却看着没有问题,但是今天讲了这个类型消除,你还觉得没问题吗?
为啥报错?
其实是因为编译器的原因,后面的newArraylist只是开辟了一段空间罢了,编译器只看你的引用,换句话说就是你的引用才是辨别你的集合的类型的身份,
然后这里提一嘴,Java编译器是通过先检查代码中泛型的类型,然后再进行类型擦除,再进行编译。所以说如果你第一步过不了那就会编译报错
著作权归https://pdai.tech所有。 链接:Java 基础 - 泛型机制详解 | Java 全栈知识体系
如何理解泛型类中的静态方法和静态变量?
泛型类中的静态方法和静态变量不可以使用泛型类所声明的泛型类型参数
举例说明:
public class Test2<T> {
public static T one; //编译错误
public static T show(T one){ //编译错误
return null;
}
}
因为泛型类中的泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用。对象都没有创建,如何确定这个泛型参数是何种类型,所以当然是错误的。
但是要注意区分下面的一种情况:
public class Test2<T> {
public static <T >T show(T one){ //这是正确的
return null;
}
}
因为这是一个泛型方法,在泛型方法中使用的T是自己在方法中定义的 T,而不是泛型类中的T。
这段转载自Java 基础 - 泛型机制详解 | Java 全栈知识体系 (pdai.tech)