【问题标题】:Java generics code compiles with javac, fails with Eclipse HeliosJava 泛型代码使用 javac 编译,Eclipse Helios 失败
【发布时间】:2010-08-10 19:53:54
【问题描述】:

我有以下使用泛型重载方法的测试类。它在使用 javac 编译时可以工作,但在 Eclipse Helios 中编译失败。我的java版本是1.6.0_21。

我阅读的所有文章都表明 Eclipse 是正确的,这段代码应该不起作用。但是,当使用 javac 编译并运行时,选择了正确的方法。

这怎么可能?

谢谢!

import java.util.ArrayList;

public class Test {
    public static void main (String [] args) {
        Test t = new Test();
        ArrayList<String> ss = new ArrayList<String>();
        ss.add("hello");
        ss.add("world");
        ArrayList<Integer> is = new ArrayList<Integer>();
        is.add(1);
        is.add(2);
        System.out.println(t.getFirst(ss));
        System.out.println(t.getFirst(is));
    }   
    public String getFirst (ArrayList<String> ss) {
        return ss.get(0);
    }
    public Integer getFirst (ArrayList<Integer> ss) {
        return ss.get(0);
    }
}

【问题讨论】:

  • 如果这是一个实际用例,我只推荐一种方法public &lt;A&gt; A getFirst (ArrayList&lt;A&gt; ss) { return ss.get(0); }...如果它只是为了说明目的而设计的,请继续
  • eclipse中的编译错误是什么?
  • 在伽利略中运行良好,应该如此。
  • @meriton:错误是:方法 getFirst(ArrayList) 与 Test 类型中的另一个方法具有相同的擦除 getFirst(ArrayList)

标签: java eclipse generics javac


【解决方案1】:

Java Language Specification, section 8.4.2 写道:

声明两个方法是编译时错误 覆盖等效签名 (定义如下)在一个类中。

如果 m1 是 m2 的子签名或 m2 是 m1 的子签名,则两个方法签名 m1 和 m2 是重写等效的。

方法 m1 的签名是方法 m2 签名的子签名

  • m2 与 m1 具有相同的签名,或者

  • m1 的签名与 m2 的签名擦除相同。

显然,这些方法不是等效的重写方法,因为 ArrayList&lt;String&gt; 不是 ArrayListArrayList&lt;Integer&gt; 的擦除)。

所以声明方法是合法的。此外,方法调用表达式是有效的,因为只有一个方法与参数类型匹配,所以通常有一个 最具体的方法

编辑: Yishai 正确地指出,在这种情况下还有另一个限制。 Java Language Specification, section 8.4.8.3 写道:

如果类型声明 T 有成员方法,则编译时错误 m1 并且存在一个方法 m2 在 T 或 T 的超类型中声明 满足以下所有条件 持有:

  • m1 和 m2 同名。
  • m2 可从 T.
  • m1 的签名不是 m2 签名的子签名(第 8.4.2 节)。
  • m1 或某些方法 m1 覆盖(直接或间接)与 m2 或某些方法 m2 覆盖(直接或间接)具有相同的擦除。

附录:关于确定性和缺乏确定性

与流行的概念相反,方法签名中的泛型不会被删除。泛型在字节码(Java 虚拟机的指令集)中被擦除。方法签名不是指令集的一部分;它们被写入源代码中指定的类文件。 (顺便说一句,这些信息也可以在运行时使用反射来查询)。

想一想:如果类型参数完全从类文件中删除,您选择的 IDE 中的代码完成如何显示 ArrayList.add(E) 采用 E 类型的参数,而不是 Object(=删除E) 如果您没有附加JDK 源代码?当方法参数的静态类型不是E 的子类型时,编译器如何知道抛出编译错误?

【讨论】:

  • 我认为情况比这更接近。考虑:如果将两种方法的返回类型都更改为 Object,则代码将不再编译。 JLS 并没有说返回类型可以作为方法签名的一个显着特征来确定覆盖等效性。我最终同意 Sun 编译器是正确的,但这是一个非常接近的电话。
  • 好点,我已经编辑包含相关部分。规则是问题不是关于覆盖等价(返回类型被指定为无关紧要),而是更一般的性质。
【解决方案2】:

此代码是正确的,如JLS 15.12.2.5 Choosing the Most Specific Method 中所述。

另外,考虑对接口进行编码:

List<String> ss = new ArrayList<String>();
List<Integer> is = new ArrayList<Integer>();
// etc.

