デフォルトのコンストラクターとオブジェクトのフィールドを直接初期化することの違いは何ですか?
次の例のいずれかを他よりも好む理由は何ですか?
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];
}
}
初期化子は、コンストラクターの本体の前に実行されます。 (イニシャライザーとコンストラクターの両方がある場合に意味があり、コンストラクターコードは2番目に実行され、初期化された値をオーバーライドします)
初期化子は、常に同じ初期値が必要な場合(たとえば、特定のサイズの配列、特定の値の整数など)が必要な場合に適していますが、次のように有利または不利に機能します。
変数を異なる方法で(つまり異なる値で)初期化するコンストラクターが多数ある場合、変更がオーバーライドされて無駄になるため、初期化子は役に立ちません。
一方、同じ値で初期化する多くのコンストラクターがある場合は、初期化を1か所に維持することで、コードの行を保存できます(そして、コードを少し保守しやすくします)。
マイケルが言ったように、好みの問題もあります-コードを一か所に保管したいかもしれません。コンストラクターが多数ある場合、コードはどのような場合でも1か所に存在しないため、初期化子を優先します。
例1を好む理由は、コードが少なくても同じ機能であるためです(常に良いです)。
それ以外は、違いはありません。
ただし、明示的なコンストラクターがある場合は、コンストラクターとフィールド初期化子に分割するのではなく、すべての初期化コードをそれらに配置(およびチェーン)することをお勧めします。
フィールドにデフォルト値を与えるために、フィールド初期化子またはコンストラクタを使用すべきですか?
読みやすさと保守性の懸念以外の懸念に触れるフィールドのインスタンス化およびフィールドの遅延/熱心なインスタンス化中に発生する可能性のある例外は考慮しません。
同じロジックを実行し、同じ結果を生成する2つのコードの場合、読みやすさと保守性が最も高い方法が推奨されます。
最初のオプションまたは2番目のオプションの選択は、コードのすべての質問の前organization、readabilityおよびmaintainability。
選択方法の一貫性を保つ(アプリケーションコード全体を明確にする)
Collection
フィールドをインスタンス化するためにフィールド初期化子を使用することをheしないでくださいNullPointerException
コンストラクタによって上書きされる可能性のあるフィールドにはフィールド初期化子を使用しないでください
単一のコンストラクタを持つクラスでは、フィールド初期化子の方法は一般的に読みやすく、冗長ではありません
複数のコンストラクターを持つクラスでは、コンストラクターがそれらの間のカップリングをまったくまたはほとんど持たない、フィールド初期化子の方法は一般的に読みやすく、冗長性が低い
複数のコンストラクターを備えたクラスで、コンストラクターがそれらの間にカップリングを持っている場合、2つの方法のいずれも実際には優れていませんが、選択した方法にかかわらず、チェーンコンストラクターと組み合わせます方法です(ユースケース1を参照)。
非常に単純なコードを使用すると、フィールド宣言中の割り当てが改善されたように見えます。
これは冗長ではなく、よりストレートです:
_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つのフィールドに対して信頼できないことが証明されているのに、フィールド宣言中に割り当てられたデフォルト値にどのように依存できますか?
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;
}
}
_
このソリューションは、重複を作成せず、すべてのロジックを1つの場所(パラメーターの最大数を持つコンストラクター)に収集するため、非常に優れています。
これには1つの欠点があります。呼び出しを別のコンストラクターにチェーンする必要があるということです。
しかし、それは欠点ですか?
。コンストラクタが新しい値を割り当てないフィールドのフィールド初期化子にデフォルト値を与える方が良いが、それでも複製の問題がある
宣言で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
クラスを変更します。
Now、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()
メソッドを導入する必要があります。各コンストラクターで呼び出します。したがって、このメソッドを呼び出すことを忘れないでください。
no argコンストラクターでinitDefaultValues()
bodyをインライン化できましたが、他のコンストラクターから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()
メソッドや引数なしのコンストラクターを呼び出す必要はありません。フィールド初期化子は完璧です。
結論
いずれの場合も)フィールド初期化子のフィールドの評価は、すべてのフィールドではなく、コンストラクターによって上書きできないフィールドに対してのみ実行する必要があります。
ユースケース1)共通の処理を行う複数のコンストラクターの場合、主に意見に基づいています。
ソリューション2(コンストラクターを使用してすべてのフィールドの値を設定し、コンストラクターのチェーンに依存)およびソリューション4(コンストラクターが割り当てないフィールドのフィールド初期化子にデフォルト値を与える彼らにとっては、新しい値と、コンストラクターに依存する)が、最も読みやすく、保守しやすく、堅牢なソリューションとして表示されます。
ユースケース2)単一のコンストラクターの場合のように共通の処理/関係のない複数のコンストラクターの場合、ソリューション2(コンストラクターが提供するフィールドのフィールド初期化子にデフォルト値を与えるそれらに新しい値を割り当てないでください).
実行する複雑な初期化ロジックがある場合は、フィールド初期化子を好み、デフォルトのコンストラクターに頼ります(たとえば、マップにデータを入力したり、一連のヒューリスティックなステップを実行することで1つのivarが別のivarに依存するなど)。
@マイケルBは言った:
...コンストラクタとフィールド初期化子に分割するのではなく、すべての初期化コードをそれらに配置(およびチェーン)したいと思います。
MichaelB(私は71歳以上のK担当者に敬意を表しています)は完全に理にかなっていますが、私の傾向は、インラインの最終初期化子で単純な初期化を維持し、コンストラクターで初期化の複雑な部分を行うことです。
私が考えることができる唯一の違いは、別のコンストラクタを追加する場合
public Foo(int inX){
x = inX;
}
その後、最初の例ではデフォルトのコンストラクターがなくなりますが、2番目の例ではデフォルトのコンストラクターがあります(必要に応じて、新しいコンストラクター内から呼び出すこともできます)