我正在研究Java通用功能,我不知道如何解释以下main
方法中的第三行:
public class Example4 {
public static void main(final String[] args) {
System.out.println(Util.<String>compare("a", "b"));
System.out.println(Util.<String>compare(new String(""), new Long(1)));
System.out.println(Util.compare(new String(""), new Long(1)));
}
}
class Util {
public static <T> boolean compare(T t1, T t2) {
return t1.equals(t2);
}
}
第一行编译,运行和返回(如预期的那样)false
。
第二行没有按预期编译,因为我明确地混合了String
和Long
。
第三行编译,运行并返回false但我不确定它是如何工作的:编译器/ JVM是否将参数类型T
实例化为Object
? (另外,有没有办法获得这个声明类型的T
是运行时?)
谢谢。
答案 0 :(得分:21)
共享的继承类型String
和Long
是Object
。
当您将此函数作为Util.<String>compare(
运行时,编译器期望找到两个字符串输入,并在没有时发出错误。但是,在没有<String>
的情况下运行它会导致使用最接近的共享继承类型 - 在本例中为Object
。
因此,当compare
接受t1
和t2
时,它们已被转换为Object
,代码运行正常。
要在运行时获取实际类型,您可以使用与其他任何对象相同的技术:getClass()
继承自Object
类。
答案 1 :(得分:9)
答案似乎超越了@Telthien和@newacct的答案。我很好奇地“看到”了我自己之间的区别:
System.out.println(Util.<String>compare("a", "b"));
明确键入,并且:
System.out.println(Util.compare(new String(""), new Long(1)));
隐式输入。
我使用前两行的变体进行了几次实验。这些实验表明,如果不使用anonymous/local class trick,编译器会在编译期间检查类型,但生成的字节码仅引用Object
,即使在第一行的情况下也是如此。
以下代码表明,即使在明确类型参数Object
的情况下,类型转换也可以安全地执行到<String>
。
public final class Example44 {
public static void main(final String[] args) {
System.out.println(new Util44<String>().compare("a", "b"));
System.out.println(new Util44().compare(new String(""), new Long(1)));
}
}
final class Util44<T> {
private T aT;
public boolean compare(T t1, T t2) {
System.out.println(this.aT);
// I was expecting the second and third assignments to fail
// with the first invocation because T is explicitly a String
// and then to work with the second invocation because I use
// a raw type and the compiler must infer a common type for T.
// Actually, all these assignments succeed with both invocation.
this.aT = (T) new String("z");
this.aT = (T) new Long(0);
this.aT = (T) new Object();
return t1.equals(t2);
}
}
main
方法的字节码如下:
// Method descriptor #15 ([Ljava/lang/String;)V
// Stack: 7, Locals: 1
public static void main(java.lang.String[] args);
0 getstatic java.lang.System.out : java.io.PrintStream [16]
3 new ca.polymtl.ptidej.generics.java.Util44 [22]
6 dup
7 invokespecial ca.polymtl.ptidej.generics.java.Util44() [24]
10 ldc <String "a"> [25]
12 ldc <String "b"> [27]
14 invokevirtual ca.polymtl.ptidej.generics.java.Util44.compare(java.lang.Object, java.lang.Object) : boolean [29]
17 invokevirtual java.io.PrintStream.println(boolean) : void [33]
20 getstatic java.lang.System.out : java.io.PrintStream [16]
23 new ca.polymtl.ptidej.generics.java.Util44 [22]
26 dup
27 invokespecial ca.polymtl.ptidej.generics.java.Util44() [24]
30 new java.lang.String [39]
33 dup
34 ldc <String ""> [41]
36 invokespecial java.lang.String(java.lang.String) [43]
39 new java.lang.Long [46]
42 dup
43 lconst_1
44 invokespecial java.lang.Long(long) [48]
47 invokevirtual ca.polymtl.ptidej.generics.java.Util44.compare(java.lang.Object, java.lang.Object) : boolean [29]
50 invokevirtual java.io.PrintStream.println(boolean) : void [33]
53 return
Line numbers:
[pc: 0, line: 24]
[pc: 20, line: 25]
[pc: 53, line: 26]
Local variable table:
[pc: 0, pc: 54] local: args index: 0 type: java.lang.String[]
实际上有意义的是,所有调用始终都是Object
作为形式参数类型的方法,如in another question/answer所述。为了得出结论,编译器总是使用Object
来生成字节码,无论是否存在明确类型参数(第一行)或隐式类型参数,但对象可以具有与{{1}不同的公共超类}。
答案 2 :(得分:2)
是的,Object
是允许编译的T
的选择。从概念上讲,编译器推断 T
的类型。它特别推断的并不重要 - 只要它可以推断某些类型将适用于T
,那么它就会编译。推断类型是什么并不重要,因为它对编译的代码没有影响。