Подтвердить что ты не робот

Почему окончательные константы в Java могут быть переопределены?

Рассмотрим следующий интерфейс в 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"!!!
4b9b3361

Ответ 1

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

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

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

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

JDK 1.1.8 JavaDocs для 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 version "1.3.1_12": Исходный поток "main"   IllegalAccessException: поле окончательное           на java.lang.reflect.Field.set(собственный метод)           в FinalFieldChange.change(FinalFieldChange.java:8)           в FinalFieldChange.main(FinalFieldChange.java:12)

     

java version "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 version "1.2.2_014": stringValue = исходное значение objValue = new   Значение

     

java version "1.5.0" Исходный поток "main" IllegalAccessException:   Поле является окончательным в файле java.lang.reflect.Field.set(Field.java:656) в   FinalStaticFieldChange.changeStaticField(12) at   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"?

Ответ 2

Вы скрываете это, это особенность "Сфера". Каждый раз, когда вы находитесь в меньшей области, вы можете переопределить все нужные вам переменные, а внешние переменные сферы будут "Затенены"

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

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

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

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

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

Ответ 3

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

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

    }
}

Ответ 4

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

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

Ответ 5

В качестве рассмотрения проекта

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

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

Ответ 6

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

Итак, если у вас есть интерфейс с открытым static (vartype) (varname), это поле прикреплено к этому интерфейсу.

Если у вас есть реализация этого интерфейса класса, трюк компилятора преобразует (this.) varname в InterfaceName.varname. Но если ваш класс переопределяет varname, к вашему классу присоединяется новая константа с именем varname, и компилятор знает, что теперь перевести (this.) Varname в NewClass.varname. То же самое относится к методам: если новый класс не переопределяет метод, (this.) MethodName переводится в SuperClass.methodName, в противном случае (this.) MethodName преобразуется в CurrentClass.methodName.

Вот почему вы столкнетесь с предупреждением: "x field/method должен быть доступен статическим образом". Компилятор сообщает вам, что, хотя он может использовать трюк, он предпочел бы, чтобы вы использовали ClassName.method/fieldName, потому что он более явный для удобства чтения.