Effective-java 3中国語翻訳シリーズ(Item 20インタフェースは抽象クラスより優先)

6683 ワード

テキストリンク
文章も伝わる
github
△注目を集めて、大神さんを歓迎します.
ITEM 20インタフェースは抽象クラスより優先
Javaには、interfaceクラスとabstractクラスの2つのメカニズムがあります.Java 8でインタフェースに言及してもデフォルトメソッドを書くことができるので、この2つのメカニズムでは、いくつかのインスタンスメソッドの実装を提供することができます.この2つのメカニズムの最も主要な違いは、1つの抽象クラスを通じて1つのクラスを実現することであり、このクラスはこの抽象クラスのサブクラスでなければならない.Javaプラットフォームでは単一継承のみが許可されているため,抽象クラスのタイプとしての使用はこの制約によって厳しく制限されている.しかし、通常のクラス(必要なメソッドと一般的なクラス定義ルールに従うクラスを定義する)では、どのクラスレベルでもインタフェースを実装できます.
既存のクラスは、インタフェースを実装することによって、その能力を容易に更新することができる.インタフェースを実装する方法だけで、クラス宣言の場所にimplementsインタフェースの文を追加する必要があります.たとえば、既存のクラスの多くは、Comparable、Iterable、Autocloseableインタフェースを実装することで、対応する機能を追加しています.一般的に既存のクラスは、新しい抽象クラスを継承することによって機能を更新することはできません.2つのクラスを同じ抽象クラスから継承するには、抽象クラスのレベルを調整して、この2つのクラスよりも1つ上のクラスにする必要があります.しかし、これはクラスレベルに危害を及ぼす可能性があります.これは、適切かどうかにかかわらず、すべてのクラスをこの新しい抽象クラスのサブクラスに強制的に設定する必要があるからです.
インタフェースはmixinsタイプを定義するのに理想的な選択です.厳密には、minxinタイプとは、クラスがその主要な機能に加えて他のオプションの動作を追加できることを意味する.たとえば、Comparableでは、クラスが独自のクラスオブジェクトとcomparableインタフェースに準拠する他のオブジェクトを使用してソートできるようにします.このようなインタフェースは、オプションの機能が「mixed in」から主要な機能にソートできるため、mixinタイプと呼ばれます.抽象クラスは、既存のクラスに追加できないため、mixinsを定義するために使用できません.1つのクラスに複数の親が存在することはできません.また、クラスレベルにも適切な場所がありません.
インタフェースを使用すると、非階層構造のフレームワークを作成できます.階層構造はいくつかのことに対して良いが、厳格な階層化構造は他のものを加えるのに不利である.たとえば、歌手を表すインタフェースと、作曲家を表すインタフェースがあるとします.
public interface Singer {
  AudioClip sing(Song s);
}
public interface Songwriter {
  Song compose(int chartPosition);
}

現実には、一部の歌手も作曲家だ.抽象クラスではなくinterfaceを使用しているので,SingerとSongwriterの2つのインタフェースを1つのクラスで同時に実現することは容易である.実際、3番目のインタフェースをSingerとSongwriterの2つのインタフェースから同時に継承し、この組み合わせに適切な新しい方法を追加することができます.
public interface SingerSongwriter extends Singer, Songwriter {
  AudioClip strum();
  void actSensitive();
}

