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


37

この質問は、Javaでネストされたクラスを静的なネストされたクラスにするか、内部のネストされたクラスにするかです。私はこことStack Overflowを検索しましたが、この決定の設計への影響に関する質問は本当に見つかりませんでした。

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

静的なネストされたクラスを使用した場合の影響についての私の理解は次のとおりです。

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

内部クラスの場合、一般に、人々は外部クラスの属性へのアクセスをプロと考えることがわかります。この直接アクセスは高いカップリングを意味し、ネストされたクラスを別のトップレベルクラスに抽出したい場合は、本質的に向きを変えた後にのみ行うことができるため、デザインの観点からこの点で異なるようにお願いします静的なネストされたクラスに入れます。

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

または言い換えると:ネストされた内部クラスを好む理由は納得できますか?


2
Javaチュートリアルから:「囲むインスタンスの非パブリックフィールドおよびメソッドへのアクセスが必要な場合は、非静的なネストされたクラス(または内部クラス)を使用します。このアクセスが必要ない場合は、静的なネストされたクラスを使用します。」
ブヨ

5
チュートリアルの引用に感謝しますが、インスタンスフィールドにアクセスする理由ではなく、違いが何であるかを繰り返します。
フランク

それはむしろ一般的なガイダンスであり、何を好むかを示唆しています。私はそれをどう解釈するかについて詳細な説明を追加この回答
ブヨ

1
内部クラスが外側のクラスに密結合されていない場合、そもそも内部クラスであってはなりません。
ケビンクルムウィーデ

回答:


23

彼の本「Effective Java Second Edition」の項目22のJoshua Blochは、どの種類のネストされたクラスを使用するのか、そしてその理由を説明しています。以下にいくつか引用符があります。

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

非静的メンバークラスの一般的な使用法の1つは、外部クラスのインスタンスを無関係なクラスのインスタンスとして表示できるようにするアダプターを定義することです。例えば、実装のMapインタフェースは、典型的には、その実装する非静的メンバクラスを使用コレクションビューによって返される、MapkeySetentrySetおよびvalues方法を。同様に、Setand などのコレクションインターフェイスの実装ではList、通常、非静的メンバークラスを使用してイテレータを実装します。

// 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> {
        ...
    }
}

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


1
このアダプターが外部クラスMySetインスタンスのビューをどのように/どのように変更するかはよくわかりません。パスに依存する型の違いのようなものを想像できます。つまりmyOneSet.iterator.getClass() != myOtherSet.iterator.getClass()、Javaでは、クラスがそれぞれ同じになるため、実際にはこれは不可能です。
フランク

@Frankこれは非常に典型的なアダプタークラスです-セットを要素のストリーム(イテレーター)として表示できます。
user253751

@immibisありがとう、イテレータ/アダプタが何をするのかよく知っていますが、この質問はそれがどのように実装されているについてでした。
フランク

@Frank外側のインスタンスのビューをどのように変更するかを言っていました。それはまさにアダプターが行うことです-オブジェクトの表示方法を変更します。この場合、そのオブジェクトは外部インスタンスです。
user253751

10

非静的内部クラスに利用できる属性アクセスは、高い結合をもたらし、したがってコード品質を低下させ、(非匿名および非ローカル)内部クラスは一般に静的であるべきであると仮定するのは正しいです。

内部クラスを非静的にする決定の設計上の意味は、Java Puzzlersで説明されています Puzzle 90(以下の引用の太字フォントは私のものです)に。

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

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

興味がある場合は、パズル90のより詳細な分析がStack Overflowのこの回答で提供されています


上記は基本的に、Java クラスおよびオブジェクトチュートリアルで提供されるガイダンスの拡張バージョンであることに注意してください

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

だから、あなたが尋ねた質問への答え、他の言葉でチュートリアルあたりは、専用で説得力のある理由は非staticを使用するためには、親インスタンスの非パブリックフィールドやメソッドへのアクセスがされたときに必要

チュートリアルの言葉遣いはやや広範です(これが、Java Puzzlersがそれを強化し、絞り込もうとする理由かもしれません)。特に、私の経験では、囲んでいるインスタンスフィールドに直接アクセスすることは本当に必要ではありません-コンストラクタ/メソッドパラメータとしてこれらを渡すような代替方法は常にデバッグと保守が容易になったという意味で。


全体として、囲んでいるインスタンスのフィールドに直接アクセスする内部クラスのデバッグとの私の(非常に苦しい)出会いは、このプラクティスがそれに関連する既知の悪とともに、グローバルステートの使用に似ているという強い印象を与えました。

もちろん、Javaは、そのような「準グローバル」の損傷が包含クラス内に含まれるようにしますが、特定の内部クラスをデバッグする必要があるとき、そのようなバンドエイドは痛みを軽減する助けにはならなかったように感じました。特定の問題のあるオブジェクトの分析に完全に焦点を合わせるのではなく、「外国の」セマンティックと詳細を念頭に置いてください。


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

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

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


この高い結合によって引き起こされる問題についてのあなたの経験を共有しますが、チュートリアルのrequireの使用には問題はありません。あなたは自分がフィールドへのアクセスを本当に必要としなかったと言うので、残念なことに、これは私の質問に完全には対処しません。
フランク

@Frankよくわかるように、チュートリアルガイダンス(Java Puzzlersでサポートされているようです)の私の解釈は、これが必要であることを証明できる場合にのみ非静的クラスを使用し、特に、代替方法が悪い。私の経験では、代替の方法は、常により良い判明ことは注目に値する
GNAT

それがポイントです。それが常により良い場合、なぜ私たちは気にしますか?
フランク

@Frank 別の答えは、これが常にそうではない説得力のある例を提供します。map.keySet javadocsを読んだところ、この機能は密結合を示唆し、その結果、非静的クラスに対する引数を無効にします。「セットはマップによって支えられているため、マップへの変更はセットに反映されます。 ...」
ブヨ

1

誤解:

少ないカップリング:[static inner]クラスはその外側のクラスの属性に直接アクセスできないため、一般的にカップリングは少なくなります。

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

単一クラス:クラスローダーは毎回新しいクラスを処理する必要はなく、外側のクラスのオブジェクトを作成します。同じクラスの新しいオブジェクトを何度も取得するだけです。

ここで何を意味するのかよくわかりません。クラスローダーは、クラスごとに1回(クラスがロードされるとき)合計2回だけ関与します。


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

Javalandでは、クラスを作成するのが少し怖いようです。重要な概念のように感じなければならないと思います。通常、独自のファイルに属します。重要な定型文が必要です。その設計に注意が必要です。

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

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

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

コードを読みやすくし、DRY違反や定型句を回避するのに役立つ場合はいつでも使用できます。私の経験ではそれほど頻繁に起こるわけではないので、準備ができている例はありませんが、実際に起こります。


静的な内部クラスは依然として適切なデフォルトのアプローチです。非静的には、内部クラスが外部を参照するために生成される追加の非表示フィールドがあります。


0

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

考慮してください:

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

工場は次のように、特に名前のメソッドを持つの恩恵を受けるcreatecreateFromSQLまたはcreateFromTemplate。ここでも、複数の内部クラスの形式で同じことができます。

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");
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.