<T> T 到底是什么东东
Java泛型的语法相当的别扭,看到一个这样的写法,感觉到很神奇,正好研究下Java泛型是怎么实现的。
public class A{ public static void main(String[] args) { A a = new A(); a.test(); String r = a.test(); } public <T> T test() { return (T) new Object(); } }
刚开始时,我看到那个"<T> T“ 感觉很神奇,但没什么意义。
查看下test()函数生成的字节码:
public test()Ljava/lang/Object; L0 LINENUMBER 14 L0 NEW java/lang/Object DUP INVOKESPECIAL java/lang/Object.<init>()V ARETURN
可以发现,这个函数实际上的返回类型是Object,和T没什么关系。
再看下调用test()函数的地方对应的字节码:
a.test(); String r = a.test();
字节码:
L1 LINENUMBER 5 L1 ALOAD 1 INVOKEVIRTUAL A.test()Ljava/lang/Object; POP L2 LINENUMBER 7 L2 ALOAD 1 INVOKEVIRTUAL A.test()Ljava/lang/Object; CHECKCAST java/lang/String ASTORE 2
可以看到a.test() 实际上只是调用了下test()函数,返回值直接被pop掉了,没有那个T什么事。
String r = a.test()处,则有个CHECKCAST指令,检查类型转换有没有成功。
所以我们可以看到<T> T这种写法实际上是一个语法糖,它和下面这种写法从本质上来说没有区别。
public class A{ public static void main(String[] args) { A a = new A(); a.test(); String r = (String) a.test(); } public Object test() { return new Object(); } }
extends的情况
下面再来看个复杂点的例子:
public class A{ interface interface1{ public String interfaceOne (); } public <T extends Date & interface1> T test1(T t) { t.interfaceOne(); t.toLocaleString(); return null; } }
对应的字节码分析:
public test1(Date) : Date L0 LINENUMBER 21 L0 ALOAD 1: t CHECKCAST A$interface1 INVOKEINTERFACE A$interface1.interfaceOne() : String POP L1 LINENUMBER 22 L1 ALOAD 1: t INVOKEVIRTUAL Date.toLocaleString() : String POP L2
可以看到,用了extends来限定参数的类型后,函数传进来的参数直接是Date类型的了。
不过,当中间调用到interface1.interfaceOne()时,还是需要一个CHECKCAST来进行类型转换。
关于CHECKCAST指令
http://www.vmth.ucdavis.edu/incoming/Jasmin/ref--7.html
checkcast checks that the top item on the operand stack (a reference to an object or array) can be cast to a given type. For example, if you write in Java:
return ((String)obj);
then the Java compiler will generate something like:
aload_1 ; push -obj- onto the stack checkcast java/lang/String ; check its a String areturn ; return it
checkcast is actually a shortand for writing Java code like:
if (! (obj == null || obj instanceof <class>)) { throw new ClassCastException(); } // if this point is reached, then object is either null, or an instance of // <class> or one of its superclasses.
所以CHECKCAST指令实际上和INSTANCEOF指令是很像的,不同的是CHECKCAST如果失败,会抛出一个异常,INSTANCEOF是返回一个值。
http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.instanceof
The instanceof instruction is very similar to the checkcast instruction
(§checkcast).
It differs in its treatment of null
, its behavior when its test fails (checkcast throws
an exception,instanceof pushes a result code), and its effect on the operand stack.
另外,据虚拟机专家RednaxelaFX的说法,JVM有可能在运行时优化掉一些CHECKCAST指令。
http://hllvm.group.iteye.com/group/topic/25910
总结:
JAVA的泛型只是一个语法糖,实际上在运行时还是有类型转换的过程,从JVM生成的代码来看,和传递一个Object(或者extends的类型)没什么区别。当然泛型的最大好处是编绎期的类型错误检查。
明白JAVA泛型的大致实现原理之后,看很多泛型代码都比较清晰了:)
和C++的泛型比较,C++的泛型是在编绎期实现的,为每一个类型都生成一份代码,所以C++的泛型容易让编绎后的代码出现膨胀。
C++不会保证在运行时,你硬塞一个什么东东进去函数里去执行的结果(极有可能程序挂掉了)。
但是Java代码是跑在JVM里的,要保证程序无论如何都能正常跑,所以泛型肯定会有CHECKCAST这样的消耗。
实际上CHECKCAST算是一个运行时的输入检查了,而C++没有这种检查,Java则要求要这种检查。而JVM则有可能优化掉这些检查,比如前面已经确认过对象的类型了,那么CHECKCAST就有可能被优化掉。