Многие объяснения уже присутствуют, чтобы объяснить, как это происходит и как это исправить, но вы также должны следовать рекомендациям, чтобы избежать NullPointerException
вообще.
См. также: A хороший список лучших практик
Я бы добавил, очень важно, хорошо использовать модификатор final
. Использование "окончательной" модификатор, когда это применимо в Java
Сводка:
final
для обеспечения хорошей инициализации. @NotNull
и @Nullable
if("knownObject".equals(unknownObject)
valueOf()
поверх toString (). StringUtils
StringUtils.isEmpty(null)
. Несмотря на то, что Вы - затенение переменная, довольно интересно знать, что можно изменить заключительные поля в 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/Ленгом/потоком"?
Похоже, что Ваш класс просто скрывает переменную, не перезаписывая его:
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
}
}
Вы скрываете его, это - функция "Scope". Любое время Вы находитесь в меньшем объеме, можно переопределить все переменные, которые Вы любите, и внешние переменные объема будут "Затенены"
Между прочим, можно определить объем его снова, если Вам нравится:
public class A implements I {
public String KEY = "b";
public String getKey() {
String KEY = "c";
return KEY;
}
}
Теперь KEY возвратит "c";
Отредактированный, потому что оригинал высосал после перечитывания.
Вы не должны получать доступ к своей константе таким образом, использовать статическую ссылку вместо этого:
I.KEY //returns "a"
B.KEY //returns "b"
Статические поля и методы присоединены к классу/интерфейсу, объявляя их (хотя интерфейсы не могут объявить статические методы, поскольку они - совершенно абстрактные классы, которые должны быть реализованы).
Так, если у Вас есть интерфейс с общедоступными помехами (vartype) (varname), то поле присоединено к тому интерфейсу.
, Если у Вас есть реализация класса, которые взаимодействуют через интерфейс, прием компилятора преобразовывает (это). varname в InterfaceName.varname. Но, если Ваш класс переопределяет varname, новый постоянный названный varname присоединен к Вашему классу, и компилятор знает, чтобы теперь перевести (это). varname в NewClass.varname. То же касается методов: если новый класс не переопределяет метод, (это). methodName переводится в SuperClass.methodName, иначе, (это). methodName переводится в CurrentClass.methodName.
Поэтому Вы встретитесь с предупреждением "x, к полю/методу нужно получить доступ статическим способом". Компилятор говорит Вам, что, хотя он может использовать прием, он предпочел бы, чтобы Вы использовали ClassName.method/fieldName, потому что это более явно в целях удобочитаемости.