web-dev-qa-db-ja.com

静的な内部クラスよりも静的でない内部クラスを好むのはなぜですか?

この質問は、Javaでネストされたクラスを静的なネストされたクラスにするか、内部のネストされたクラスにするかです。こことスタックオーバーフローで検索しましたが、実際には質問が見つかりませんでしたこの決定の設計への影響について。

私が見つけた質問は、静的クラスと内部入れ子クラスの違いについて尋ねています。これは私には明らかです。しかし、私はまだJava-で静的ネストされたクラスを使用する説得力のある理由を見つけていません。匿名クラスを除いて、この質問では考慮していません。

静的なネストされたクラスを使用する効果についての私の理解は次のとおりです。

  • 結合が少ない:クラスはその外部クラスの属性に直接アクセスできないため、通常は結合が少なくなります。結合が少ないということは、一般に、コードの品質が向上し、テスト、リファクタリングが容易になることなどを意味します。
  • Single Classクラスローダーは、毎回新しいクラスを処理する必要はありません。外部クラスのオブジェクトを作成します。同じクラスの新しいオブジェクトを繰り返し取得するだけです。

内部クラスの場合、私は一般的に人々が外部クラスの属性へのアクセスをプロと見なしていることに気づきます。この点については、設計の観点からは違います。この直接アクセスは結合が高いことを意味し、ネストされたクラスを別の最上位クラスに抽出したい場合は、本質的に回した後にのみ可能です。それを静的なネストされたクラスに入れます。

だから私の質問はこれに帰着します:非静的内部クラスに利用可能な属性アクセスが高い結合をもたらし、したがってコード品質を低下させると私が間違っていると私はこれから(非匿名)ネストされたクラスが一般的に静的ですか?

または言い換えると、ネストされた内部クラスを好む説得力のある理由はありますか?

39
Frank

Joshua Blochは、彼の著書「Effective Java Second Edition "」の22項で、どの種類のネストされたクラスをいつ使用するのか、およびその理由を説明しています。

静的メンバークラスの一般的な用途の1つは、パブリックヘルパークラスとしての使用で、その外部クラスと組み合わせた場合にのみ役立ちます。たとえば、電卓でサポートされている操作を説明する列挙型について考えます。 Operation列挙型は、Calculatorクラスのパブリック静的メンバークラスである必要があります。その後、Calculatorのクライアントは、Calculator.Operation.PLUSCalculator.Operation.MINUSなどの名前を使用して操作を参照できます。

非静的メンバークラスの一般的な用途の1つは、外部クラスのインスタンスを無関係なクラスのインスタンスとして表示できるようにするAdapterを定義することです。たとえば、Mapインターフェースの実装は通常、非静的メンバークラスを使用してコレクションビューを実装します。これは、MapkeySetentrySetvaluesメソッドによって返されます。同様に、SetListなどのコレクションインターフェイスの実装では、通常、非静的メンバークラスを使用してイテレーターを実装します。

// Typical use of a nonstatic member class
public class MySet<E> extends AbstractSet<E> {
    ... // Bulk of the class omitted

    public Iterator<E> iterator() {
        return new MyIterator();
    }

    private class MyIterator implements Iterator<E> {
        ...
    }
}

囲んでいるインスタンスへのアクセスを必要としないメンバークラスを宣言する場合、alwaysstatic修飾子を宣言に入れて、非静的メンバークラスではなく静的にします。

26
erakitin

非静的な内部クラスで利用可能な属性アクセスが高い結合につながるため、コード品質が低下し、(非匿名で 非ローカル )内部クラスは一般に静的である必要があると想定するのは正しい。

内部クラスを非静的にする決定の設計上の意味は、 Java PuzzlersPuzzle 90(引用の下の太字のフォントは私のものです):

メンバークラスを作成するときはいつでも、このクラスは本当に包含インスタンスを必要としますか?答えが「いいえ」の場合は、staticにしてください。内部クラスは便利な場合もありますが、プログラムを理解しにくくする複雑な問題を簡単に引き起こす可能性があります。ジェネリック(パズル89)、リフレクション(パズル80)、継承(このパズル)との複雑な相互作用があります。 Inner1staticと宣言すると、問題はなくなります。 Inner2staticと宣言すると、プログラムの機能を実際に理解できます。

要約すると、あるクラスが別のクラスの内部クラスとサブクラスの両方になることはほとんどありません。より一般的には、内部クラスを拡張することはほとんど適切ではありません。必要な場合は、囲んでいるインスタンスについてじっくりと考えてください。また、staticのネストされたクラスは、非staticよりも優先されます。ほとんどのメンバークラスはstaticとして宣言でき、宣言する必要があります。

興味がある場合は、Puzzle 90のより詳細な分析が Stack Overflowでのこの回答 に記載されています。


上記は基本的に JavaClasses and Objectstutorial で提供されるガイダンスの拡張バージョンであることは注目に値します:

囲んでいるインスタンスの非パブリックフィールドおよびメソッドへのアクセスが必要な場合は、非静的ネストクラス(または内部クラス)を使用します。このアクセスが必要ない場合は、静的ネストクラスを使用してください。

したがって、あなたが尋ねた質問への答えつまり、チュートリアルごとのは、非静的を使用する唯一の納得の理由です。囲んでいるインスタンスの非パブリックフィールドおよびメソッドへのアクセスがrequiredの場合。

