web-dev-qa-db-ja.com

Javaで複数のコンストラクターを処理する最良の方法

私はJavaで複数のコンストラクターを処理する最良の(つまり、最もクリーン/安全/最も効率的な)方法は何かと疑問に思っていましたか?特に、1つ以上のコンストラクターですべてのフィールドが指定されているわけではありません。

public class Book
{

    private String title;
    private String isbn;

    public Book()
    {
      //nothing specified!
    }

    public Book(String title)
    {
      //only title!
    }

    ...     

}

フィールドが指定されていない場合はどうすればよいですか?私はこれまでフィールドでnullにならないようにクラスでデフォルト値を使用していましたが、それは物事を行う「良い」方法ですか?

72
Peter

少し簡単な答え:

public class Book
{
    private final String title;

    public Book(String title)
    {
      this.title = title;
    }

    public Book()
    {
      this("Default Title");
    }

    ...
}
136
Craig P. Motlin

Builderパターンの使用を検討してください。パラメータにデフォルト値を設定し、明確で簡潔な方法で初期化できます。例えば:


    Book b = new Book.Builder("Catcher in the Rye").Isbn("12345")
       .Weight("5 pounds").build();

編集:また、異なるシグネチャを持つ複数のコンストラクターの必要性がなくなり、読みやすくなりました。

34
kgrad

クラスの不変条件、つまりクラスのインスタンスに対して常に真となるプロパティを指定する必要があります(たとえば、本のタイトルがnullになることはなく、犬のサイズは常に> 0になります)。

これらの不変式は、構築中に確立される必要があり、オブジェクトの存続期間中保存される必要があります。つまり、メソッドは不変式を壊してはなりません。コンストラクターは、必須の引数を使用するか、デフォルト値を設定することにより、これらの不変式を設定できます。

class Book {
    private String title; // not nullable
    private String isbn;  // nullable

    // Here we provide a default value, but we could also skip the 
    // parameterless constructor entirely, to force users of the class to
    // provide a title
    public Book()
    {
        this("Untitled"); 
    }

    public Book(String title) throws IllegalArgumentException
    {
        if (title == null) 
            throw new IllegalArgumentException("Book title can't be null");
        this.title = title;
        // leave isbn without value
    }
    // Constructor with title and isbn
}

ただし、これらの不変式の選択は、作成しているクラス、その使用方法などに大きく依存するため、質問に対する明確な答えはありません。

19
Luc Touraille

常に有効で正当なオブジェクトを作成する必要があります。コンストラクターparmsを使用できない場合は、Builderオブジェクトを使用して作成し、オブジェクトが完成したときにのみBuilderからオブジェクトを解放する必要があります。

コンストラクターの使用に関する質問:私は常に、他のすべてが優先する1つのベースコンストラクターを持ち、「省略された」パラメーターで次の論理コンストラクターにチェーンし、ベースコンストラクターで終わるようにします。そう:

class SomeClass
{
SomeClass() {
    this("DefaultA");
    }

SomeClass(String a) {
    this(a,"DefaultB");
    }

SomeClass(String a, String b) {
    myA=a;
    myB=b;
    }
...
}

これが不可能な場合は、すべてのコンストラクターが従うプライベートinit()メソッドを使用しようとします。

そして、コンストラクターとパラメーターの数を少なくしてください-ガイドラインとしてそれぞれ最大5つ。

11
Lawrence Dol

一般的なコンストラクタのヒント:

  • 単一のコンストラクターですべての初期化に焦点を当て、他のコンストラクターからそれを呼び出すようにしてください
    • これは、デフォルトのパラメーターをシミュレートするために複数のコンストラクターが存在する場合にうまく機能します
  • コンストラクターから最終以外のメソッドを呼び出さないでください
    • プライベートメソッドは定義により最終的なものです
    • 多態性はここであなたを殺すことができます。サブクラスが初期化される前に、サブクラスの実装を呼び出すことになります
    • 「ヘルパー」メソッドが必要な場合は、必ずprivateまたはfinalにしてください
  • Super()への呼び出しで明示的にしてください
    • 明示的に記述しなくてもsuper()が呼び出されていることに気付かないJavaプログラマーの数に驚かれます(これを呼び出す必要がないと仮定して(...) )
  • コンストラクターの初期化ルールの順序を知ってください。基本的には:

    1. this(...)存在する場合(just別のコンストラクターに移動)
    2. super(...)を呼び出す[明示的でない場合は、暗黙的にsuper()を呼び出す]
    3. (これらのルールを再帰的に使用してスーパークラスを構築します)
    4. 宣言を介してフィールドを初期化する
    5. 現在のコンストラクターの本体を実行する
    6. 前のコンストラクターに戻る(this(...)呼び出しに遭遇した場合)

全体の流れは次のようになります。

  • スーパークラス階層の最上位までObjectに移動します
  • 行われていない間
    • 初期化フィールド
    • コンストラクター本体を実行する
    • サブクラスにドロップダウン

悪の素敵な例として、以下が何を印刷するかを考えてみて、それを実行してください

package com.javadude.sample;

/** THIS IS REALLY EVIL CODE! BEWARE!!! */
class A {
    private int x = 10;
    public A() {
        init();
    }
    protected void init() {
        x = 20;
    }
    public int getX() {
        return x;
    }
}