正如@McDowell 所说,修改后的方法签名出现在类文件中:

$ javap build/classes/Test
Compiled from "Test.java"
public class Test extends java.lang.Object{
    public Test();
    public static void main(java.lang.String[]);
    public java.lang.String getFirst(java.util.ArrayList);
    public java.lang.Integer getFirst(java.util.ArrayList);
}

请注意,这与@meriton 对类文件的观察并不矛盾。比如这个片段的输出

Method[] methods = Test.class.getDeclaredMethods();
for (Method m : methods) {
    System.out.println(Arrays.toString(m.getGenericParameterTypes()));
}

显示main()的形参,以及两个泛型类型参数:

[class [Ljava.lang.String;]
[java.util.ArrayList<java.lang.String>]
[java.util.ArrayList<java.lang.Integer>]

【讨论】:

  • 类型擦除后,方法签名变为public String getFirst(ArrayList)public Integer getFirst(ArrayList)
  • 没有 McDowell,方法签名不会被删除。
  • @meriton: @McDowell: IIUC,你们都是正确的:签名被删除并且通用类型参数被保留,如上所述。
【解决方案3】:

在 Eclipse Helios 中为我工作。方法选择发生在编译时,编译器有足够的信息来做这件事。

【讨论】:

    【解决方案4】:

    如果 javac 中存在错误,那将是可能的。 Javac 只是软件,与任何其他软件一样容易出现错误。

    另外,Java 语言规范非常复杂,有些地方有点模糊,因此也可能是 Eclipse 和 javac 之间的解释不同。

    我会首先在 Eclipse 支持频道上询问这个问题。他们通常非常擅长捡起这些东西并解释为什么他们认为自己是对的,或者承认自己错了。

    为了记录,我认为 Eclipse 也在这里。

    【讨论】:

    • 嗯。我纯粹是根据历史平均值进行猜测。鉴于其他人的分析,我怀疑你是对的。无论哪种方式,与 Eclipse 人一起提出这个问题是前进的方向。
    【解决方案5】:

    您确定 Eclipse 也设置为使用 Java 1.6?

    【讨论】:

    • 出于兴趣,您为什么认为这很相关? Eclipse 至少设置为 1.5,否则根本无法编译。您认为 1.5 和 1.6 中的语言语义有什么显着差异吗?
    • 我只是想确保它没有设置为 1.4。我不使用 Eclipse,但我知道 IDE 通常具有独立于您的系统的 Java 版本设置。至于 1.5-1.6 的差异,我不知道有什么重要的。
    • 如果设置为 1.4,由于泛型,它根本不会编译。
    【解决方案6】:

    经过一番研究,我有了答案:

    上面指定的代码不应编译。 ArrayList&lt;String&gt;ArrayList&lt;Integer&gt;,在运行时仍然是 ArrayList。但是您的代码无法正常工作,因为返回类型。如果为这两种方法设置相同的返回类型,javac 将不会编译...

    我了解到 Java 1.6 中有一个关于此错误的错误(Java 1.7 中已修复)。都是关于返回类型的......所以,您需要更改方法的签名。

    bug 6182950 in Oracle's Bug Database

    【讨论】:

      【解决方案7】:

      Eclipse 和 javac 使用不同的编译器。 Eclipse 使用第三方编译器将您的代码转换为 Java VM 的字节码。 Javac 使用的 Java 编译器比 Sun 发布的要多。因此,相同的代码可能会产生略微不同的结果。我相信 Netbeans 使用 Sun 的编译器,所以也请在那里检查它。

      【讨论】:

      • 这里的语言有点松散。需要明确的是,javac 是 Sun (Oracle) 发布的编译器。而且 Eclipse 不使用第三方编译器 - 它使用自己的编译器实现。
      【解决方案8】:

      要记住的一点是(全部?当然有些,例如 NetBeans 编辑器会这样做)静态代码分析工具不会将返回类型或修饰符(私有/公共等)视为方法签名的一部分.

      如果是这种情况,那么在类型擦除的帮助下,getFirst 方法都将获得签名 getFirst(java.util.ArrayList) 并因此触发名称冲突...

      【讨论】:

      • 泛型在 bytecode(Java 虚拟机的指令集)中被擦除。方法签名不是指令集的一部分;它们被写入源代码中指定的类文件。有关更多信息,请参阅我的回答。
      猜你喜欢
      • 2012-11-10
      • 1970-01-01
      • 2020-12-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多