Конструктор по умолчанию против инициализации встроенного поля - PullRequest
101 голосов
/ 07 февраля 2011

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

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

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

Ответы [ 5 ]

64 голосов
/ 07 февраля 2011

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

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

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

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

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

20 голосов
/ 07 февраля 2011

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

Кроме этого, без разницы.

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

7 голосов
/ 19 февраля 2017

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

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

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. Нельзя задавать значение по умолчанию в инициализаторах полей для всех полей

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() тело в конструктор 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 ( Задание значения по умолчанию в инициализаторах поля дляполя, конструкторы которых не присваивают им новое значение ), выглядят лучше.

3 голосов
/ 07 февраля 2011

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

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

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

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

2 голосов
/ 07 февраля 2011

Единственное различие, о котором я могу подумать, состоит в том, что если бы вы добавили еще один конструктор

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...