назначить частный член внутри класса непосредственно после объявления [duplicate]

Исключение нулевого указателя - это индикатор того, что вы используете объект, не инициализируя его.

Например, ниже - класс ученика, который будет использовать его в нашем коде.

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 ");
        }
    }
}
195
задан Jeroen Vannevel 1 September 2014 в 15:13
поделиться

13 ответов

  • Нет никакой разницы - инициализация переменной экземпляра фактически помещается в конструктор (ы) компилятором.
  • Первый вариант более читабельен.
  • Вы не может иметь обработку исключений с первым вариантом.
  • Кроме того, имеется блок инициализации, который также помещается в конструктор (ы) компилятором:
    {
        a = new A();
    }
    

Проверить Объяснение и советы Sun

Из этого учебника :

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

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

ExpensiveObject o;

public ExpensiveObject getExpensiveObject() {
    if (o == null) {
        o = new ExpensiveObject();
    }
    return o;
}

И в конечном счете (как указано Биллом), ради управления зависимостью, лучше избегать с помощью оператора new в любом месте вашего класса. Вместо этого использование Injection Dependency предпочтительнее, то есть позволить кому-то другому (другому классу / фреймворку) создавать экземпляры и вводить зависимости в вашем классе.

241
ответ дан Robert Harvey 17 August 2018 в 22:23
поделиться
  • 1
    Объекты @Bozho Do инициализации объекта идут в конструктор до или после блока инициализации? – Cruncher 3 September 2013 в 21:34
  • 2
    раньше, думаю. Но не уверен :) – Bozho 4 September 2013 в 11:55
  • 3
    the first variant is more "readable", который можно обсуждать: если вы инициализируете все свои поля в конструкторе, вы точно знаете, что когда вы читаете код, у вас есть только одно место для поиска ... – nbro 8 May 2015 в 10:42
  • 4
    @Bozho - Не могли бы вы объяснить, почему у вас нет обработки исключений с первым вариантом? – Mario Galea 23 November 2015 в 15:52
  • 5
    @Bozho Я думаю, что в первом варианте исключение при вызове new не может быть обработано внутри самого класса, скорее, оно будет выброшено и позволит пользователю обрабатывать это (или не обрабатывать). Второй вариант, вы можете поместить try {} вокруг нового и обработать исключение внутри самого класса. – Gordon Liang 17 December 2015 в 20:02

Второй вариант предпочтительнее, так как позволяет использовать различную логику в ctors для создания экземпляра класса и использовать цепочку ctors. Например,

class A {
    int b;

    // secondary ctor
    A(String b) {
         this(Integer.valueOf(b));
    }

    // primary ctor
    A(int b) {
         this.b = b;
    }
}

Таким образом, второй вариант более гибкий.

0
ответ дан Andriy Kryvtsun 17 August 2018 в 22:23
поделиться

Пример 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.
        }
    }

    // ...

}
6
ответ дан BalusC 17 August 2018 в 22:23
поделиться

Другим вариантом будет использование Injection Dependency .

class A{
   B b;

   A(B b) {
      this.b = b;
   }
}

Это снимает ответственность за создание объекта B из конструктора A. Это сделает ваш код более надежным и более легким в обслуживании в долгосрочной перспективе. Идея состоит в том, чтобы уменьшить связь между двумя классами A и B. Преимущество, которое это дает вам, состоит в том, что теперь вы можете передать любой объект, который расширяет B (или реализует B, если он является интерфейсом) конструктору A, и он будет работать. Один недостаток заключается в том, что вы отказываетесь от инкапсуляции объекта B, поэтому он подвергается вызову конструктора A. Вам придется подумать, стоит ли выигрывать этот компромисс, но во многих случаях они есть.