この柔軟性は必要ありませんが、必要に応じてインタフェースを提供することができます.抽象クラスを使うと,上記の組合せを実現しようとするとクラスの階層が肥大化する.n種のタイプがある場合、2のn次方種の組合せクラスが現れる必要がある可能性があり、これを組合せ爆発と呼ぶ.この膨大なクラス構造は、これらのクラス階層に共通の動作をキャプチャできるクラスがないため、これらのクラスにはパラメータの異なる方法が多く存在する.使用(Item 18)のパッケージクラスでは、インタフェースがより安全で有利な機能を提供します.抽象クラス定義タイプを使用する場合、プログラム猿は継承して書き換えるか、新しい機能を追加するしかありません.これはパッケージクラスよりも制限が多く、提供可能な機能が少なくなります.インタフェースが他のインタフェースのためにデフォルトの方法を実現する場合、使用者にヘルプドキュメントを提供することを考慮します.Item 19の@implSpecドキュメントラベルを使用します.インタフェースでデフォルトのメソッドを使用するには、次の制限があります.
  • 多くのインタフェースがobjectの方法、例えばequalsおよびhashCodeを使用する必要がある場合でも、これらのデフォルトの方法を自分で実現することは許されません.
  • インタフェースでは、インスタンス属性と非共通の静的メンバー(プライベート静的メソッドを除く)を含めることはできません.
  • は、制御されていないインタフェースにデフォルトのメソッドを追加することはできません.

  • しかし、インタフェースと抽象クラスの利点を組み合わせて、インタフェースを使用して抽象的なスケルトン実装クラスを実現することができます.ここでインタフェース定義タイプは、スケルトン実装クラスが元のインタフェースメソッドの上に元のインタフェースメソッドを保持するデフォルトメソッドも実装される可能性があります.拡張スケルトンの実装は,実装インタフェースから多くの作業を剥離した.これをテンプレートメソッドモードと呼ぶ.
    従来、スケルトン実装クラスはAbstract+インタフェース名と呼ばれ、ここでは実装するインタフェースである.例えば、Collectionsフレームワークは、AbstractCollection、AbstractSet、AbstractList、AbstractMapといった主要なcollectionsインタフェースごとに行う.それらの命名にはSkeletalCollection、SkeletalSet、SkeletalList、SkeletalMapと呼ぶべきかもしれないが、Abstractの習慣は根強い.適切に設計された場合、スケルトンインプリメンテーション(個別の抽象クラスとインタフェースのデフォルトメソッドの組合せにかかわらず)は、プログラマが独自のインタフェースインプリメンテーションを容易に提供することができる.例えば、AbstractListの上に位置する静的ファクトリメソッドがあり、完全で機能的なListインプリメンテーションを含む.
    static List intArrayAsList(int[] a) {
            Objects.requireNonNull(a);
            //        JAVA9    ,             
            return new AbstractList<>() {
                @Override public Integer get(int i) {
                    return a[i]; // Autoboxing (Item 6)
                }
                @Override public Integer set(int i, Integer val) {
                    int oldVal = a[i];
                    a[i] = val; // Auto-unboxing
                    return oldVal; // Autoboxing
                }
                @Override public int size() {
                    return a.length;
                }
            };
        }
    

    リストで何かを実現したい場合、この例はスケルトン実装クラスの能力をよく体現しています.ちなみに、この例はint配列をIntegerリストとして使用できるAdapterです.intとIntegerを往復変換する必要はありません(変換の性能が高くないため).この実装は匿名クラスの形式であることに注意してください.(Item 24).スケルトンインプリメンテーションクラスは、抽象クラスをタイプとして使用する場合の制約を受けずに抽象クラスにインプリメンテーションを提供します.スケルトンインプリメンテーションクラスをインプリメンテーションするインタフェースは、そのスケルトンインプリメンテーションクラスをインプリメンテーションするインタフェースが継承される場合が多いですが、これもオプションです.1つのクラスがスケルトンインプリメンテーションクラスを継承できない場合は、直接インタフェースをインプリメンテーションできます.このクラスは、インタフェースのデフォルトのメソッドから継続できますいくつかの実装を継承します.また、スケルトン実装クラスは、呼び出し者が作業を完了するのに役立ちます.インプリメンテーションインタフェースのクラスは、スケルトンインプリメンテーションクラスのサブクラスオブジェクトである可能性のあるクラス内部のプライベートオブジェクトにインタフェースメソッドのインプリメンテーションを転送することができる.この技術はシミュレーション多重継承と呼ばれ,この最近の議論はItem 18にある.多重継承の利点が多く、多くの欠陥を回避しています.
    スケルトン実装クラスを書くのは比較的簡単ですが、プロセスは少し退屈です.
  • まず,インタフェースを研究し,どの方法が最も基礎的で,骨格として書くことができる抽象的な方法を決定し,他の人に実現されるかを決定する.
  • は、次に、元の方法の上で直接実現可能な方法にデフォルトの実装を提供する.しかし、equalsメソッドやhashCodeメソッドなど、Objectのメソッドにデフォルトの実装を提供することはできません.

  • 元のメソッドとデフォルトのメソッドをすべて実行している場合は、スケルトン実装クラスを実装する必要はありません.そうでなければ、インタフェースを実装するクラスを書き、すべてのインタフェースに残っている方法を実装します.このクラスには共通のパラメータとメソッドが含まれていない可能性があります.一例を参考にして、Map.Entryインタフェース.明らかにgetKey,getValue,および(オプション)setValue法は元の方法であり,このインタフェースにはequalsとhashCodeの挙動があり,toStringの実現がある.Objectのメソッドにデフォルトのインプリメンテーションを追加することは許されないため、すべてのインプリメンテーションはスケルトンインプリメンテーションクラスに置き換えられます.
        // Skeletal implementation class
        public abstract class AbstractMapEntry
                implements Map.Entry {
            // Entries in a modifiable map must override this method
            @Override public V setValue(V value) {
                throw new UnsupportedOperationException();
            }
            // Implements the general contract of Map.Entry.equals
            @Override public boolean equals(Object o) {
                if (o == this)
                    return true;
                if (!(o instanceof Map.Entry))
                    return false;
                Map.Entry,?> e = (Map.Entry) o;
                return Objects.equals(e.getKey(), getKey())
                        && Objects.equals(e.getValue(), getValue());
            }
            // Implements the general contract of Map.Entry.hashCode
            @Override public int hashCode() {
                return Objects.hashCode(getKey())
                        ^ Objects.hashCode(getValue());
            }
            @Override public String toString() {
                return getKey() + "=" + getValue();
            }
        }
    

    このスケルトン実装はMapでは実現できないことに注意する.Entryインタフェースの内部も、Mapにはなりません.Entryのサブインタフェースは、equals、hashCode、toStringなどのObjectを上書きするデフォルトの方法が許可されていないためです.
    スケルトン実装は継承のために設計されているので、Item 19で述べたドキュメントと設計の原則に従います.簡単にするために、前の例ではドキュメントは省略されていますが、良いドキュメントはスケルトン実装において絶対に必要です.
    AbstractMapのような単純な実装がある.SimpleEntry.インタフェースも実装され、継承のために設計されていますが、抽象的ではなく、単独で使用することができます.
    まとめると、インタフェースは、さまざまな実装を可能にする定義タイプの最良の方法です.重要なインタフェースをエクスポートする場合は、スケルトン実装を提供する必要があります.可能な範囲では、このインタフェースを実装するすべてのクラスが呼び出されるように、できるだけデフォルトのインタフェースメソッドを提供することで実装する必要があります.すなわち、インタフェースの制限は、通常、スケルトン実装に抽象クラスの形式を採用することを要求する.