无法重现Type Erasure示例的结果 [英] Cannot reproduce result of Type Erasure example

查看:178
本文介绍了无法重现Type Erasure示例的结果的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在阅读Java Generics and Collections第8.4节。作者在尝试解释二进制兼容性时定义了以下代码:

I am reading 'Java Generics and Collections' section 8.4. The author defines the following code while trying to explain Binary Compatibility:

interface Name extends Comparable {
    public int compareTo(Object o);
}
class SimpleName implements Name {
    private String base;
    public SimpleName(String base) {
        this.base = base;
    }
    public int compareTo(Object o) {
        return base.compareTo(((SimpleName)o).base);
    }
}
class ExtendedName extends SimpleName {
    private String ext;
    public ExtendedName(String base, String ext) {
        super(base); this.ext = ext;
    }
    public int compareTo(Object o) {
        int c = super.compareTo(o);
        if (c == 0 && o instanceof ExtendedName)
        return ext.compareTo(((ExtendedName)o).ext);
        else
        return c;
    }
}
class Client {
    public static void main(String[] args) {
        Name m = new ExtendedName("a","b");
        Name n = new ExtendedName("a","c");
        assert m.compareTo(n) < 0;
    }
}

然后讨论制作Name接口和SimpleName类通用并将ExtendedName保留为原样。因此,新代码为:

and then talks about making the Name interface and SimpleName class generic and leaving the ExtendedName as is. As a result the new code is:

interface Name extends Comparable<Name> {
    public int compareTo(Name o);
}
class SimpleName implements Name {
    private String base;
    public SimpleName(String base) {
        this.base = base;
    }
    public int compareTo(Name o) {
        return base.compareTo(((SimpleName)o).base);
    }
}
// use legacy class file for ExtendedName
class Test {
    public static void main(String[] args) {
        Name m = new ExtendedName("a","b");
        Name n = new ExtendedName("a","c");
        assert m.compareTo(n) == 0; // answer is now different!
    }
}

作者描述了以下行为的结果:

The author describes the result of such an action as following:


假设我们生成了Name和SimpleName,以便他们定义
compareTo(Name),但是我们没有ExtendedName的源代码。由于它只定义
compareTo(Object),调用compareTo(Name)而不是compareTo(Object)的客户端代码将调用SimpleName(定义它)的方法,而不是
ExtendedName(它所在的位置)未定义),因此将比较基本名称但忽略
扩展名。

Say that we generify Name and SimpleName so that they define compareTo(Name), but that we do not have the source for ExtendedName. Since it defines only compareTo(Object), client code that calls compareTo(Name) rather than compareTo(Object) will invoke the method on SimpleName (where it is defined) rather than ExtendedName (where it is not defined), so the base names will be compared but the extensions ignored.

但是,当我只创建名称和SimpleName泛型我得到编译时错误,而不是作者上面描述的内容。错误是:

However when I make only Name and SimpleName generic I get a compile time error and not what the author describes above. The error is:


名称冲突:NameHalfMovedToGenerics.ExtendedName中的compareTo(Object)和Comparable中的compareTo(T)具有相同的擦除不会覆盖其他

name clash: compareTo(Object) in NameHalfMovedToGenerics.ExtendedName and compareTo(T) in Comparable have the same erasure, yet neither overrides the other

这不是我第一次面临这样的问题 - 早些时候在尝试阅读有关擦除的Sun文档时我遇到了类似的问题,我的代码没有显示与作者描述的相同的结果。

And this is not the first time I am facing such an issue - earlier while trying to read Sun documentation on erasure I faced a similar issue where my code doesn't show the same result as described by the author.

我在理解作者试图说的内容时犯了错误?

Have I made a mistake in understanding what the author is trying to say?

我们非常感谢任何帮助。

Any help will be much appreciated.

提前致谢。

推荐答案

这是单独编译下可能出现的问题示例。

This is an example of a problem that can occur under separate compilation.

单独编译的主要细节是,当编译调用者类时,某些信息会从被调用者复制到调用者的类文件中。如果稍后针对被调用者的不同版本运行调用者,则从旧版本的被调用者复制的信息可能与被调用者的新版本不完全匹配,并且结果可能不同。仅通过查看源代码就很难看到。此示例显示了在进行此类修改时,程序的行为如何以令人惊讶的方式发生变化。

The main subtlety with separate compilation is that, when a caller class is compiled, certain information is copied from the callee into the caller's class file. If the caller is later run against a different version of the callee, the information copied from the old version of the callee might not match exactly the new version of the callee, and the results might be different. This is very hard to see by just looking at source code. This example shows how the behavior of a program can change in a surprising way when such a modification is made.

在示例中,名称 SimpleName ,但仍使用旧的,已编译的 ExtendedName 的二进制文件。这真的意味着 ExtendedName 的源代码不可用。当针对修改后的类层次结构编译程序时,它会记录与针对旧层次结构编译时不同的信息。