34
ответ дан Bill the Lizard 17 August 2018 в 22:23
поделиться
  • 1
    С другой стороны, он увеличивает сцепление в том смысле, что теперь вы стали более заметными. Раньше использование B было внутренним вопросом A, и если окажется, что лучший дизайн не должен использовать B, ваше предложение сложнее изменить. – JaakkoK 3 January 2010 в 08:51
  • 2
    связь существует в любом случае - A требует B. Но создание экземпляра внутри класса означает «A нуждается в именно этот B », в то время как DI позволяет использовать несколько различных B. – Bozho 3 January 2010 в 09:57
  • 3
    A нуждается в B now в этой конструкции , и я хотел бы остановиться на ситуации, если эта ситуация изменится. – JaakkoK 3 January 2010 в 10:52
  • 4
    @jk: Если вы отделяете создание объекта от бизнес-логики всюду - в частности, где создается A - с использованием классов DI и Factory, это совсем не сложно. Это нужно изменить только в одном месте - Factory, который создает объекты A. Если вы согласны в этом, это не сложно понять. Я думаю, что преимущества перевешивают издержки. Муфта снижена, а общая конструкция лучше проверять и поддерживать. – Bill the Lizard 3 January 2010 в 15:33
  • 5
    @BilltheLizard вы бы использовали эту идиому даже для чего-то простого, как List<Integer> intList = new ArrayList<>();? Это может быть полностью внутренняя деталь реализации. Передача ArrayList в constuctor кажется совершенно противоположной хорошей инкапсуляции. – sprinter 23 January 2015 в 00:11

Оба метода приемлемы. Обратите внимание, что в последнем случае b=new B() не может быть инициализирован, если присутствует другой конструктор. Подумайте о инициализационном коде вне конструктора как об общем конструкторе, и код будет выполнен.

1
ответ дан Chandra Patni 17 August 2018 в 22:23
поделиться

Сегодня меня сожгли интересным способом:

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.

14
ответ дан Edward Falk 17 August 2018 в 22:23
поделиться
  • 1
    Урок здесь никогда не должен вызывать избыточные функции от конструктора! :) Эффективная Java, пункт 17 имеет хорошее обсуждение. – Mohit Chugh 26 August 2015 в 06:33
  • 2
    Отличная точка. Инициализируя при объявлении, вы теряете контроль точно , когда инициализируется переменная. И это может, но вы в a $$ (да, компиляторы тоже меняют свои реализации!). – Scott Biggs 10 June 2016 в 06:40
  • 3
    @MohitChugh: Действительно, правда, как камень. Фактически, современные современные Java IDE, такие как NetBeans (и, кроме того, другие), бросают вам предупреждения, если вы вызываете переопределяемые методы из конструктора. И по этой причине Эдвард Фальк столкнулся. – GeertVc 30 July 2018 в 09:01

Второй пример - ленивая инициализация. Первая - более простая инициализация, они по существу одинаковы.

0
ответ дан fastcodejava 17 August 2018 в 22:23
поделиться

Я не видел в ответах следующее:

Возможное преимущество наличия инициализации во время объявления может быть в настоящее время IDE, где вы можете очень легко перейти к объявлению переменной (в основном Ctrl-<hover_over_the_variable>-<left_mouse_click>) из любого места вашего кода. Затем вы сразу увидите значение этой переменной. В противном случае вам нужно «искать» место, где выполняется инициализация (в основном: конструктор).

Это преимущество, конечно, вторично по отношению ко всем другим логическим рассуждениям, но для некоторых людей, которые «могут» быть более важным.

0
ответ дан GeertVc 17 August 2018 в 22:23
поделиться

Я думаю, что пример 2 предпочтительнее. Я считаю, что наилучшей практикой является объявление вне конструктора и инициализация в конструкторе.

1
ответ дан jkeesh 17 August 2018 в 22:23
поделиться

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

Когда вы не хотите или не могут использовать эти шаблоны и для примитивных типов данных, есть три веские причины, по которым я могу подумать, почему предпочтительнее инициализировать атрибуты класса вне конструктора:

  1. избегать повторения = если вы имеют более одного конструктора, или когда вам нужно будет добавить больше, вам не придется повторять инициализацию снова и снова во всех телах конструкторов;
  2. улучшенная читаемость = вы можете легко сказать с первого взгляда какие переменные должны быть инициализированы извне класса,
  3. сокращенные строки кода = для каждой инициализации, сделанной при объявлении, в конструкторе будет меньше строки.
3
ответ дан Marco Lackovic 17 August 2018 в 22:23
поделиться

мое личное «правило» (вряд ли когда-либо сломано) заключается в следующем:

  • объявить все переменные в начале блока
  • сделать все переменные окончательными, если они не могут быть
  • объявить одну переменную на строку
  • никогда не инициализировать переменную, где объявлено
  • , только инициализировать что-то в конструкторе, когда ему нужны данные от конструктора, чтобы выполнить инициализацию

