无法重现Type Erasure示例的结果 [英] Cannot reproduce result of Type Erasure example
问题描述
我正在阅读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.
在示例中,名称修改并重新编译了code>和
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屋!