默认构造函数与内联字段初始化

默认构造函数和直接初始化对象的字段有什么区别?

有什么理由比其他的更喜欢下面的例子之一?

例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]; } } 

初始化器在构造器体之前执行。 (如果你有初始化和构造函数,构造函数代码执行第二次并覆盖初始值,

当你总是需要相同的初始值(比如在你的例子中,一个给定大小的数组或特定值的整数)时,初始化器是很好的,但它可以对你有利或者不利于你:

如果你有许多不同的初始化variables的构造函数(即不同的值),那么初始化程序是无用的,因为这些变化将被覆盖,并且浪费。

另一方面,如果你有许多使用相同值初始化的构造函数,那么你可以通过在一个地方保存初始化来保存代码行(并且使你的代码更容易维护)。

就像迈克尔说的那样,还有一个涉及味道的问题 – 你可能想把代码保存在一个地方。 虽然如果你有很多构造函数,你的代码在任何情况下都不在一个地方,所以我会倾向于初始化。

更喜欢示例1的原因是对于less代码(这总是好的)它是相同的function。

除此之外,没有区别。

然而,如果你有明确的构造函数,我宁愿把所有的初始化代码放到那些(并链接它们),而不是在构造函数和字段初始值之间分割。

当有复杂的初始化逻辑执行时(比如填充地图,一个ivar依靠另一个通过一系列启发式步骤来执行,等等),我更喜欢字段初始值设定项并使用默认构造函数。

@迈克尔B说:

…我宁愿将所有初始化代码放入那些(并链接它们),而不是在构造函数和字段初始值设定项之间进行分割。

MichaelB(我鞠躬到71 + K代表)是非常合理的,但我的倾向是保持内联最终初始化器中的简单初始化,并在构造函数中执行复杂的初始化部分。

我能想到的唯一区别是,如果你要添加另一个构造函数

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

那么在第一个例子中,你将不再有一个默认的构造函数,而在第二个例子中,你仍然会有默认的构造函数(如果需要的话,甚至可以从新的构造函数中调用它)

我们应该倾向于字段初始值设定项还是构造函数来给字段赋予默认值?

我不会考虑可能会在字段实例化和字段延迟/渴望实例化中出现的exception,这些exception涉及可读性和可维护性问题以外的其他问题。

对于执行相同逻辑并产生相同结果的两个代码,应该有利于具有最佳可读性和可维护性的方式。

TL; DR

经验法则:

  • 保持select方式的一致性

  • 每种方式都有其优点和缺点

  • 根据领域的特殊性,不要犹豫在同一个class级中混合两种方式

  • 不要犹豫,使用字段初始值设定对不昂贵的集合字段实例化,以防止NullPointerException

  • 在具有单一构造函数的类中,字段初始值设定器的方式通常更具可读性,而且不会太冗长

  • 在具有多个构造函数的类中,如果构造函数在它们之间没有或很less耦合,那么字段初始化方法通常更具可读性,而且不会太冗长。

  • 在具有多个构造函数的类中,类声明很多字段,因为具有默认值的字段可能是分散的,所以在中心构造函数中对它们进行赋值应该更好。 所以build设者的方式应该是青睐的。

  • 在其他有多个构造函数的类中,没有一个方法更好。


用一个非常简单的代码,在现场声明中的分配似乎更好,它是。

这是不太详细和更直的:

 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]; } } 

在具有如此真实特性的真实课堂中,情况是不同的。
事实上,根据遇到的具体情况,一种方法是,另一方或任何一方都应该受到青睐。


概论

  • 你必须保持一致的方式select一个解决scheme或另一个。
    不要改变同一类和类之间的决策规则。

有利于构造方面的评估领域的特殊性:

  • 对于所有创build的实例具有不同的默认值的字段。

使用字段初始值设定项来对字段赋值将会很容易出错,因为在构造函数执行过程中定义的值可能会被覆盖。

  • 该类声明了许多字段

识别默认字段值比较困难,因为具有默认值的字段可能明显远离。

赞成声明中评估领域的特殊性:

  • 该类声明了一个构造函数

