在课上学习泛型时,老师提到不能使用instanceof这个运算符来检验泛型类型的信息,因为“运行时泛型消失了”:
我有点不太明白这句话该怎么理解:消失的到底是什么东西?instanceof又是什么?而且后面又一次出现instanceof时我们被告诫尽量避免使用这个运算符,这让我感到很奇怪(注:下图中instanceof后面不应该有括号,它是个运算符而不是方法):
一方面我不知道这个东西到底怎么使用,另一方面也不知道为什么要尽量避免使用。此外,在我的想象中泛型<E>中的E在使用时确定了类型以后会被替换掉,如果instanceof检测类型的话应该能检测出来这个确定的类型才对,然而现实中并不能。于是我决定对其进行一番探究,相信如果掌握了它的使用机制,既能在适当的时侯正确使用,又能帮助理解泛型的实现。
最开始我先尝试探究instanceof的作用域。显然,主数据类型是无法进行判断的(将Integer改成int也过不了编译):
但是Java为主数据类型提供了自动封装成对象的机制,所以直接定义成对象就没问题了:
那么如果换成父类呢?众所周知,Object是所有对象的父类。测试结果表明没问题:
为了保险起见以及后续的探究,我补充了两个简单的类以及一个接口,它们之间具有继承关系以及接口的使用:
首先搞了一个子类的对象,分别用本身这个类、父类和接口名进行测试:
结果是3个true,这说明无论是父类还是接口,只要继承或引用了,instanceof就都能体现出来。从逻辑上讲,这也是十分合理的:从中文翻译上来讲,instanceof的意思就是“本质上是”,那么继承父类的子类当然也是父类了,接口也是如此。接下来Animal的测试结果也没有什么可说的:
接下来一个很有价值的测试点就是多态。对于多态变量,重写和重载方法的选择机制已经比较复杂了,那instanceof会怎样呢?我声明了一个Animal类型的变量并将其实例化为Dog,再次进行了测试:
这回的结果仍然是3个true,说明“本质上是”的理解方式仍然行的通。虽然Mark被声明为了Animal,但是其实例化出来的是Dog,因为Dog继承了Animal又引用了Mammal接口,所以当然Mark在本质上也是Animal和Mammal。那么如果把Animal换成接口Mammal呢?
不出意外地,结果仍然是3个true,因为Mark本质上仍然是Dog,它被声明成啥并不重要。接下来测试测试泛型吧:
可以看到a被实例化为了只能装String的HashSet,无论是接口Set还是HashSet都能通过instanceof,因为a在本质上当然是HashSet也是Set。那么对于更为具体的Set<String>以及HashSet<String>呢?
结果是根本不能通过编译。在这里,IDE给出的错误提示告诉我们,一旦泛型被参数化了就不能再执行instanceof了,建议我们将尖括号里面的String改成问号,并解释说在运行时泛型类型信息会被抹掉。我先试了一下改成问号,果然没问题了:
所以看样子在运行时并不是将XX<E>中的E替换成某种具体的类型,而是把整个<E>抹掉了只剩下XX,这才能解释为什么我限定了括号里是String时不能通过编译而去掉或者换成通配符才行。这颠覆了我最初的想象
总结一下:instanceof的使用方法是看一个对象的“本质”,对主数据类型不适用;“本质”指的是实例化new的类,那个类的本身、其父类、引用的接口都能通过instanceof检测,这与这个对象被声明成什么类无关;泛型在运行时被抹掉的信息是括号中在使用时确定的类,因此当然不能用instanceof检测出具体的类型,这也就解答了文首的第一个问题。回过头来再看那一下不建议使用instanceof的那段话:
当然不能用父类中使用instanceof来检测子类了。不过我认为这个说法可能有些过于绝对,以及课程中讲的“不能将父类强转为子类”。回想当初自己写桥牌软件时用socket传输东西,在传送过程中用的都是Object类型,而传的东西原来是什么类型实际上还隐藏在它自身,在接收的时候完全可以强转回它原来的类型,所以应该也能用instanceof检测出来。