Итак, у меня был бы код вроде:

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» - это то, что я делаю все время, и это упрощает работу с кодом, так как вам не нужно искать вещи.

Ваш пробег может отличаться.

14
ответ дан TofuBeer 17 August 2018 в 22:23
поделиться
  • 1
    возможно, это было для "никогда не инициализировать переменную, где объявлено" (хотя это был не я). Или фигурная фигурная скобка на новой строке, которая считается идиомой C / C ++. Во всяком случае, один передо мной от меня, чтобы компенсировать;) – Bozho 4 January 2010 в 07:42
  • 2
    Я бы предпочел, чтобы люди голосовали по техническим причинам, нежели эстетические (размещение {} или их необязательное использование). Если люди голосуют, они должны хотя бы сказать, что они считают неправильным с ответом ... нет ничего технически неправильного, и именно так я кодировал в C / C ++ / Java в течение последних 20 лет (хорошо Java 16) поэтому я на 100% уверен, что он работает :-) (и спасибо за счетчик голосов :-) – TofuBeer 4 January 2010 в 07:47
  • 3
    это уродливо, как грех, вот что с ним не так. Это довольно забавно, что вы будете есть свои собственные глаза перед использованием тернарного оператора, но предпочитаете несколько статических блоков инициализации над соответствующим конструктором ООП. Ваш путь полностью разрушает Injection Dependency (по номиналу, да компилятор по существу исправляет его для вас, перемещая все в конструктор, но тогда вы, по сути, учите людей полагаться на магию компилятора, а не на правильную вещь), является неподъемным, и возвращает нас к ужасным дням C ++. Начинающие читатели, пожалуйста, не делайте этого. – Visionary Software Solutions 6 August 2013 в 07:23
  • 4
    Если вы собираетесь включить правило о внесении переменных, которые могут быть окончательными, окончательными. Тогда вы действительно должны включить бит о создании всех переменных, которые могут быть частными, частными. – Cruncher 3 September 2013 в 21:40
  • 5
    @TofuBeer: не беспокойтесь об этом. Большинство разработчиков Java, как правило, чересчур педантичны и придирчивы. Я уверен, что они выбрали код, даже если бы Джошуа Блох написал это (если бы они не знали, что это был он). Индивидуальный вкус - это личный вкус; в конечном счете, ни центральный процессор, ни JRE не заботятся о синтаксическом стиле. – Aquarelle 20 September 2013 в 23:38

Есть еще одна тонкая причина для инициализации вне конструктора, о которой никто не упоминал раньше (очень конкретный должен сказать). Если вы используете инструменты UML для создания диаграмм классов из кода (обратное проектирование), большинство инструментов, которые, как я полагаю, заметят инициализацию примера 1 и передадут его на диаграмму (если вы предпочитаете, чтобы он отображал начальные значения, например Я делаю). Они не будут принимать эти начальные значения из примера 2. Опять же, это очень конкретная причина - если вы работаете с инструментами UML, но как только я узнал об этом, я пытаюсь использовать все значения по умолчанию вне конструктора, если, как и было упомянутый выше, существует проблема возможного выброса или сложной логики.

0
ответ дан tulu 17 August 2018 в 22:23
поделиться

Я считаю, что это почти вопрос вкуса, если инициализация проста и не нуждается в какой-либо логике.

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

Подробнее о инициализации в Java см. в http://java.sun.com/docs/books/tutorial/java/javaOO/initial.html (и для объяснений по блокам инициализатора и другим малоизвестным функциям инициализации).

4
ответ дан Vinko Vrsalovic 17 August 2018 в 22:23
поделиться
  • 1
    вот почему у вас есть DI и @Required :) – Sujoy 3 January 2010 в 13:39
  • 2
    Да. Я только описывал различия между двумя примерами OP. – Vinko Vrsalovic 3 January 2010 в 14:12
  • 3
    Скорее всего, большое количество конструкторов означает, что вы нарушаете принцип единой ответственности и имеете гораздо большие проблемы с вашим дизайном. – Visionary Software Solutions 6 August 2013 в 07:31
Другие вопросы по тегам:

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