Why can final constants in Java be overridden?

2019-02-02 07:21发布

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

6条回答
劳资没心,怎么记你
2楼-- · 2019-02-02 07:57

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

I.KEY //returns "a"
B.KEY //returns "b"
查看更多
smile是对你的礼貌
3楼-- · 2019-02-02 07:59

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

    }
}
查看更多
看我几分像从前
4楼-- · 2019-02-02 08:07

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

查看更多
贪生不怕死
5楼-- · 2019-02-02 08:12

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.

查看更多
聊天终结者
6楼-- · 2019-02-02 08:17

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.

查看更多
甜甜的少女心
7楼-- · 2019-02-02 08:17

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.

查看更多
登录 后发表回答