具有相同名称但不同类型的变量

我在这里读到,在Java中,两个具有相同名称但不同类型的变量可能在同一范围内共存。 我的意思是这个

class test { private int x; private double x; } 

但是所有java IDE都不允许这样的代码。 我想知道这样的代码是否真的在语法上是正确的,或者只是IDE不允许这样的代码来防止歧义。

无论如何这里是网站的摘录

“如果你很幸运,你可能能够重新编译来自Jad的输出。 但是,Java VM对变量命名的规则比Java语言本身更宽松。例如,有效的类文件可以有几个名为’a的变量’,只要它们有不同的类型。如果你反编译这样的类,你得到的源代码将无效。

JAD通常会重命名有问题的字段,并制作一个可重新编译的文件……唯一的问题是重新编译的文件与原始类不兼容。“

您不能在同一范围内存在具有相同名称(但不同类型)的变量。 考虑一下,如果有可能,那么java编译器将如何确定你的意思。

请考虑此代码段

 class test { private int x; private double x; test() //constructor { System.out.println(x); //Error cannot determine which x you meant } } 

java编译器无法理解您实际引用的是哪个x。 所以这样的代码在语法上不正确且不可编译。

但是,存在诸如ClassEditor之类的工具,它们可以在创建后修改生成的类文件。 在那里可以将两个变量的名称更改为相同。

但是这样的类不一定是java jvm可以运行的。

您引用的软件即JAD可以在类文件中重命名这些重复的命名变量,这样您将获得的源代码实际上在语法上是正确的

正如其他人所说,Java非法,但字节码合法。

javac断言

assert是一个Java示例,它在Oracle JDK 1.8.0_45中生成多个具有相同名称但不同类型的字段。 例如:

 public class Assert { // We can't use a primitive like int here or it would get inlined. static final int[] $assertionsDisabled = new int[0]; public static void main(String[] args) { System.out.println($assertionsDisabled.length); // currentTimeMillis so it won't get optimized away. assert System.currentTimeMillis() == 0L; } } 

assert的存在会生成一个bool $assertionsDisable一个合成字段来缓存方法调用,请参阅: https : //stackoverflow.com/a/29439538/895245了解详细信息。

然后:

 javac Assert.java javap -c -constants -private -verbose Assert.class 

包含以下行:

  #3 = Fieldref #9.#28 // Assert.$assertionsDisabled:[I #5 = Fieldref #9.#31 // Assert.$assertionsDisabled:Z #12 = Utf8 $assertionsDisabled #28 = NameAndType #12:#13 // $assertionsDisabled:[I #31 = NameAndType #12:#14 // $assertionsDisabled:Z public static void main(java.lang.String[]); 3: getstatic #3 // Field $assertionsDisabled:[I 10: getstatic #5 // Field $assertionsDisabled:Z 

注意常量表甚至将#12重用为变量名。

如果我们已经声明了另一个布尔值,那么它将无法编译:

 static final boolean $assertionsDisabled = false; 

有错误:

 the symbol $assertionsDisabled conflicts with a compile synthesized symbol 

这也是为什么使用带有美元符号的字段名称是一个非常糟糕的主意: 何时应该在变量名称中使用美元符号($)?

茉莉

当然,我们也可以尝试使用Jasmin:

 .class public FieldOverload .super java/lang/Object .field static f I .field static f F .method public static main([Ljava/lang/String;)V .limit stack 2 ldc 1 putstatic FieldOverload/f I ldc 1.5 putstatic FieldOverload/f F getstatic java/lang/System/out Ljava/io/PrintStream; getstatic FieldOverload/f I invokevirtual java/io/PrintStream/println(I)V getstatic java/lang/System/out Ljava/io/PrintStream; getstatic FieldOverload/f F invokevirtual java/io/PrintStream/println(F)V return .end method 

其中包含两个静态字段,一个是intI ),一个是floatF ),还有输出:

 1 1.5 

如果有效,因为:

  • getstatic指向常量表上的Fieldref结构
  • Fieldref指向NameAndType
  • 显然, NameAndType指向该类型

因此,为了区分它们,Jasmin只使用两种不同类型的Fieldref

根据语言规范( JLS 8.3 ):

类声明的主体声明两个具有相同名称的字段是编译时错误。

您引用的语句是关于类文件(即编译文件,而不是源代码)。

当然,您不能在同一个类中使用int x和long x字段,就像您不能拥有两个具有相同名称和参数列表的方法一样。 但这是在源级别。 JVM和字节码有不同的规则。 考虑一下:

包装测试;

 public class Test { static int x; @Override protected Test clone() throws CloneNotSupportedException { return this; } public static void main(String[] args) { int y = x; } } 

如果您使用字节码大纲工具(我已经使用了Andrey Loskutov的Eclipse插件),您将在此行中看到int Test.main()

 GETSTATIC test/Test.x : I 

这是JVM从字段x加载值的方式,其全名是“test / Test.x:I”。 这会向您提示字段值是否存在于字段全名中。

众所周知,javac不是创建类的唯一方法,有工具/库可以直接创建字节码,并且可以自由创建具有相同名称但不同类型的字段的类。 JNI也是如此。

很难展示我试图certificate的一个有效例子。 但让我们考虑方法,类似的问题。 字节码分析表明,在Test类中有两种方法具有相同的名称和参数,这是JLS不允许的:

 protected clone()Ltest/Test; throws java/lang/CloneNotSupportedException protected volatile bridge clone()Ljava/lang/Object; throws java/lang/CloneNotSupportedException 

javac添加了“bridge”方法,因为Test.clone()返回Test,这意味着它不会覆盖返回Object的Object.clone(),这是因为JVM认为这两种方法不同

 1 clone()Ltest/Test; 2 clone()Ljava/lang/Object;