Исключение нулевого указателя - это индикатор того, что вы используете объект, не инициализируя его.
Например, ниже - класс ученика, который будет использовать его в нашем коде.
public class Student {
private int id;
public int getId() {
return this.id;
}
public setId(int newId) {
this.id = newId;
}
}
Приведенный ниже код дает вам исключение с нулевым указателем.
public class School {
Student obj_Student;
public School() {
try {
obj_Student.getId();
}
catch(Exception e) {
System.out.println("Null Pointer ");
}
}
}
Поскольку вы используете Obj_Student
, но вы забыли инициализировать его, как в правильном коде, показанном ниже:
public class School {
Student obj_Student;
public School() {
try {
obj_Student = new Student();
obj_Student.setId(12);
obj_Student.getId();
}
catch(Exception e) {
System.out.println("Null Pointer ");
}
}
}
{
a = new A();
}
Проверить Объяснение и советы Sun
Из этого учебника :
Полевые декларации, однако, являются не являются частью какого-либо метода, поэтому они не могут быть выполнены в качестве операторов. Вместо этого компилятор Java автоматически генерирует код инициализации поля экземпляра и помещает его в конструктор или конструкторы для класса. Код инициализации вставляется в конструктор в порядке, указанном в исходном коде, что означает, что инициализатор поля может использовать начальные значения объявленных перед ним полей.
blockquote>Кроме того, вы можете хотите лениво инициализировать ваше поле. В случаях, когда инициализация поля является дорогостоящей операцией, вы можете инициализировать ее, как только это будет необходимо:
ExpensiveObject o; public ExpensiveObject getExpensiveObject() { if (o == null) { o = new ExpensiveObject(); } return o; }
И в конечном счете (как указано Биллом), ради управления зависимостью, лучше избегать с помощью оператора
new
в любом месте вашего класса. Вместо этого использование Injection Dependency предпочтительнее, то есть позволить кому-то другому (другому классу / фреймворку) создавать экземпляры и вводить зависимости в вашем классе.
Второй вариант предпочтительнее, так как позволяет использовать различную логику в ctors для создания экземпляра класса и использовать цепочку ctors. Например,
class A {
int b;
// secondary ctor
A(String b) {
this(Integer.valueOf(b));
}
// primary ctor
A(int b) {
this.b = b;
}
}
Таким образом, второй вариант более гибкий.
Пример 2 менее гибок. Если вы добавите другой конструктор, вам также нужно запомнить экземпляр поля в этом конструкторе. Просто создайте экземпляр поля непосредственно или введите ленивую загрузку где-нибудь в геттере.
Если для создания экземпляра требуется больше, чем просто new
, используйте блок инициализатора. Это будет выполняться независимо от используемого конструктора. Например,
public class A {
private Properties properties;
{
try {
properties = new Properties();
properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("file.properties"));
} catch (IOException e) {
throw new ConfigurationException("Failed to load properties file.", e); // It's a subclass of RuntimeException.
}
}
// ...
}
Другим вариантом будет использование Injection Dependency .
class A{
B b;
A(B b) {
this.b = b;
}
}
Это снимает ответственность за создание объекта B
из конструктора A
. Это сделает ваш код более надежным и более легким в обслуживании в долгосрочной перспективе. Идея состоит в том, чтобы уменьшить связь между двумя классами A
и B
. Преимущество, которое это дает вам, состоит в том, что теперь вы можете передать любой объект, который расширяет B
(или реализует B
, если он является интерфейсом) конструктору A
, и он будет работать. Один недостаток заключается в том, что вы отказываетесь от инкапсуляции объекта B
, поэтому он подвергается вызову конструктора A
. Вам придется подумать, стоит ли выигрывать этот компромисс, но во многих случаях они есть.
B
было внутренним вопросом A
, и если окажется, что лучший дизайн не должен использовать B
, ваше предложение сложнее изменить.
– JaakkoK
3 January 2010 в 08:51
A
нуждается в B
now i> в этой конструкции i>, и я хотел бы остановиться на ситуации, если эта ситуация изменится.
– JaakkoK
3 January 2010 в 10:52
List<Integer> intList = new ArrayList<>();
? Это может быть полностью внутренняя деталь реализации. Передача ArrayList в constuctor кажется совершенно противоположной хорошей инкапсуляции.
– sprinter
23 January 2015 в 00:11
Оба метода приемлемы. Обратите внимание, что в последнем случае b=new B()
не может быть инициализирован, если присутствует другой конструктор. Подумайте о инициализационном коде вне конструктора как об общем конструкторе, и код будет выполнен.
Сегодня меня сожгли интересным способом:
class MyClass extends FooClass {
String a = null;
public MyClass() {
super(); // Superclass calls init();
}
@Override
protected void init() {
super.init();
if (something)
a = getStringYadaYada();
}
}
Увидеть ошибку? Оказывается, что инициализатор a = null
вызывается после , вызывается конструктор суперкласса. Поскольку конструктор суперкласса вызывает init (), инициализация a
является , после инициализацией a = null
.
Второй пример - ленивая инициализация. Первая - более простая инициализация, они по существу одинаковы.
Я не видел в ответах следующее:
Возможное преимущество наличия инициализации во время объявления может быть в настоящее время IDE, где вы можете очень легко перейти к объявлению переменной (в основном Ctrl-<hover_over_the_variable>-<left_mouse_click>
) из любого места вашего кода. Затем вы сразу увидите значение этой переменной. В противном случае вам нужно «искать» место, где выполняется инициализация (в основном: конструктор).
Это преимущество, конечно, вторично по отношению ко всем другим логическим рассуждениям, но для некоторых людей, которые «могут» быть более важным.
Я думаю, что пример 2 предпочтительнее. Я считаю, что наилучшей практикой является объявление вне конструктора и инициализация в конструкторе.
Использование либо инъекции зависимостей , либо ленивая инициализация всегда предпочтительнее, как уже было подробно объяснено в других ответах.
Когда вы не хотите или не могут использовать эти шаблоны и для примитивных типов данных, есть три веские причины, по которым я могу подумать, почему предпочтительнее инициализировать атрибуты класса вне конструктора:
мое личное «правило» (вряд ли когда-либо сломано) заключается в следующем:
Итак, у меня был бы код вроде:
public class X
{
public static final int USED_AS_A_CASE_LABEL = 1; // only exception - the compiler makes me
private static final int A;
private final int b;
private int c;
static
{
A = 42;
}
{
b = 7;
}
public X(final int val)
{
c = val;
}
public void foo(final boolean f)
{
final int d;
final int e;
d = 7;
// I will eat my own eyes before using ?: - personal taste.
if(f)
{
e = 1;
}
else
{
e = 2;
}
}
}
Таким образом, я всегда на 100% уверен, где искать объявления переменных (в начале блока) и их назначения (как только это имеет смысл после объявления). Это приводит к повышению эффективности, поскольку вы никогда не инициализируете переменную со значением, которое не используется (например, объявлять и инициализировать vars, а затем выдавать исключение, прежде чем половина этих варов должна иметь значение). Вы также не завершаете бессмысленную инициализацию (например, int i = 0, а затем позже, прежде чем использовать «i», выполните i = 5;.
Я очень сильно оцениваю последовательность, поэтому следуя этому «rule» - это то, что я делаю все время, и это упрощает работу с кодом, так как вам не нужно искать вещи.
Ваш пробег может отличаться.
Есть еще одна тонкая причина для инициализации вне конструктора, о которой никто не упоминал раньше (очень конкретный должен сказать). Если вы используете инструменты UML для создания диаграмм классов из кода (обратное проектирование), большинство инструментов, которые, как я полагаю, заметят инициализацию примера 1 и передадут его на диаграмму (если вы предпочитаете, чтобы он отображал начальные значения, например Я делаю). Они не будут принимать эти начальные значения из примера 2. Опять же, это очень конкретная причина - если вы работаете с инструментами UML, но как только я узнал об этом, я пытаюсь использовать все значения по умолчанию вне конструктора, если, как и было упомянутый выше, существует проблема возможного выброса или сложной логики.
Я считаю, что это почти вопрос вкуса, если инициализация проста и не нуждается в какой-либо логике.
Подход конструктора немного более хрупкий, если вы не используете блок инициализатора, потому что, если позже вы добавите второй конструктор и забудете инициализировать b там, вы получите нуль b только тогда, когда используя этот последний конструктор.
Подробнее о инициализации в Java см. в http://java.sun.com/docs/books/tutorial/java/javaOO/initial.html (и для объяснений по блокам инициализатора и другим малоизвестным функциям инициализации).
the first variant is more "readable"
, который можно обсуждать: если вы инициализируете все свои поля в конструкторе, вы точно знаете, что когда вы читаете код, у вас есть только одно место для поиска ... – nbro 8 May 2015 в 10:42