为什么最后的常量在Java被复盖?
-
03-07-2019 - |
题
考虑下面的接口在Java:
public interface I {
public final String KEY = "a";
}
和以下类别:
public class A implements I {
public String KEY = "b";
public String getKey() {
return KEY;
}
}
为什么是它可能对A类来复盖界面我的最后定?
试试自己:
A a = new A();
String s = a.getKey(); // returns "b"!!!
解决方案
尽管事实上,你是阴影的变量是相当有兴趣知道,你可以改变的最后领域中java为你可以读 在这里,:
Java5-"最后的"不是最后的了
Narve Saetre从马基纳网络在挪威的给我注意昨天, 一提的是,令人遗憾的是,我们可以改变处理一个 最后的阵列。我误解了他,并开始耐心地解释 我们不能让一系列恒定的,而这是没有办法的 保护内容的一个阵列。"不",他说,"我们可以改变一个 最后处理使用的反思"。
我试图Narve的代码样本,并且令人难以置信的是,Java5允许我 修改的最后处理,甚至一个处理一个原始现场!我知道, 它使用的是允许在某一点,但是,这是后来不允许, 所以我跑一些测试与旧版本。第一,我们需要一个 类的最后领域:
public class Person { private final String name; private final int age; private final int iq = 110; private final Object country = "South Africa"; public Person(String name, int age) { this.name = name; this.age = age; } public String toString() { return name + ", " + age + " of IQ=" + iq + " from " + country; } }
JAVA1.1.x
在JAVA1.1.x,我们不能够接入的私人领域使用 反射。我们可以,但是,创造另一个人与公众 领域,然后汇编我们的类害,并交换人 课程。有没有检查访问在运行时,如果我们运行 对不同类的一个,我们编制的反对。然而,我们不能重新绑定最后的领域,在运行时使用 类交换或者反映。
The JAVA1.1.8JavaDocs为java。郎。反映。领域有以下 说:
- 如果这个领域对象强制执行Java语言的访问控制和潜在领域是无法访问的方法,该方法将引发 IllegalAccessException.
- 如果潜在领域是最终方法,该方法引发的一个IllegalAccessException.
JAVA1.2.x
在JAVA1.2.x,这改变了一下。我们现在可以使私人领域 访问与setAccessible(true)的方法。访问的领域 现在检查在运行时,所以我们不能使用类交换的伎俩 访问私人领域。然而,我们现在可以突然重新绑定最终 领域!看看这个代码:
import java.lang.reflect.Field; public class FinalFieldChange { private static void change(Person p, String name, Object value) throws NoSuchFieldException, IllegalAccessException { Field firstNameField = Person.class.getDeclaredField(name); firstNameField.setAccessible(true); firstNameField.set(p, value); } public static void main(String[] args) throws Exception { Person heinz = new Person("Heinz Kabutz", 32); change(heinz, "name", "Ng Keng Yap"); change(heinz, "age", new Integer(27)); change(heinz, "iq", new Integer(150)); change(heinz, "country", "Malaysia"); System.out.println(heinz); } }
当我跑这JAVA1.2.2_014,我得到了以下结果:
Ng Keng Yap, 27 of IQ=110 from Malaysia Note, no exceptions, no complaints, and an incorrect IQ result. It seems that if we set a
最终场的一个原始在宣言》时,值是内联, 如果该类是原始的或一串。
JAVA1.3.x和1.4.x
在JAVA1.3.x、太阳收紧入一点,并且阻止我们 修改最终场的反映。这也是情况 JAVA1.4.x.如果我们试图运行FinalFieldChange类重新绑定 最后的领域,在运行时使用的反思,我们会得到:
java version"1.3.1_12":例外线"主要" IllegalAccessException:场是最终的 在java。郎。反映。领域。设置(地方法) 在FinalFieldChange.改变(FinalFieldChange.java:8) 在FinalFieldChange.主(FinalFieldChange.java:12)
java version"1.4.2_05"例外线"主要" IllegalAccessException:场是最终的 在java。郎。反映。领域。设置(现场。java:519) 在FinalFieldChange.改变(FinalFieldChange.java:8) 在FinalFieldChange.主(FinalFieldChange.java:12)
JAVA5.x
现在我们得到JAVA5.x.该FinalFieldChange类具有相同的输出 作为在JAVA1.2.x:
Ng Keng Yap, 27 of IQ=110 from Malaysia When Narve Saetre mailed me that he managed to change a final field in JDK 5 using
反,我希望这一错误蹑手蹑脚入JAVA.但是, 我们都觉得那是不可能的,尤其是这样一个根本的错误。之后,一些搜索时,我发现JSR-133:Java存储器模型和 线规范。大部分的说明书是很难阅读, 让我想起我的大学天(I用来写喜欢这;-) 然而,JSR-133是如此重要的是,它应该读一读 所有Java程序员。(好运气)
开始第9章最后一段的语义上,第25页。具体地说, 阅读第9.1.1后建筑的修改的最后一个领域。它的 有意义的允许更新最后的领域。例如,我们可以 放松的要求有领域的非最终在被创建.
如果我们阅读第9.1.1仔细,我们看到,我们应该只修改 最后领域的一部分,我们的建设进程。利用情况 我们deserialize的一个目的,那么一旦我们建造的 目的,我们初始化最后的领域之前,通过它。一旦我们 已对象可用来另外一个线程,我们不应该改变 最后领域使用的反思。该结果将不会是可以预测的。
它甚至可以这样说:如果最终场初始化为一个编译时间 恒定在该领域的宣言,变化最后的领域不可能 可以观察到,由于采用这一最后的领域是取代在编译 时间与编译的时间不变。这就解释了为什么我们的智商场 保持相同,但国家的变化。
奇怪的是,JAVA5略有不同JAVA1.2.x,你不能 修改一个静态的最终领域。
import java.lang.reflect.Field; public class FinalStaticFieldChange { /** Static fields of type String or primitive would get inlined */ private static final String stringValue = "original value"; private static final Object objValue = stringValue; private static void changeStaticField(String name) throws NoSuchFieldException, IllegalAccessException { Field statFinField = FinalStaticFieldChange.class.getDeclaredField(name); statFinField.setAccessible(true); statFinField.set(null, "new Value"); } public static void main(String[] args) throws Exception { changeStaticField("stringValue"); changeStaticField("objValue"); System.out.println("stringValue = " + stringValue); System.out.println("objValue = " + objValue); System.out.println(); } }
当我们执行这与JAVA1.2.x和JAVA5.x,我们得到如下的 输出:
java version"1.2.2_014":stringValue=原始价值objValue=new 值
java version"升级到1.5.0"例外线"主要"IllegalAccessException:场是最终在java。郎。反映。领域。设置(现场。java:656)在 FinalStaticFieldChange.changeStaticField(12) FinalStaticFieldChange.主(16)
因此,JAVA5像JAVA1.2.x,只是不同的?
结论
你知道当JAVA1.3.0被释放?我在努力找出来,所以我 下载并安装它。的readme.txt 文件的日期 2000/06/02 13:10.因此,它是超过4岁(我的天啊, 感觉像是昨天).JAVA1.3.0发布之前几个月我 开始写Java(tm)专家的通讯!我认为它会 可以安全地说,很少Java开发可以记住细节 预JDK1.3.0.啊,怀旧不是有什么用!做你 记得Java第一次获得这个错误:"无法初始化主题:找不到类java/lang/线"?
其他提示
您正在隐藏它,它是“范围”的一个特征。每当你在较小的范围内时,你可以重新定义你喜欢的所有变量,外部范围变量将是“阴影”
顺便说一句,如果您愿意,可以再次进行搜索:
public class A implements I {
public String KEY = "b";
public String getKey() {
String KEY = "c";
return KEY;
}
}
现在KEY将返回“c”;
编辑,因为原来在重新阅读时很糟糕。
看起来你的类只是隐藏变量,而不是覆盖变量:
public class A implements I {
public String KEY = "B";
public static void main(String args[])
{
A t = new A();
System.out.println(t.KEY);
System.out.println(((I) t).KEY);
}
}
如您所见,这将打印“B”和“A”。您甚至可以分配给它,因为A.KEY变量未定义为final。
A.KEY="C" <-- this compiles.
但是 -
public class C implements I{
public static void main (String args[])
{
C t = new C();
c.KEY="V"; <--- compiler error ! can't assign to final
}
}
你不应该问你的恒定的以这种方式,使用静态的参考,而不是:
I.KEY //returns "a"
B.KEY //returns "b"
作为设计考虑因素,
public interface I {
public final String KEY = "a";
}
静态方法总是返回父键。
public class A implements I {
public String KEY = "b";
public String getKey() {
return KEY; // returns "b"
}
public static String getParentKey(){
return KEY; // returns "a"
}
}
就像Jom注意到的那样。使用重新定义的接口成员的静态方法的设计可能是一个严重的问题。通常,尽量避免对常量使用相同的名称。
静态字段和方法附加到声明它们的类/接口(尽管接口不能声明静态方法,因为它们是完全需要实现的抽象类)。
所以,如果你有一个带有public static(vartype)(varname)的接口, 该字段附加到该接口。
如果你有一个类实现该接口,编译器技巧会将(this。)varname转换为InterfaceName.varname。但是,如果您的类重新定义了varname,则会将一个名为varname的新常量附加到您的类,编译器现在知道将(。。)varname转换为NewClass.varname。这同样适用于方法:如果新类没有重新定义方法,则(this。)methodName被翻译成SuperClass.methodName,否则,(this。)methodName被翻译成CurrentClass.methodName。
这就是为什么你会遇到警告“应该以静态方式访问x字段/方法”的原因。编译器告诉你,虽然它可能会使用这个技巧,但它更喜欢你使用ClassName.method / fieldName,因为它更易于显示。