Конструкция конструктора по умолчанию и инициализация встроенного поля

Какая разница между конструктором по умолчанию и просто инициализацией полей объекта напрямую?

Какие причины могут предпочесть один из следующих примеров?

Пример 1

public class Foo
{
    private int x = 5;
    private String[] y = new String[10];
}

Пример 2

public class Foo
{
    private int x;
    private String[] y;

    public Foo()
    {
        x = 5;
        y = new String[10];
    }
}

Ответ 1

Инициализаторы выполняются перед конструкторами. (Что имеет последствия, если у вас есть как инициализаторы, так и конструкторы, код конструктора выполняет второй и переопределяет инициализированное значение)

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

Если у вас много конструкторов, которые инициализируют переменные по-разному (т.е. с разными значениями), то инициализаторы бесполезны, потому что изменения будут переопределены и расточительны.

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

Как сказал Майкл, есть и вопрос вкуса - вы можете оставить код в одном месте. Хотя, если у вас много конструкторов, ваш код не будет в одном месте в любом случае, поэтому я бы предпочел инициализировать.

Ответ 2

Причиной предпочтения примера 1 является то, что он имеет ту же функциональность для меньшего кода (что всегда хорошо).

Кроме того, никакой разницы.

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

Ответ 3

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

Я не буду рассматривать исключения, которые могут возникнуть во время создания экземпляра поля и ленивый/нетерпеливый экземпляр, который затрагивает другие проблемы, чем проблемы с читабельностью и ремонтопригодностью.
Для двух кодов, которые выполняют одну и ту же логику и приводят к одному и тому же результату, следует придерживаться наилучшей удобочитаемости и ремонтопригодности.

TL; DR

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

  • сохраняйте согласованность в выборе (это делает общий код приложения более понятным)

  • не стесняйтесь использовать инициализаторы полей для создания экземпляров полей Collection для предотвращения NullPointerException

  • не используйте инициализаторы полей для полей, которые могут быть перезаписаны конструкторами

  • в классах с одним конструктором способ инициализации поля обычно более читабельный и менее подробный

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

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


Вопрос OP

С очень простым кодом назначение во время объявления поля кажется лучше, и оно есть.

Это менее подробный и более прямой:

public class Foo {
    private int x = 5;
    private String[] y = new String[10];
}

чем метод конструктора:

public class Foo{
    private int x;
    private String[] y;

    public Foo(){
        x = 5;
        y = new String[10];
    }
}

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


Более подробные примеры для иллюстрации

Пример исследования 1

Я начну с простого класса Car который я обновлю, чтобы проиллюстрировать эти моменты.
Car объявляет 4 поля и некоторые конструкторы, которые имеют отношение между ними.

1. Предоставление значения по умолчанию в полевых intializers для всех полей нежелательно

public class Car {

  private String name = "Super car";
  private String origin = "Mars";      
  private int nbSeat = 5;
  private Color color = Color.black;
  ... 

  ... 
  // Other fields

  ... 

  public Car() {         
  }

  public Car(int nbSeat) {
      this.nbSeat = nbSeat;          
  }

  public Car(int nbSeat, Color color) {
      this.nbSeat = nbSeat;          
      this.color = color;
  }   

}

Значения по умолчанию, указанные в объявлении полей, не все надежны. Только поля name и origin имеют значения по умолчанию.

nbSeat и color сначала оцениваются в их объявлении, а затем они могут быть перезаписаны в конструкторах с аргументами.
Он подвержен ошибкам и, кроме того, таким способом оценки полей, класс снижает уровень надежности. Как можно полагаться на любое значение по умолчанию, назначенное во время объявления полей, в то время как оно оказалось ненадежным для двух полей?

2.Использовать конструктор для оценки всех полей и полагаться на цепочки конструкторов.

public class Car {

  private String name;
  private String origin;      
  private int nbSeat;
  private Color color;
  ... 

  ... 
  // Other fields

  ... 

  public Car() {  
     this(5, Color.black);
  }

  public Car(int nbSeat) {    
     this(nbSeat, Color.black);       
  }

  public Car(int nbSeat, Color color) {
      this.name = "Super car";
      this.origin = "Mars";     
      this.nbSeat = nbSeat;          
      this.color = color; 
  }   

}

Это решение действительно прекрасное, поскольку оно не создает дублирование, оно собирает всю логику в месте: конструктор с максимальным количеством параметров.
У этого есть один недостаток: требование связать вызов другому конструктору.
Но это недостаток?

3. Предоставление значения по умолчанию в полевых инициализаторах для полей, которые конструкторы не присваивают им, новое значение лучше, но все еще имеет проблемы с дублированием

Не оценивая nbSeat и color в своей декларации, мы четко различаем поля со значениями по умолчанию и полями без.

public class Car {

  private String name = "Super car";
  private String origin = "Mars";      
  private int nbSeat;
  private Color color;
  ... 

  ... 
  // Other fields

  ... 

  public Car() {         
     nbSeat = 5;
     color = Color.black;
  }     

