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

Многие объяснения уже присутствуют, чтобы объяснить, как это происходит и как это исправить, но вы также должны следовать рекомендациям, чтобы избежать NullPointerException вообще.

См. также: A хороший список лучших практик

Я бы добавил, очень важно, хорошо использовать модификатор final. Использование "окончательной" модификатор, когда это применимо в Java

Сводка:

  1. Используйте модификатор final для обеспечения хорошей инициализации.
  2. Избегайте возврата null в методы, например, при возврате пустых коллекций.
  3. Использовать аннотации @NotNull и @Nullable
  4. Быстрое завершение работы и использование утверждений, чтобы избежать распространения нулевых объектов через все приложение, когда они не должен быть пустым.
  5. Сначала используйте значения с известным объектом: if("knownObject".equals(unknownObject)
  6. Предпочитают valueOf() поверх toString ().
  7. Используйте null safe StringUtils StringUtils.isEmpty(null).

31
задан Cœur 16 January 2017 в 16:00
поделиться

5 ответов

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

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

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

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

JDK 1.1.8 JavaDocs для java.lang.reflect. Поле имело следующее для высказывания:

  • , Если этот Объект поля осуществляет управление доступом языка Java, и базовое поле недоступно, метод бросает IllegalAccessException.
  • , Если базовое поле является окончательным, метод бросает IllegalAccessException.

JDK 1.2.x

В JDK 1.2.x, это изменилось немного. Мы могли теперь сделать частные поля доступными с setAccessible (истинный) метод. Доступ полей был теперь проверен во времени выполнения, таким образом, мы не могли использовать прием свопинга класса для доступа к частным полям. Однако мы могли теперь внезапно снова переплести заключительные поля! Посмотрите на этот код:

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

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

JDK 1.3.x и 1.4.x

В JDK 1.3.x, Sun ограничил доступ немного и препятствовал тому, чтобы мы изменили заключительное поле с отражением. Это также имело место с JDK 1.4.x. Если бы мы пытались выполнить класс FinalFieldChange для повторного переплетения заключительных полей во времени выполнения с помощью отражения, то мы добрались бы:

версия "1.3.1_12" Java: поток Исключения "основной" IllegalAccessException: поле является окончательным в java.lang.reflect. Field.set (Собственный метод) в FinalFieldChange.change(FinalFieldChange.java:8) в FinalFieldChange.main(FinalFieldChange.java:12)

поток Исключения версии "1.4.2_05" Java "основной" IllegalAccessException: Поле является окончательным в java.lang.reflect. Field.set (Поле 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, мы получаем следующий вывод:

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

поток Исключения версии "1.5.0" Java "основной" IllegalAccessException: Поле является окончательным в java.lang.reflect. Field.set (Поле java:656) в FinalStaticFieldChange.changeStaticField (12) в FinalStaticFieldChange.main (16)

Так, JDK 5 похож на JDK 1.2.x, просто отличающийся?

Заключение

Вы знаете, когда JDK 1.3.0 был выпущен? Я изо всех сил пытался узнать, таким образом, я загрузил и установил его. Файл readme.txt имеет дату 02.06.2000 13:10. Так, этому больше чем 4 года (совершенство меня, вчера похоже). JDK 1.3.0 был выпущен за несколько месяцев до того, как я начал писать Java (TM) Новостная рассылка специалистов! Я думаю, что было бы безопасно сказать, что очень немного Java-разработчиков могут помнить детали pre-JDK1.3.0. Ahh, ностальгия не то, чем это раньше было! Сделайте Вы помните рабочий Java впервые и получение этой ошибки: "Не мог инициализировать потоки: не может найти класс Java/Ленгом/потоком"?

18
ответ дан 27 November 2019 в 22:06
поделиться

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

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
ответ дан 27 November 2019 в 22:06
поделиться

Вы скрываете его, это - функция "Scope". Любое время Вы находитесь в меньшем объеме, можно переопределить все переменные, которые Вы любите, и внешние переменные объема будут "Затенены"

Между прочим, можно определить объем его снова, если Вам нравится:

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

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

Теперь KEY возвратит "c";

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

36
ответ дан 27 November 2019 в 22:06
поделиться

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

I.KEY //returns "a"
B.KEY //returns "b"
2
ответ дан 27 November 2019 в 22:06
поделиться

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

Так, если у Вас есть интерфейс с общедоступными помехами (vartype) (varname), то поле присоединено к тому интерфейсу.

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

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

1
ответ дан 27 November 2019 в 22:06
поделиться
Другие вопросы по тегам:

Похожие вопросы: