为什么Java中的final常量可以被重写?

发布于 2024-07-07 21:29:23 字数 395 浏览 6 评论 0原文

考虑 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"!!!

Consider the following interface in Java:

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

And the following class:

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

    public String getKey() {
        return KEY;
    }
}

Why is it possible for class A to come along and override interface I's final constant?

Try for yourself:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

遗弃M 2024-07-14 21:29:23

你把它隐藏起来了,这是“Scope”的一个功能。 任何时候你在一个较小的作用域中,你都可以重新定义你喜欢的所有变量,外部作用域变量将被“隐藏”

顺便说一句,如果你愿意,你可以再次作用域:

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

    public String getKey() {
        String KEY = "c";
        return KEY;
    }
}

现在KEY将返回“c”;

编辑是因为重读时原著很烂。

You are hiding it, it's a feature of "Scope". Any time you are in a smaller scope, you can redefine all the variables you like and the outer scope variables will be "Shadowed"

By the way, you can scope it again if you like:

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

    public String getKey() {
        String KEY = "c";
        return KEY;
    }
}

Now KEY will return "c";

Edited because the original sucked upon re-reading.

雪若未夕 2024-07-14 21:29:23

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

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

挪威 Machina Networks 的 Narve Saetre 昨天给我发了一条信息,
提到遗憾的是我们可以将手柄更改为
最终数组。 我误会了他,开始耐心解释
我们无法使数组常量,并且没有办法
保护数组的内容。 “不,”他说,“我们可以改变一个
使用反射的最终句柄。”

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

公共类人{ 
    私有最终字符串名称; 
    私有最终 int 年龄; 
    私有最终 int iq = 110; 
    private Final 对象国家=“南非”; 

    公共人(字符串名称,整数年龄){ 
      this.name = 名称; 
      this.age = 年龄; 
    } 

    公共字符串 toString() { 
      返回姓名 + ", " + 年龄 + " IQ=" + iq + " 来自 " + 国家/地区; 
    } 
  } 
  

JDK 1.1.x

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

java.lang.reflect.Field 的 JDK 1.1.8 JavaDocs 具有以下内容
说:

  • 如果此 Field 对象强制执行 Java 语言访问控制,并且底层字段不可访问,则该方法会抛出
    非法访问异常。
  • 如果基础字段是final,该方法会抛出IllegalAccessException。

JDK 1.2.x

在 JDK 1.2.x 中,这发生了一些变化。 我们现在可以创建私有字段
可使用 setAccessible(true) 方法访问。 字段的访问权限是
现在在运行时检查,因此我们无法使用类交换技巧
访问私有字段。 然而,我们现在可以突然重新绑定最终
田野! 看一下这段代码:

导入java.lang.reflect.Field; 

  公共类 FinalFieldChange { 
    private static void change(Person p, 字符串名称, 对象值) 
        抛出 NoSuchFieldException、IllegalAccessException { 
      字段firstNameField = Person.class.getDeclaredField(name); 
      firstNameField.setAccessible(true); 
      FirstNameField.set(p, 值); 
    } 
    公共静态无效主(字符串[] args)抛出异常{ 
      Person heinz = new Person("Heinz Kabutz", 32); 
      更改(heinz,“姓名”,“黄庆业”); 
      更改(海因茨,“年龄”,新整数(27)); 
      更改(海因茨,“智商”,新整数(150)); 
      变化(亨氏,“国家”,“马来西亚”); 
      System.out.println(heinz); 
    } 
  } 
  

当我在 JDK 1.2.2_014 中运行此代码时,得到以下结果:

Ng Keng Yap,27 智商=110,来自马来西亚 注意,没有例外,没有投诉,智商结果不正确。   看来如果我们设置一个 
  

声明时原语的最终字段,该值是内联的,
如果类型是原始类型或字符串。

JDK 1.3.x 和 1.4.x

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

<块引用>

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

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

JDK 5.x

现在我们进入 JDK 5.x。 FinalFieldChange 类具有相同的输出
如 JDK 1.2.x 中所示:

Ng Keng Yap,27 岁,智商=110,来自马来西亚 当 Narve Saetre 发邮件给我时,他设法使用以下命令更改了 JDK 5 中的最后一个字段 
  

反思一下,我希望 JDK 中已经出现了一个错误。 然而,
我们都觉得这不太可能,尤其是这样一个根本性的错误。
经过一番搜索,我找到了 JSR-133:Java Memory Model 和
螺纹规格。 大部分规范都很难阅读,并且
让我想起了我的大学时光(我以前就是这样写的;-)
然而,JSR-133 非常重要,因此应该阅读
对于所有 Java 程序员。 (祝你好运)

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

如果我们仔细阅读第 9.1.1 节,我们会发现我们应该只修改
最终字段是我们构建过程的一部分。 用例是
我们反序列化一个对象,然后一旦我们构造了
对象,我们在传递它之前初始化最终字段。 一旦我们
已使该对象可供另一个线程使用,我们不应该更改
使用反射的最终字段。 结果是不可预测的。