  public Car(int nbSeat) {
      this.nbSeat = nbSeat;
      color = Color.black;          
  }

  public Car(int nbSeat, Color color) {
      this.nbSeat = nbSeat;          
      this.color = color;
  }   

}

Это решение довольно хорошо, но оно повторяет логику создания экземпляров в каждом конструкторе Car вопреки предыдущему решению с цепью конструктора.

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

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

Вот улучшенная версия.

4. Дать значение по умолчанию в полевых инализаторах для полей, которые конструкторы не присваивают им новое значение и полагаются на цепочки конструкторов в порядке

public class Car {

  private String name = "Super car";
  private String origin = "Mars";      
  private int nbSeat;
  private Color color;
  ... 

  ... 
  // Other fields

  ...   

  public Car() {  
     this(5, Color.black);
  }

  public Car(int nbSeat) {    
     this(nbSeat, Color.black);       
  }

  public Car(int nbSeat, Color color) {
      // assignment at a single place
      this.nbSeat = nbSeat;          
      this.color = color;
      // validation rules at a single place
         ...
  }   

}

Учебный пример 2

Мы модифицируем оригинальный класс Car.
Теперь Car объявляет 5 полей и 3 конструктора, которые не имеют никакой связи между ними.

1.Использование конструктора для значения полей со значениями по умолчанию нежелательно

public class Car {

  private String name;
  private String origin;      
  private int nbSeat;
  private Color color;
  private Car replacingCar;
  ... 

  ... 
  // Other fields

  ... 

  public Car() {         
    initDefaultValues();
  }

  public Car(int nbSeat, Color color) {
     initDefaultValues();
     this.nbSeat = nbSeat;         
     this.color = color;
  }

  public Car(Car replacingCar) {         
     initDefaultValues();
     this.replacingCar = replacingCar;         
     // specific validation rules          
  }

  private void initDefaultValues() {
     name = "Super car";
     origin = "Mars";      
  }

}

Поскольку мы не оцениваем поля name и origin в их объявлении, и у нас нет общего конструктора, естественно вызываемого другими конструкторами, мы вынуждены вводить метод initDefaultValues() и вызывать его в каждом конструкторе. Поэтому мы не должны забывать называть этот метод.
Обратите внимание, что мы могли бы initDefaultValues() тело initDefaultValues() в конструкторе no arg, но вызов this() без аргумента из другого конструктора не является естественным и может быть легко забыт:

public class Car {

  private String name;
  private String origin;      
  private int nbSeat;
  private Color color;
  private Car replacingCar;
  ... 

  ... 
  // Other fields

  ... 

  public Car() {         
     name = "Super car";
     origin = "Mars";     
  }

  public Car(int nbSeat, Color color) {
     this();
     this.nbSeat = nbSeat;         
     this.color = color;
  }

  public Car(Car replacingCar) {         
     this();
     this.replacingCar = replacingCar;         
     // specific validation rules          
  }

}

2. Предоставление значения по умолчанию в инициализаторах поля для полей, которые конструкторы не присваивают им новое значение, прекрасно

public class Car {

  private String name = "Super car";
  private String origin = "Mars";      
  private int nbSeat;
  private Color color;
  private Car replacingCar;
  ... 

  ... 
  // Other fields

  ... 

  public Car() {         
  }

  public Car(int nbSeat, Color color) {
      this.nbSeat = nbSeat;         
      this.color = color;
  }

  public Car(Car replacingCar) {         
     this.replacingCar = replacingCar;         
     // specific validation rules          
  }

}

Здесь нам не нужно иметь метод initDefaultValues() или конструктор no arg для вызова. Инициализаторы полей идеальны.


Заключение

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

Случай использования 1) В случае множественных конструкторов с общей обработкой между ними он основан прежде всего на мнениях.
Решение 2 (Использование конструктора для оценки всех полей и использование цепочки конструкторов) и решение 4 (Предоставление значения по умолчанию в полевых интерпретаторах для полей, которые конструкторы не присваивают им новое значение и полагающихся на цепочку конструкторов) отображаются как наиболее читаемые, поддерживаемые и надежные решения.

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

Ответ 4

Я предпочитаю инициализаторы полей и прибегать к конструктору по умолчанию, когда есть сложная логика инициализации для выполнения (например, заполнение карты, один ivar зависит от другой посредством серии эвристических шагов для выполнения и т.д.).

@Майкл Б сказал:

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

MichaelB (я склоняюсь к 71+ K rep) имеет смысл, но моя тенденция состоит в том, чтобы сохранить простые инициализации в встроенных финальных инициализаторах и выполнить сложную часть инициализаций в конструкторе.

Ответ 5

Единственное отличие, о котором я могу думать, это то, что если вы должны добавить другой конструктор

public Foo(int inX){
x = inX;
}

то в первом примере у вас больше не будет конструктора по умолчанию, тогда как во втором примере у вас все равно будет конструктор по умолчанию (и даже мог бы позвонить ему из нашего нового конструктора, если мы захотим)