Почему конечные константы в Java могут быть переопределены? - PullRequest
31 голосов
/ 15 октября 2008

Рассмотрим следующий интерфейс в Java:

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

И следующий класс:

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

    public String getKey() {
        return KEY;
    }
}

Почему класс А может прийти и переопределить последнюю константу интерфейса I?

Попробуйте сами:

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

Ответы [ 6 ]

36 голосов
/ 15 октября 2008

Вы это скрываете, это особенность "Scope". В любое время, когда вы находитесь в области видимости меньшего размера, вы можете переопределить все переменные, которые вам нравятся, и переменные внешней области видимости будут "Shadowed"

Кстати, вы можете добавить его снова, если хотите:

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

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

Теперь KEY вернет "c";

Отредактировано, потому что оригинал высосан при перечитывании.

18 голосов
/ 15 октября 2008

Несмотря на то, что вы скрываете переменную, довольно интересно знать, что вы можете изменить конечные поля в java, так как вы можете прочитать здесь :

Java 5 - "финал" больше не является окончательным

Narve Saetre из Machina Networks в Норвегии прислала мне записку вчера, упоминая, что было жаль, что мы могли изменить ручку на окончательный массив. Я не понял его и начал терпеливо объяснять что мы не можем сделать массив постоянным, и что не было никакого способа защита содержимого массива. «Нет», сказал он, «мы можем изменить конечная ручка с использованием отражения. "

Я попробовал пример кода Narve, и невероятно, Java 5 позволила мне измените окончательный дескриптор, даже дескриптор примитивного поля! я знал это Раньше это было разрешено в какой-то момент, но затем это было запрещено поэтому я провел несколько тестов со старыми версиями Java. Во-первых, нам нужен класс с окончательными полями:

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 мы не смогли получить доступ к закрытым полям, используя отражение. Мы могли бы, однако, создать другого человека с публичным поля, затем скомпилируйте наш класс против этого и поменяйте местами классы. Не было проверки доступа во время выполнения, если мы работали против другого класса к тому, против которого мы собрали. Однако мы не могли перепривязать окончательные поля во время выполнения, используя либо обмен или отражение класса.

JavaDocs JDK 1.1.8 для java.lang.reflect.Field имел следующее сказать:

  • Если этот объект Field обеспечивает контроль доступа к языку Java, а основное поле недоступно, метод выдает IllegalAccessException.
  • Если базовое поле является окончательным, метод генерирует исключение 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

конечное поле примитива во время объявления, значение встроено, если тип является примитивным или String.

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) at 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 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. (Удачи)

Начните с главы 9 Окончательная семантика поля, на странице 25. В частности, прочитайте раздел 9.1.1 Модификация конечных полей после постройки. Это имеет смысл разрешить обновления для окончательных полей. Например, мы могли бы ослабьте требование, чтобы в JDO были поля, не являющиеся окончательными.

Если мы внимательно прочитаем раздел 9.1.1, мы увидим, что мы должны только изменить последние поля как часть нашего процесса строительства. Вариант использования где мы десериализовать объект, а затем, как только мы построили объект, мы инициализируем последние поля, прежде чем передать его. Однажды мы сделали объект доступным для другого потока, мы не должны менять финальные поля с использованием отражения. Результат не будет предсказуемым.

Это даже говорит это: если последнее поле инициализируется во время компиляции константа в объявлении поля, изменения в конечном поле могут не соблюдать, поскольку использование этого последнего поля заменяется при компиляции время с постоянной времени компиляции. Это объясняет, почему наше поле iq остается прежним, но страна меняется.

Странно, JDK 5 немного отличается от JDK 1.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();
  }
}

Когда мы запускаем это с JDK 1.2.x и JDK 5.x, мы получаем следующее выход:

Java-версия "1.2.2_014": stringValue = исходное значение objValue = new Значение

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-разработчики могут помнить детали до JDK 1.3.3. Ааа, ностальгия уже не та, что раньше! Вы не забудьте запустить Java в первый раз и получить эту ошибку: "Невозможно инициализировать потоки: не удается найти класс java / lang / Thread"?

4 голосов
/ 15 октября 2008

Похоже, ваш класс просто скрывает переменную, а не перезаписывает ее:

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

    }
}
2 голосов
/ 04 февраля 2013

В качестве проектного решения,

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

Так же, как заметил Джом. Разработка статических методов с использованием переопределенных элементов интерфейса может быть серьезной проблемой. В общем, старайтесь не использовать одно и то же имя для константы.

2 голосов
/ 15 октября 2008

Вы не должны обращаться к своей константе таким образом, вместо этого используйте статическую ссылку:

I.KEY //returns "a"
B.KEY //returns "b"
1 голос
/ 15 октября 2008

Статические поля и методы присоединяются к классу / интерфейсу, объявляющему их (хотя интерфейсы не могут объявлять статические методы, поскольку они являются полностью абстрактными классами, которые необходимо реализовать).

Итак, если у вас есть интерфейс с общедоступным 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, потому что он более явный для удобства чтения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...