它甚至这样说:如果最终字段被初始化为编译时
字段声明中的常量,对最终字段的更改可能不会
可以观察到,因为该最终字段的使用在编译时被替换
时间与编译时常数。 这解释了为什么我们的智商字段
保持不变,但国家/地区发生变化。

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

导入java.lang.reflect.Field; 

  公共类 FinalStaticFieldChange { 
    /** String 或primitive 类型的静态字段将被内联 */ 
    private static Final String stringValue = "原始值"; 
    私有静态最终对象 objValue = stringValue; 

    私有静态无效changeStaticField(字符串名称) 
        抛出 NoSuchFieldException、IllegalAccessException { 
      字段 statFinField = FinalStaticFieldChange.class.getDeclaredField(name); 
      statFinField.setAccessible(true); 
      statFinField.set(null, "新值"); 
    } 

    公共静态无效主(字符串[] args)抛出异常{ 
      更改静态字段(“字符串值”); 
      更改静态字段(“objValue”); 
      System.out.println("字符串值 = " + 字符串值); 
      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:
字段最终位于 java.lang.reflect.Field.set(Field.java:656) 处
FinalStaticFieldChange.changeStaticField(12) 位于
FinalStaticFieldChange.main(16)

那么,JDK 5 就像 JDK 1.2.x,只是不同?

结论

你知道JDK 1.3.0什么时候发布的吗? 我很难找到答案,所以我
下载并安装它。 readme.txt 文件包含日期
2000/06/02 13:10。 所以,它已经 4 岁多了(天哪,它
感觉就像昨天一样)。 JDK 1.3.0 比我早几个月发布
开始撰写 Java(tm) 专家时事通讯! 我认为会
可以肯定地说,很少有 Java 开发人员能够记住这些细节
JDK1.3.0 之前的版本。 啊,怀旧不是以前的样子了! 你
记得第一次运行 Java 时出现以下错误:
“无法初始化线程:找不到类 java/lang/Thread”?

Despite the fact that you are shadowing the variable it's quite interesting to know that you can change final fields in java as you can read here:

Java 5 - "final" is not final anymore

Narve Saetre from Machina Networks in Norway sent me a note yesterday,
mentioning that it was a pity that we could change the handle to a
final array. I misunderstood him, and started patiently explaining
that we could not make an array constant, and that there was no way of
protecting the contents of an array. "No", said he, "we can change a
final handle using reflection."

I tried Narve's sample code, and unbelievably, Java 5 allowed me to
modify a final handle, even a handle to a primitive field! I knew that
it used to be allowed at some point, but that it was then disallowed,
so I ran some tests with older versions of Java. First, we need a
class with final fields:

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

In JDK 1.1.x, we were not able to access private fields using
reflection. We could, however, create another Person with public
fields, then compile our class against that, and swap the Person
classes. There was no access checking at runtime if we were running
against a different class to the one that we compiled against.
However, we could not rebind final fields at runtime using either
class swapping or reflection.

The JDK 1.1.8 JavaDocs for java.lang.reflect.Field had the following
to say:

  • If this Field object enforces Java language access control, and the underlying field is inaccessible, the method throws an
    IllegalAccessException.
  • If the underlying field is final, the method throws an IllegalAccessException.

JDK 1.2.x

In JDK 1.2.x, this changed a bit. We could now make private fields
accessible with the setAccessible(true) method. Access of fields was
now checked at runtime, so we could not use the class swapping trick
to access private fields. However, we could now suddenly rebind final
fields! Look at this code:

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

When I ran this in JDK 1.2.2_014, I got the following result:

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 field of a primitive at declaration time, the value is inlined,
if the type is primitive or a String.

JDK 1.3.x and 1.4.x

In JDK 1.3.x, Sun tightened up the access a bit, and prevented us from
modifying a final field with reflection. This was also the case with
JDK 1.4.x. If we tried running the FinalFieldChange class to rebind
the final fields at runtime using reflection, we would get:

java version "1.3.1_12": Exception thread "main"
IllegalAccessException: field is final
at java.lang.reflect.Field.set(Native Method)
at FinalFieldChange.change(FinalFieldChange.java:8)
at FinalFieldChange.main(FinalFieldChange.java:12)

java version "1.4.2_05" Exception thread "main"
IllegalAccessException: Field is final
at java.lang.reflect.Field.set(Field.java:519)
at FinalFieldChange.change(FinalFieldChange.java:8)
at FinalFieldChange.main(FinalFieldChange.java:12)

JDK 5.x

Now we get to JDK 5.x. The FinalFieldChange class has the same output
as in 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

reflection, I was hoping that a bug had crept into the JDK. However,
we both felt that to be unlikely, especially such a fundamental bug.
After some searching, I found the JSR-133: Java Memory Model and
Thread Specification. Most of the specification is hard reading, and
reminds me of my university days (I used to write like that ;-)
However, JSR-133 is so important that it should be required reading
for all Java programmers. (Good luck)

Start with chapter 9 Final Field Semantics, on page 25. Specifically,
read section 9.1.1 Post-Construction Modification of Final Fields. It
makes sense to allow updates to final fields. For example, we could
relax the requirement to have fields non-final in JDO.

If we read section 9.1.1 carefully, we see that we should only modify
final fields as part of our construction process. The use case is
where we deserialize an object, and then once we have constructed the
object, we initialise the final fields, before passing it on. Once we
have made the object available to another thread, we should not change
final fields using reflection. The result would not be predictable.

It even says this: If a final field is initialized to a compile-time
constant in the field declaration, changes to the final field may not
be observed, since uses of that final field are replaced at compile
time with the compile-time constant. This explains why our iq field
stays the same, but country changes.

Strangely, JDK 5 differs slightly from JDK 1.2.x, in that you cannot
modify a static final field.

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

When we run this with JDK 1.2.x and JDK 5.x, we get the following
output:

java version "1.2.2_014": stringValue = original value objValue = new
Value

java version "1.5.0" Exception thread "main" IllegalAccessException:
Field is final at java.lang.reflect.Field.set(Field.java:656) at
FinalStaticFieldChange.changeStaticField(12) at
FinalStaticFieldChange.main(16)

So, JDK 5 is like JDK 1.2.x, just different?

Conclusion

Do you know when JDK 1.3.0 was released? I struggled to find out, so I
downloaded and installed it. The readme.txt file has the date
2000/06/02 13:10. So, it is more than 4 years old (goodness me, it
feels like yesterday). JDK 1.3.0 was released several months before I
started writing The Java(tm) Specialists' Newsletter! I think it would
be safe to say that very few Java developers can remember the details
of pre-JDK1.3.0. Ahh, nostalgia isn't what it used to be! Do you
remember running Java for the first time and getting this error:
"Unable to initialize threads: cannot find class java/lang/Thread"?

失眠症患者 2024-07-14 21:29:23

看起来您的类只是隐藏变量,而不是覆盖它:

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 变量未定义为最终变量。

 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

    }
}