チュートリアルの表現はやや広い(これがJavaパズルを強化して狭める試みをする理由である可能性がある)。特に、囲んでいるインスタンスフィールドに直接アクセスすることは、実際には必須私の経験では-これらをコンストラクタ/メソッドパラメータとして渡すなどの代替方法は、常にデバッグと保守が容易になったという意味で。


全体として、囲んでいるインスタンスのフィールドに直接アクセスする内部クラスのデバッグでの(かなり痛い)遭遇は、このプラクティスがそれに関連付けられた known evils とともにグローバル状態の使用に似ているという強い印象を与えました。

もちろん、Javaは、そのような「準グローバル」の損傷が包含クラス内に含まれるようにしますが、特定の内部クラスをデバッグしなければならなかったとき、そのようなバンドエイドはそうしなかったように感じました痛みを軽減するのに役立つ:特定の問題のあるオブジェクトの分析に完全に集中するのではなく、「外来」のセマンティクスと詳細を覚えておく必要がありました。


完全を期すために、上記の推論が当てはまらない 場合によっては があります。たとえば、 map.keySet javadocs を読んだところ、この機能は密結合を示唆し、その結果、静的でないクラスに対する引数を無効にします。

このマップに含まれるキーのSetビューを返します。セットはマップによってサポートされているため、マップへの変更はセットに反映され、その逆も同様です...

上記のことで、関係するコードの保守、テスト、およびデバッグが容易になることはありませんが、少なくとも、複雑化が意図した機能によって一致/正当化されると主張することはできます。

10
gnat

いくつかの誤解:

より少ない結合:[静的内部]クラスはその外部クラスの属性に直接アクセスできないため、通常は結合が少なくなります。

IIRC-実際、それは可能です。 (もちろん、それらがオブジェクトフィールドである場合、外部オブジェクトはありません。それでも、そのようなオブジェクトをどこから取得しても、それらのフィールドに直接アクセスできます)。

単一クラス:クラスローダーは、毎回新しいクラスを処理する必要はありません。外部クラスのオブジェクトを作成します。同じクラスの新しいオブジェクトを繰り返し取得するだけです。

ここの意味がよくわかりません。クラスローダーは、各クラス(クラスが読み込まれるとき)に1回ずつ、合計2回のみ関与します。


とりとめは次のとおりです。

ジャワランドでは、クラスを作成するのが少し怖いようです。それは重要なコンセプトのように感じられるべきだと思います。通常は、独自のファイルに属しています。重要なボイラープレートが必要です。デザインに注意が必要です。

他の言語との比較-たとえば、Scala、またはC++の場合:2ビットのデータが一緒になるという小さなホルダー(クラス、構造体など)を作成するドラマは絶対にありません。柔軟なアクセスルールにより、ボイラープレートを削減し、関連するコードを物理的に近くに保つことができます。これにより、2つのクラス間の「心の距離」が短くなります。

内部クラスをプライベートに保つことで、直接アクセスに対する設計上の懸念を払拭することができます。

(...「なぜ静的でないpublic内部クラスなのか?」と尋ねた場合、それは非常に良い質問です-私はそれらの正当なケースをまだ見つけたことはないと思います) 。

コードを読みやすくし、DRY違反やボイラープレートを回避するのに役立ちます。いつでも使用できます。私の経験ではそれほど頻繁に発生するわけではないので、準備ができた例はありませんが、起こります。


静的内部クラスはdefaultのアプローチとしては良いです、ところで。非静的には、内部クラスが外部を参照するために生成される追加の非表示フィールドがあります。

1
Iain

ファクトリパターンを作成するために使用できます。ファクトリパターンは、作成されたオブジェクトが機能するために追加のコンストラクタパラメータを必要とする場合によく使用されますが、毎回提供するのは面倒です。

考慮してください:

public class QueryFactory {
    @Inject private Database database;

    public Query create(String sql) {
        return new Query(database, sql);
    }
}

public class Query {
    private final Database database;
    private final String sql;

    public Query(Database database, String sql) {
        this.database = database;
        this.sql = sql;
    } 

    public List performQuery() {
        return database.query(sql);
    }
}

使用されます:

Query q = queryFactory.create("SELECT * FROM employees");

q.performQuery();

同じことが非静的内部クラスでも実現できます。

public class QueryFactory {
    @Inject private Database database;

    public class Query {
        private final String sql;

        public Query(String sql) {
            this.sql = sql;
        }

        public List performQuery() {
            return database.query(sql);
        }
    }
}

使用:

Query q = queryFactory.new Query("SELECT * FROM employees");

q.performQuery();

ファクトリは、createcreateFromSQLcreateFromTemplateなどの特定の名前のメソッドを使用することでメリットを得ます。ここでも、複数の内部クラスの形式で同じことができます。

public class QueryFactory {

    public class SQL { ... }
    public class Native { ... }
    public class Builder { ... }

}

ファクトリーから構築されたクラスへのパラメーターの受け渡しは少なくて済みますが、読みやすさが少し損なわれる可能性があります。

比較:

Queries.Native q = queries.new Native("select * from employees");

Query q = queryFactory.createNative("select * from employees");
0
john16384