class B extends A {
    private int y = 42;
    protected void init() {
        y = getX();
    }
    public int getY() {
        return y;
    }
}

public class Test {
    public static void main(String[] args) {
        B b = new B();
        System.out.println("x=" + b.getX());
        System.out.println("y=" + b.getY());
    }
}

上記がなぜ機能するのかを説明するコメントを追加します...一部ではない...

6

別の考慮事項として、フィールドが必須または範囲が制限されている場合は、コンストラクターでチェックを実行します。

public Book(String title)
{
    if (title==null)
        throw new IllegalArgumentException("title can't be null");
    this.title = title;
}
3
Steve Kuo

コンストラクタの代わりに静的ファクトリメソッドの使用を検討する価値があるかもしれません。

私はinsteadと言っていますが、明らかにあなたはreplaceコンストラクタ。ただし、できることは、コンストラクタを静的ファクトリメソッドの後ろに隠すことです。このようにして、静的なファクトリメソッドをクラスAPIの一部として公開しますが、同時にコンストラクタを非表示にしてプライベートまたはパッケージプライベートにします。

特に、Joshua BlochのEffective Java 2nd Editionに見られるように、Builderパターンと比較すると、かなりシンプルなソリューションです。ギャングオブフォーのデザインパターンは、同じ名前のまったく異なるデザインパターンを定義しているため、少し混乱するかもしれませんが、ネストされたクラス、ビルダーを作成することを意味しますオブジェクトなど.

このアプローチにより、ユーザーとクライアントの間に抽象レイヤーが追加され、カプセル化が強化され、変更が容易になります。また、インスタンス制御も提供されます。オブジェクトはクラス内でインスタンス化されるため、クライアントではなく、ユーザーがこれらのオブジェクトをいつどのように作成するかを決定します。

最後に、テストを簡単にします。ロジックや検証を実行せずにフィールドに値を割り当てるだけのダムコンストラクターを提供することで、無効な状態をシステムに導入して、システムの動作と反応をテストできます。コンストラクターでデータを検証している場合、これを行うことはできません。

これについては、(すでに言及した)Joshua BlochのEffective Java 2nd Editionでさらに読むことができます。これはすべての開発者にとって重要なツールですツールボックスとそれが本の第1章の主題であるのも不思議ではありません。 ;-)

あなたの例に従って:

public class Book {

    private static final String DEFAULT_TITLE = "The Importance of Being Ernest";

    private final String title;
    private final String isbn;

    private Book(String title, String isbn) {
        this.title = title;
        this.isbn = isbn;
    }

    public static Book createBook(String title, String isbn) {
        return new Book(title, isbn);
    }

    public static Book createBookWithDefaultTitle(String isbn) {
        return new Book(DEFAULT_TITLE, isbn);
    }

    ...

}

どちらの方法を選択した場合でも、1つのmainコンストラクターを使用することをお勧めします。これは、別のコンストラクターによって使用されている場合でも、盲目的にすべての値を割り当てます。

3

私は次のことをします:

 public class Book 
 {
 private final String title; 
 private final String isbn; 
 
 public Book(final String t 、final String i)
 {
 if(t == null)
 {
 throw new IllegalArgumentException( "t cannot be null"); 
} 
 
 if(i == null)
 {
 throw new IllegalArgumentException( "i cannot be null"); 
} 
 
 title = t; 
 isbn = i; 
} 
} 

私はここで次のことを仮定しています:

1)タイトルは変更されません(そのためタイトルは最終です)2)isbnは変更されません(したがってisbnが最終です)3)タイトルとisbnの両方のない本を持つことは無効です。

学生のクラスを考えてみましょう:

 public class Student 
 {
 private final StudentID id; 
 private String firstName; 
 private String lastName; 
 
 public Student(final StudentID i、
 final String first、
 final String last)
 {
 if(i == null)
 {
 throw new IllegalArgumentException( "i cannot be null"); 
} 
 
 if(first == null)
 {
 throw new IllegalArgumentException( "first cannot be null"); 
} 
 
 if(last == null)
 {
 throw new IllegalArgumentException( "last cannot be null"); 
} 
 
 id = i; 
 firstName = first; 
 lastName = last; 
} 
} 

そこで、ID、名、および姓で生徒を作成する必要があります。学生IDは決して変更できませんが、姓と名は変更できます(結婚する、賭けを失うことで名前を変更するなど)。

どの建設業者が持っているかを決定するとき、あなたは本当に何が理にかなっているのかを考える必要があります。多くの場合、人々はset/getメソッドを教えられているため追加しますが、非常に頻繁に悪い考えです。

不変クラスは、可変クラスよりもはるかに優れています(つまり、最終変数を持つクラスです)。この本: http://books.google.com/books?id=ZZOiqZQIbRMC&pg=PA97&sig=JgnunNhNb8MYDcx60Kq4IyHUC58#PPP1,M1 (有効なJava)には、不変性に関する適切な議論があります。アイテム12と13を見てください。

0
TofuBeer

何人かの人々はヌルチェックを追加することを推奨しています。時々それは正しいことですが、常にそうではありません。スキップする理由を示すこの優れた記事をご覧ください。

http://misko.hevery.com/2009/02/09/to-assert-or-not-to-assert/

0
Craig P. Motlin