It looks like your class is simply hiding the variable, not overwriting it:

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

This will print "B", and "A", as you found. You can even assign to it, as the A.KEY variable is not defined as final.

 A.KEY="C" <-- this compiles.

But -

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

    }
}
靖瑶 2024-07-14 21:29:23

作为设计考虑,

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

正如乔姆所注意到的那样。 使用重新定义的接口成员的静态方法的设计可能是一个严重的问题。 一般来说,尽量避免对常量使用相同的名称。

As a design consideration,

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

The static methods always returns the parent key.

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

    public String getKey() {
        return KEY; // returns "b"
    }

    public static String getParentKey(){
        return KEY; // returns "a"
    }
}

Just like Jom has noticed. The design of static methods using re-defined interface members could be a heavy problem. In general, try to avoid use the same name for the constant.

梦初启 2024-07-14 21:29:23

您不应该以这种方式访问​​常量,而应使用静态引用:

I.KEY //returns "a"
B.KEY //returns "b"

You should not access your constant in this way, use the static reference instead:

I.KEY //returns "a"
B.KEY //returns "b"
旧时光的容颜 2024-07-14 21:29:23

静态字段和方法附加到声明它们的类/接口(尽管接口不能声明静态方法,因为它们是需要实现的完全抽象类)。

因此,如果您有一个带有 public static (vartype) (varname) 的接口,
该字段附加到该接口。

如果您有一个类实现该接口,则编译器技巧会将 (this.)varname 转换为 InterfaceName.varname。 但是,如果您的类重新定义了 varname,则会将一个名为 varname 的新常量附加到您的类,并且编译器现在知道将 (this.)varname 翻译为 NewClass.varname。 这同样适用于方法:如果新类没有重新定义方法,则 (this.)methodName 被翻译为 SuperClass.methodName,否则,(this.)methodName 被翻译为 CurrentClass.methodName。

这就是为什么您会遇到警告“x 字段/方法应该以静态方式访问”。 编译器告诉您,尽管它可能会使用该技巧,但它更愿意您使用 ClassName.method/fieldName,因为出于可读性目的,它更加明确。

Static fields and methods are attached to the class/interface declaring them (though interfaces cannot declare static methods as they are wholly abstract classes which need to be implemented).

So, if you have an interface with a public static (vartype) (varname),
that field is attached to that interface.

If you have a class implement that interface, the compiler trick transforms (this.)varname into InterfaceName.varname. But, if your class redefines varname, a new constant named varname is attached to your class, and the compiler knows to now translate (this.)varname into NewClass.varname. The same applies for methods: if the new class does not re-define the method, (this.)methodName is translated into SuperClass.methodName, otherwise, (this.)methodName is translated into CurrentClass.methodName.

This is why you will encounter the warning "x field/method should be accessed in a static way". The compiler is telling you that, although it may use the trick, it would prefer that you used ClassName.method/fieldName, because it is more explicit for readability purposes.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文