28

我正在研究 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

第二行没有按预期编译,因为我明确混合StringLong.

第三行编译、运行并返回 false,但我不确定它是如何工作的:编译器/JVM 是否将参数类型实例T化为Object?(另外,有没有办法获得这种声明的T运行时类型?)

谢谢你。

4

3 回答 3

21

String和的共享继承类型LongObject

当您运行此函数时,Util.<String>compare(编译器希望找到两个字符串输入,并在没有找到时给出错误。但是,不运行它会<String>导致使用最接近的共享继承类型——在这种情况下,Object.

因此,当compare接受t1and时t2,它们被转换为Object,并且代码运行良好。

要在运行时获取实际类型,您可以使用与任何其他对象相同的技术:getClass()继承自Object类。

于 2013-04-08T08:52:13.563 回答
11

答案似乎超出了@Telthien 和@newacct 的答案。我很想自己“看到”以下之间的区别:

System.out.println(Util.<String>compare("a", "b"));

明确键入,并且:

System.out.println(Util.compare(new String(""), new Long(1)));

带有隐式类型。

我使用前两条线的变体进行了几次实验。这些实验表明,在没有使用匿名/本地类技巧的情况下,编译器会在编译期间检查类型,但生成的字节码仅引用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所解释的那样,所有调用总是对具有形式参数类型的方法实际上是有道理的。总而言之,编译器总是使用生成的字节码,不管是否存在显式类型参数(第一行)或隐式类型参数,但对象可能具有不同于.ObjectObject

于 2013-04-15T07:54:40.920 回答
2

是的,Object这是一个T允许它编译的选择。从概念上讲,编译器推断T. 它特别推断出什么并不重要——只要它可以推断出某种类型适用于T,那么它就会编译。推断的类型是什么并不重要,因为它对编译后的代码没有影响。

于 2013-04-08T20:27:48.773 回答