Какая разница между конструктором по умолчанию и просто инициализацией полей объекта напрямую?
Ответ 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 (предоставление значения по умолчанию в полевых интерпретаторах для полей, которые конструкторы не присваивают им новое значение) выглядит лучше,