OP示例代码说明了这种情况。
它不那么冗长,因为我们只有一个构造函数,它不分散实例化逻辑。

  • 该类声明了多个构造函数,但它们之间没有或很less耦合。

如果实例化validation规则和传递的参数在构造函数之间没有或很less耦合,则我们处于与单个构造函数情况类似的configuration:字段初始化方式通常更具可读性且不太冗长。

  • 对于“ Collections字段(列表,地图等)。

在声明过程中实例化它们的具体类通常是一种理想的简化方式,因为它不会真正改变底层实例的状态,而是防止某些NullPointException并减less处理代码中的样板代码的方法。

对于这种或那种方式的特殊性并不是真正的优势:

  • 该类声明了具有它们之间耦合的多重构造函数。

在类中有多个执行常见处理的构造函数有利于执行逻辑的所有其他构造函数委托的中心构造函数的显示。
为了避免在多个构造函数和字段初始值之间散布实例的初始化规则,在这个中心构造函数中收集具有默认值和validation规则的字段赋值似乎更加相关。

所以,在字段初始值设定项或所有其他构造函数调用的中心构造函数中分配默认值主要是基于意见的:首选项是查看类顶部的所有缺省值,或者将它们设置在中心构造函数的主体中。


研究案例1

我将从一个简单的Car类开始,我将更新以说明这些观点。
Car申报4个领域和3个build设者之间有联系。

给所有领域的领域初始者赋予默认值是不可取的

 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 ; } } 

字段声明中指定的缺省值并不全是可靠的。 只有nameorigin字段具有真正的默认值。

nbSeatcolor字段首先在它们的声明中被赋值,然后这些可以在带有参数的构造函数中被覆盖。
这是错误的倾向,除了这种估价领域的方式,类减less了它的可靠性水平。 如何能够依赖在字段声明期间赋值的任何默认值,而在两个字段中certificate是不可靠的?

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; } } 

这个解决scheme非常好,因为它不会产生重复,它将所有的逻辑都集中在一个地方:具有最多参数的构造函数。
它有一个缺点:需要将调用链接到另一个构造函数。
但这是一个缺点吗?

3.在字段初始化器中为构造函数没有赋值的字段赋予一个默认值,这个值是更好的,但是仍然存在重复问题

我们将使用任何已创build实例的默认值来为其声明中的字段赋值,并为任何已创build的实例使用没有默认值的字段的构造函数。

通过在声明中不重视nbSeatcolor字段,我们可以清楚地区分默认值和没有字段。

 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; } } 

这个解决scheme相当不错,但它重复了每个Car实例中的实例化逻辑,与之前的解决scheme相反。

在这个简单的例子中,我们可以开始理解重复问题,但似乎只是有点烦人。
在实际情况下,复制可能非常重要,因为构造函数可能执行计算和validation。
让一个构造函数执行实例化逻辑变得非常有用。

所以最后在字段声明中的赋值并不总是让构造函数委托给另一个构造函数。

这是一个改进的版本。

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"; } } 

由于我们在声明中并没有对nameorigin字段进行评估,而且我们也没有自然地由其他构造函数调用的公共构造函数,所以我们不得不引入一个initDefaultValues()方法并在每个构造方法中调用它。
所以我们不要忘记调用这个方法。

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()方法来调用它。
字段初始值设定器完成这项工作


结论

在任何情况下)对字段初始值设定项中的字段进行赋值不应该针对所有字段执行,而只针对那些不能被构造函数覆盖的字段。

用例1)在多个构造函数之间具有共同处理的情况下,它主要是基于意见的。
解决scheme2( 使用构造函数来赋值所有字段并依赖构造函数链 )和解决scheme4( 在字段初始化程序中为构造函数未赋予新值并依赖于构造函数链的字段赋予默认值 ) ,可维护和强大的解决scheme。
对于声明许多字段的类,由于具有默认值的字段可能是分散的,因此在中心构造函数中对它们进行赋值应该更好。

用例2)如果在多个构造函数之间没有共同的处理/关系,就像在单个构造函数中一样,解决scheme2( 在字段初始化器中为构造函数不赋予新值的字段赋予一个默认值 )看起来更好。