89

Java で複数のコンストラクターを処理する最良の (つまり、最もクリーン/安全/最も効率的な) 方法は何だろうと思っていました。特に、1 つ以上のコンストラクターですべてのフィールドが指定されていない場合:

public class Book
{

    private String title;
    private String isbn;

    public Book()
    {
      //nothing specified!
    }

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

    ...     

}

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

4

9 に答える 9

160

少し単純化した答え:

public class Book
{
    private final String title;

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

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

    ...
}
于 2009-02-24T14:14:03.467 に答える
42

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


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

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

于 2009-02-24T14:19:05.187 に答える
23

クラスの不変条件、つまり、クラスのインスタンスに対して常に true になるプロパティを指定する必要があります (たとえば、本のタイトルが 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
}

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

于 2009-02-24T14:36:18.217 に答える
14

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

コンストラクターの使用の問題について: 私は常に、「省略された」パラメーターを使用して次の論理コンストラクターに連鎖し、基本コンストラクターで終了する、他のすべてのコンストラクターが従う基本コンストラクターを 1 つ持つようにしています。そう:

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

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

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

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

また、コンストラクターとパラメーターの数を少なく保ちます (ガイドラインとして、それぞれ最大 5 つ)。

于 2009-02-24T17:57:18.950 に答える
7

いくつかの一般的なコンストラクターのヒント:

  • すべての初期化を単一のコンストラクターに集中させ、他のコンストラクターから呼び出すようにしてください
    • これは、デフォルトのパラメーターをシミュレートするために複数のコンストラクターが存在する場合にうまく機能します
  • コンストラクターから非finalメソッドを呼び出さないでください
    • プライベートメソッドは定義上最終的なものです
    • ポリモーフィズムはここであなたを殺すことができます。サブクラスが初期化される前に、サブクラスの実装を呼び出すことになりかねません。
    • 「ヘルパー」メソッドが必要な場合は、必ずプライベートまたは最終的なものにしてください
  • super()の呼び出しには明示的にしてください
    • 明示的に記述しなくてもsuper()が呼び出されることに気付いていないJavaプログラマーの数に驚かれることでしょう(this(...)への呼び出しがないと仮定した場合)
  • コンストラクターの初期化ルールの順序を理解します。それは基本的に:

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

全体的なフローは次のようになります。

  • スーパークラス階層をオブジェクトまでずっと上に移動します
  • 行われていない間
    • initフィールド
    • コンストラクター本体を実行する
    • サブクラスにドロップダウン

悪の良い例として、以下が何を印刷するかを理解してから実行してみてください

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

上記が機能する理由を説明するコメントを追加します...それのいくつかは明白かもしれません。一部はそうではありません...

于 2009-02-24T15:56:55.527 に答える
3

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

public Book(String title)
{
    if (title==null)
        throw new IllegalArgumentException("title can't be null");
    this.title = title;
}
于 2009-02-24T15:32:42.917 に答える
0

私は次のことをします:

公開授業 本
{
    プライベート最終文字列タイトル。
    プライベート最終文字列 isbn;

    public Book(最終文字列 t, 最終文字列 i)
    {
        if(t == null)
        {
            throw new IllegalArgumentException("t を null にすることはできません");
        }

        if(i == null)
        {
            throw new IllegalArgumentException("i を null にすることはできません");
        }

        タイトル = t;
        isbn = 私;
    }
}

ここでは、次のことを前提としています。

1) タイトルは決して変更されません (したがって、タイトルは最終的なものです) 2) ISBN は決して変更されません (したがって、ISBN は最終的なものです) 3) タイトルと ISBN の両方がない本を持つことは有効ではありません。

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

公開クラス 学生
{
    プライベート最終学生 ID;
    プライベート文字列 firstName;
    プライベート文字列 lastName;

    public Student(最終StudentID i,
                   最終的な文字列が最初に、
                   最終文字列最後)
    {
        if(i == null)
        {
            throw new IllegalArgumentException("i を null にすることはできません");
        }

        if(first == null)
        {
            throw new IllegalArgumentException("first を null にすることはできません");
        }

        if(最後 == null)
        {
            throw new IllegalArgumentException("最後を null にすることはできません");
        }

        ID = 私;
        firstName = 最初;
        lastName = 最後;
    }
}

そこで、ID、名、姓を使用して Student を作成する必要があります。学生 ID は変更できませんが、姓名は変更できます (結婚、賭けに負けて名前が変わるなど)。

どのコンストラクターを使用するかを決定するときは、何を使用するのが理にかなっているのかを本当に考える必要があります。多くの場合、人々は教えられているために set/get メソッドを追加しますが、多くの場合、それは悪い考えです。

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

于 2009-02-24T17:51:49.230 に答える
0

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

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

于 2009-02-24T17:53:50.957 に答える