泛型类型擦除的愚蠢

本文探讨了Java泛型在实际应用中的困惑与陷阱,包括类型擦除、原始类型与通配符的区别,以及IDE操作提示的误导。作者通过具体代码示例,揭示了泛型在编译时的静态类型检查机制及其对安全性的考量。

一.泛型的迷惑行为

  1. 嘛,今天翻看了一下以前的程序的warning,虽然都测试完了,但还是有warning。看到一个有趣的事情,有一个泛型还是原始类型没有进行处理。本着消除warning的原则,我让eclipse推断一下类型参数,没想到推断出了编译错误。于是我又改成了?通配符,希望勉强给一个万能类型参数,结果还是有编译问题。只好放在那里勉强能跑。
    感觉有点儿迷惑,以前觉得java类型擦除后,原始类型,无界通配符,Object限定类都是一回儿事。
PlanningEntryAPIs method = new PlanningEntryAPIs1();
method.checkResourceExclusiveConflict(Ap.getEntry())
public boolean checkResourceExclusiveConflict(List<PlanningEntry<R>> entries) 
public List<FlightEntry> getEntry()

FlightEntry接口是PlanningEntry< Plane >接口的继承。
对于在这上面添加上了< Plane>的类型参数会报错,我是知道原因的。
List<PlanningEntry< R>>不是List< FlightEntry >的子类型,泛型不支持协。然而有趣的是没有类型参数可以执行,有<?>不可以执行,有object也不可以执行。
2.还有很讨厌的东西,eclipse中提示的操作竟然是类型错误的操作。

List<?> a = new ArrayList<Object>();
a.add(new Object());//此时打到一半可以看到ide显示当前的add(Object),结果当你输入进去,ide又会报错

3.还有很好玩的操作

      List a = new ArrayList<Integer>();
      a.add(3.14);
      System.out.println(a.get(0));

这段代码完美执行,不会报任何错误。

Java泛型类型擦除带来的恼火

之前说类型擦除是导致很多令人迷惑的事情的源头。我一直不理解。现在有感觉了,我觉得类型擦除最愚蠢的结果,就是把泛型的所有问题都交给了编译器,泛型依靠编译器实现,但还有些能逃脱这个的方法。下面我解释一下。
因为类型擦除,Java的泛型是编译器的结果,一切都是编译器的旨意。编译器的静态类型检查。而为了兼容引入泛型之前的代码,他们又规定原始类型是不受编译器类型检查的,有时候还要搞类型推断。
很多很愚蠢的规则都是这么搞又要保证安全的后果。

坑爹的通配符

以前我觉得<?>就是告诉java编译器,这个泛型引用可以支持任一泛型。
T<? extends A>这个泛型是T< A>的子类,T<? super B>这个泛型是T< B>的父类。

其实我感觉也不太靠谱。我理解通配符就是指导编译器转型用的,这样加上对泛型本身是编译器结果的理解就解释通很多问题。以前觉得像是超类子类。现在我觉得,就是告诉编译器返回值要怎么转型,参数可以接受什么类型。这个关系感觉和父子类还不太一样,T<? super A>这个泛型如果是T< A>父类,可是T< A>中很多方法就会根本失去可以接受的类型。并且从编译结果看,始终只存在一个类罢了。JVM不会生成两个类。并且从我下面的看法也是这样

编译器神奇的安全观

泛型仅仅是编译时对于参数返回值的要求,内部根本没用。
T<? extends A> x= new T< A1>() A1是A的子类。
这样做了以后其实根本泛型就变成了T< A>,你要证明的是对象和这个引用匹配罢了,证明对象和这个引用能达到同一目的。我自认为的编译器的安全就是你的对象和你的引用能做到统一事情。

为什么变成T< A>合法呢,因为如果有个原参数a类型A1,那么它接受A是合理的,参数逆变是安全的。返回值类型A1,向上转型为A更是安全的。所以编译器认为安全。这时候就是非常安全。调用函数也很安全

但是<?>,<? super A1>就属于不怎么安全的了,这也是我为什么总觉得二者不是父子类关系。
T<?> x= new T< A>(),这样做虽然合理,但基本把泛型废掉了。对于A来说根本不知道能转型成什么样子。所有的和泛型参数有关的参数都不能再使用。但返回object一定是合理的。

T <? super A1> = new T< A>(),这么做以后,也是对于和泛型相关的都不能参数和返回值都不能使用,因为不知道转到哪里是安全的。但返回object一定是合理的。

我认为,一切泛型的处理都交给了编译器,所以语法上总是以引用的参数为主,一切安全机制都是静态的。导致很多因素被限制死了,通配符也是从编译的角度限制泛型的安全。

原始类型

按照编译器来解决泛型安全<?>和<? super A1>基本被限制了。
但编译器对原始类型是完全不检查的,返回object,给object为了兼容旧代码,比如jdk 1.8之前的list。
所以一开始我上面的调用如果去掉泛型参数就完全可以调用的。但这么做就是等于要求不要进行静态类型检查,埋下安全隐患。

开始的三个

<?>< Object > 原始类型,编译器要求不同的,<?>因为允许指向一个任意的泛型对象,为了对象和引用相同,很多时候禁止泛型参数。

< Object> 代表泛型参数object
原始类型,不受类型检查,编译时总能合法。
虽然三者的编译结果完全一样,但含义不同。在编译器主导下就是不同。

顺便解释一下第二个问题,eclispe怎么给我们提示的呢?不是通过语法推断的,是通过反射机制来获取类型方法,然后对泛型进行类型推断。反射是运行获取类型。这个类内部的泛型参数的方法仍然存在,并且类型为object。虽然编译器禁止你用了。

第三个问题,原始类型不受检查约束。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值