In the example, Name and SimpleName were modified and recompiled, but the old, compiled binary of ExtendedName is still used. That's really what it means by "the source code for ExtendedName is not available." When a program is compiled against the modified class hierarchy, it records different information than it would have if it were compiled against the old hierarchy.

让我执行步骤I执行以重现此示例。

Let me run through the steps I performed to reproduce this example.

在一个空目录中,我创建了两个子目录 v1 V2 。在 v1 中,我将第一个示例代码块中的类放入单独的文件 Name.java SimpleName.java ExtendedName.java

In an empty directory, I created two subdirectories v1 and v2. In v1 I put the classes from the first example code block into separate files Name.java, SimpleName.java, and ExtendedName.java.

请注意,我是不使用 v1 v2 目录作为包。所有这些文件都在未命名的包中。此外,我正在使用单独的文件,因为如果它们都是嵌套类,则很难单独重新编译它们,这是示例工作所必需的。

Note that I'm not using the v1 and v2 directories as packages. All these files are in the unnamed package. Also, I'm using separate files, since if they're all nested classes it's hard to recompile some of them separately, which is necessary for the example to work.

另外,我将主程序重命名为 Test1.java ,并将其修改如下:

In addition I renamed the main program to Test1.java and modified it as follows:

class Test1 {
    public static void main(String[] args) {
        Name m = new ExtendedName("a","b");
        Name n = new ExtendedName("a","c");
        System.out.println(m.compareTo(n));
    }
}

v1 我编译了所有内容并运行了Test1:

In v1 I compiled everything and ran Test1:

$ ls
ExtendedName.java  Name.java  SimpleName.java  Test1.java
$ java -version
java version "1.7.0_45"
Java(TM) SE Runtime Environment (build 1.7.0_45-b18)
Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode)
$ javac *.java
$ java Test1
-1

现在,在 v2 我放置了 Name.java SimpleName.java 文件,使用泛型修改,如第二个示例代码块中所示。我还将 v1 / Test1.java 复制到 v2 / Test2.java 并相应地重命名了该类,但是否则代码是相同的。

Now, in v2 I placed the Name.java and SimpleName.java files, modified using generics as shown in the second example code block. I also copied in v1/Test1.java to v2/Test2.java and renamed the class accordingly, but otherwise the code is the same.

$ ls
Name.java  SimpleName.java  Test2.java
$ javac -cp ../v1 *.java
$ java -cp .:../v1 Test2
0

这表明名称 m.compareTo(n)的结果不同和 SimpleName 被修改,同时使用旧的 ExtendedName 二进制文件。发生了什么?

This shows that the result of m.compareTo(n) is different after Name and SimpleName were modified, while using the old ExtendedName binary. What happened?

我们可以通过查看 Test1 类的反汇编输出来看到差异(针对旧类)和 Test2 类(针对新类编译)以查看为 m.compareTo(n)生成的字节码通话。仍在 v2

We can see the difference by looking at the disassembled output from the Test1 class (compiled against the old classes) and the Test2 class (compiled against the new classes) to see what bytecode is generated for the m.compareTo(n) call. Still in v2:

$ javap -c -cp ../v1 Test1
...
29: invokeinterface #8,  2     // InterfaceMethod Name.compareTo:(Ljava/lang/Object;)I
...

$ javap -c Test2
...
29: invokeinterface #8,  2     // InterfaceMethod Name.compareTo:(LName;)I
...

编译 Test1 时,复制到的信息Test1.class 文件是对 compareTo(Object)的调用,因为这是 Name 界面就此而已。使用修改后的类,编译 Test2 会产生调用 compareTo(Name)的字节码,因为那是修改后的名称界面现在有。当 Test2 运行时,它会查找 compareTo(Name)方法,从而绕过 ExtendedName 类中的compareTo(Object)方法,调用 SimpleName.compareTo(Name)而不是。这就是行为不同的原因。

When compiling Test1, the information copied into the Test1.class file is a call to compareTo(Object) because that's the method the Name interface has at this point. With the modified classes, compiling Test2 results in bytecode that calls compareTo(Name) since that's what the modified Name interface now has. When Test2 runs, it looks for the compareTo(Name) method and thus bypasses the compareTo(Object) method in the ExtendedName class, calling SimpleName.compareTo(Name) instead. That's why the behavior differs.

请注意旧 Test1 二进制的行为不会改变:

Note that the behavior of the old Test1 binary does not change:

$ java -cp .:../v1 Test1
-1

但是如果 Test1.java 针对新的类层次结构重新编译,它的行为会改变。这基本上是 Test2.java ,但使用不同的名称,以便我们可以轻松地看到运行旧二进制文件和重新编译版本之间的区别。

But if Test1.java were recompiled against the new class hierarchy, its behavior would change. That's essentially what Test2.java is, but with a different name so that we can easily see the difference between running an old binary and a recompiled version.

这篇关于无法重现Type Erasure示例的结果的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