当前位置: 首页 > 面试题库 >

为什么可以覆盖Java中的最终常量?

丁嘉庆
2023-03-14
问题内容

考虑一下Java中的以下接口

public interface I {
    public final String KEY = "a";
}

和以下类:

public class A implements I {
    public String KEY = "b";

    public String getKey() {
        return KEY;
    }
}

为什么类A可以出现并覆盖接口I的最终常量?

自己尝试:

A a = new A();
String s = a.getKey(); // returns "b"!!!

问题答案:

尽管事实上您正在隐藏变量,但是知道可以在java中更改final字段非常有趣,因为您可以在此处阅读:

Java 5-“最终”不再是最终的

挪威Machina Networks的Narve
Saetre昨天给我发了一封便条,其中提到我们可以将句柄更改为最终数组,这是很遗憾的。我误解了他,并开始耐心地解释说我们无法使数组常量,也无法保护数组的内容。他说:“不,我们可以使用反射来改变最终的手柄。”

我尝试了Narve的示例代码,令人难以置信的是,Java
5允许我修改最终句柄,甚至是原始字段的句柄!我知道它曾经在某个时候被允许,但是后来被禁止了,所以我对旧版本的Java进行了一些测试。首先,我们需要一个带有final字段的类:

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;
  }
}

JDK 1.1.x

在JDK
1.1.x中,我们无法使用反射来访问私有字段。但是,我们可以创建另一个具有公共字段的Person,然后根据该字段编译我们的类,并交换Person类。如果我们针对的是与编译所针对的类不同的类,则在运行时不会进行访问检查。但是,我们无法在运行时使用类交换或反射来重新绑定最终字段。

用于java.lang.reflect.Field的JDK 1.1.8 JavaDocs表示如下:

  • 如果此Field对象强制实施Java语言访问控制,并且基础字段不可访问,则该方法将引发IllegalAccessException。
  • 如果基础字段为final,则该方法将引发IllegalAccessException。

JDK 1.2.x

在JDK
1.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);
  }
}

当我在JDK 1.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

声明时为基本类型的final字段,如果类型为基本类型或字符串,则该值将内联。

JDK 1.3.x和1.4.x

在JDK 1.3.x中,Sun稍微加强了访问权限,并阻止我们修改带有反射的最终字段。JDK
1.4.x也是如此。如果我们尝试运行FinalFieldChange类以在运行时使用反射重新绑定最终字段,则会得到:

Java版本“ 1.3.1_12”:异常线程“ main”
IllegalAccessException:该字段在FinalFieldChange.main(FinalFieldChange.FinalFieldChange.change(FinalFieldChange.java:8)的java.lang.reflect.Field.set(本机方法)处是最终的。
java:12)

Java版本“ 1.4.2_05”异常线程“ main”
IllegalAccessException:字段在FinalFieldChange.main(在FinalFieldChange.change(FinalFieldChange.java:8)在java.lang.reflect.Field.set(Field.java:519)是最终的(
FinalFieldChange.java:12)

JDK 5.x

现在我们进入JDK5.x。FinalFieldChange类的输出与JDK 1.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

反思,我希望有一个漏洞爬到JDK中。但是,我们俩都认为这不太可能,尤其是这样的基本错误。经过一番搜索,我发现了《
JSR-133:Java内存模型和线程规范》。大部分规范很难读,并且使我想起了我的大学时代(我以前常常写过这样的书;-)。但是,JSR-133非常重要,因此所有Java程序员都必须阅读它。(祝好运)

从第25页的第9章“最终字段语义”开始。具体来说,请阅读第9.1.1节“最终字段的构造后修改”。允许更新最终字段很有意义。例如,我们可以放宽对JDO中非最终字段的要求。

如果我们仔细阅读第9.1.1节,我们会看到我们仅应在构建过程中修改最终字段。用例是对一个对象进行反序列化,然后在构造该对象后,在传递它之前初始化最终字段。一旦使对象可用于另一个线程,就不应使用反射更改最终字段。结果将不可预测。

它甚至说:如果在字段声明中将final字段初始化为编译时常量,则可能不会观察到对final字段的更改,因为在编译时将使用final字段替换为compile-
time常量。这解释了为什么我们的iq字段保持不变,但国家/地区有所变化。

奇怪的是,JDK 5与JDK 1.2.x略有不同,因为您不能修改静态的final字段。

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();
  }
}

当我们使用JDK 1.2.x和JDK 5.x运行它时,我们得到以下输出:

Java版本“ 1.2.2_014”:stringValue =原始值objValue =新值

java版本“ 1.5.0”异常线程“ main”
IllegalAccessException:字段在FinalStaticFieldChange.changeStaticField(12)的FinalStaticFieldChange.main(16)处的java.lang.reflect.Field.set(Field.java:656)是最终的

因此,JDK 5就像JDK 1.2.x,只是有所不同?

结论

您知道JDK 1.3.0何时发布吗?我很难找到答案,所以我下载并安装了它。readme.txt文件的日期为2000/06/02
13:10。因此,它已经有4年多了(天哪,就像昨天一样)。在我开始编写Java(tm)专家通讯之前的几个月,就发布了JDK
1.3.0!我认为可以肯定地说,很少有Java开发人员可以记住JDK1.3.0之前的细节。啊,怀旧不再是过去了!您还记得第一次运行Java并收到以下错误:“无法初始化线程:找不到类java
/ lang / Thread”吗?



 类似资料:
  • 问题内容: 对于在要克隆对象的类中重写克隆方法,我感到困惑。 对象类具有 受保护的对象 方法,并且按照受保护的行为,即 当方法受到保护时,只能由类本身,该类的子类或与该类位于同一包中的类访问 。 由于Java中的每个类都从Object扩展而来,因此它应该具有clone方法,但仍然被迫重写clone。为什么需要它? 另外,我在某些地方已经阅读了有关覆盖克隆对象并将其公开的信息。我不知道为什么会这样吗

  • 我试图理解为什么我们不能覆盖静态和最终方法。我不明白背后的目的。

  • 问题内容: 我正在浏览Kathe sierra编写的SCJP 6,并遇到了有关以重写方法引发异常的解释。我真的不明白。有人可以向我解释吗? 覆盖方法不得抛出比被覆盖方法声明的异常新的或更广泛的检查异常。例如,声明FileNotFoundException的方法不能被声明SQLException,Exception或任何其他非运行时异常的方法覆盖,除非它是FileNotFoundException的

  • 问题内容: 我试图重写,但这是&操作,不 和 -这是我想要的。我可以覆盖 和 吗? 问题答案: 你不能覆盖的,和布尔运算符。

  • 本文向大家介绍当我们尝试覆盖Java中超类的最终方法时,会发生什么?,包括了当我们尝试覆盖Java中超类的最终方法时,会发生什么?的使用技巧和注意事项,需要的朋友参考一下 子类不能重写 在超类中声明为final的 任何方法 。如果我们尝试覆盖超类的最终方法,则会在Java中收到错误消息。 实现方法覆盖的规则 方法声明应与要重写的方法相同。 在尝试覆盖之前,该类(子类)应扩展另一个类(超类)。 子类

  • 所以我尝试从Windows迁移到Linux。今天我尝试用Emacs而不是Windows IDE编写和编译东西。这是我的示例代码: 这是我使用esc-x从emacs编译的- 在终端上,我使用“hello”执行hello/你好。然后我把代码改成了“cout” 它为什么这么做?