『Javaプログラミング思想』ノート8.多態

5903 ワード

ブログにアクセス
オブジェクト向けのプログラム設計言語では,マルチステートはデータ抽象(カプセル化)と継承に続く3番目の基本的特徴である.マルチステートは,何をするか,どのようにするかを分離することによって,インタフェースと実装を別の角度から分離する.多状態の役割はタイプ間の結合関係を除去することである.
8.1再論の向上
オブジェクトは、独自のクラスとしても、ベースクラスとしても使用できます.
8.1.1忘れ物の種類
特別なエクスポートクラスではなく、ベースクラスをパラメータとして受け入れる簡単な方法だけを書きます.
public class Test {
    public static void main(String[] args) {
        func(new Unicycle());
        func(new Bicycle());
        func(new Tricycle());
    }

    public static void func(Cycle cycle) {
        cycle.ride();
    }
}

class Cycle {
    void ride() {}
}

class Unicycle extends Cycle {
    void ride() {
        System.out.println("Unicycle");
    }
}

class Bicycle extends Cycle {
    void ride() {
        System.out.println("Bicycle");
    }
}

class Tricycle extends Cycle {
    void ride() {
        System.out.println("Tricycle");
    }
}

8.2乗り継ぎfunc(Cycle cycle)Cycleの参照を受け入れます.では、コンパイラはどのようにしてこのCycleの参照がどの具体的なオブジェクトを指しているかを知ることができますか?実際、コンパイラは知らない.
8.2.1メソッド呼び出しバインディング
  • バインド:1つのメソッド呼び出しが同じメソッドボディに関連付けられていることをバインドと呼ぶ.
  • 前期バインド:プログラム実行前にバインドする(コンパイラと接続プログラムによって実現される)ことを前期バインドと呼ぶ.
  • 後期バインド(ダイナミックバインド、ランタイムバインド):実行時にオブジェクトのタイプに応じてバインドされます.
  • Javaでは、staticメソッドとfinalメソッド(privateメソッドはfinalメソッドに属する)を除いて、他のすべてのメソッドが後期バインドされています.
  • は、finalキーワードを説明するときに、finalキーワードが動的バインドを閉じることができ、前期バインドが必要であるため、実行効率を向上させることができると述べた.

  • 8.2.2正しい行為を生む
    コンパイル時に、コンパイラは特別な情報を得る必要がなく、正しい呼び出しを行うことができます.
    Cycle cycle = new Tricycle();
    cycle.ride();

    8.2.3拡張性
    良好なOOPプログラムでは、ほとんどの方法またはすべての方法がベースクラスのモデルに従い、ベースクラスインタフェースとのみ通信します.このようなプログラムは、汎用ベースクラスから新しいデータ型を継承できるため、拡張可能である.マルチステートはプログラマーに「変化するものと変わらないものを分離させる」重要な技術である.
    8.2.4欠陥:「プライベートメソッドの上書き」
  • 親のプライベートメソッドサブクラスは再ロードできません.すなわち、サブクラスのメソッドは新しいメソッド
  • です.
  • 非privateメソッドのみが
  • を上書きできる
  • 下記のプログラムが呼び出すのは依然として親の対応方法
  • である.
  • 約束:子の中の方法は親の中のprivateの方法と同名ではなく、名前をつけて解決できる問題はそんなに複雑にしないでください.
    public class Test {
        public static void main(String[] args) {
            Test test = new TestDemo();
            test.func();
            // Output: Test
        }
    
        private void func() {
            System.out.println("Test");
        }
    }
    
    class TestDemo extends Test {
        public void func() {
            System.out.println("TestDemo");
        }
    }

    8.2.5欠陥:ドメインと静的方法
  • 通常のメソッドの呼び出しのみがマルチステートの
  • である.
  • サブクラスオブジェクトが親オブジェクトに変換されると、どのドメインアクセス操作もコンパイラによって解析されるため、マルチステートの
  • ではない.
  • ある方法が静的であれば、彼は多態の
  • ではない.
    8.3コンストラクタとマルチステート
    コンストラクタは、staticと暗黙的に宣言されているため、多態性を有しない.
    8.3.1コンストラクタの呼び出し順序
  • ベースクラスのコンストラクタは、常にクラスをエクスポートするコンストラクタ中に呼び出され、継承階層に従ってそのリンクを徐々に考え、各ベースクラスのコンストラクタが呼び出されるようにします.
  • ベースクラスのコンストラクタのみが自分の要素を初期化するために適切な方法と権限を持っているため、すべてのコンストラクタが呼び出されなければ、オブジェクトが正しく構築されません.
  • は、ベースクラスコンストラクタを明確に指定する、すなわちデフォルトコンストラクタ
  • を呼び出す.
    オブジェクト呼び出しコンストラクタ順序
  • ベースクラスコンストラクタ(ルートコンストラクタから)
  • を呼び出す.
  • 宣言順にメンバーを呼び出す初期化方法
  • は、導出クラスのコンストラクタ
  • を呼び出す.
    8.3.2継承と整理
  • メソッドを組み合わせて新しいクラスを作成する場合、オブジェクトのクリーンアップの問題を心配する必要はありません.サブオブジェクトは通常GCに残して処理されます.
  • 確かにクリーンアップの問題に遭遇した場合、クリーンアップ方法ではサブクラスのクリーンアップロジックを先に書き、親クラスのクリーンアップ方法を呼び出す.すなわち、クリーンアップ順序は初期化順序とは逆であるべきである.

  • 8.3.3コンストラクタ内部のマルチステートメソッドの動作
    コンストラクタの内部で構築中のオブジェクトの動的バインドメソッドを呼び出すと、どうなりますか?
    初期化の実際の手順:
  • は、他のことが起こる前に、オブジェクトに割り当てられた記憶領域をバイナリのゼロに初期化する.
  • は、8.3.1のようにベースクラスコンストラクタを呼び出す.ベースクラスコンストラクタではfunc()が呼び出されるため、実際にはカバーされるfunc()メソッドである.
  • は、宣言された順序でメンバーの初期化方法を呼び出す.
  • は、エクスポートクラスのコンストラクタボディを呼び出す.
  • public class Test {
        public static void main(String[] args) {
            new Child(100);
        }
    }
    
    class Child extends Parent {
        private int i;
        void func() {
            System.out.println("Child func, i = " + i);
        }
    
        public Child(int i) {
            this.i = i;
            System.out.println("Before Child constructor, i = " + i);
            func();
            System.out.println("After Child constructor, i = " + i);
        }
    }
    
    class Parent {
        void func() {
            System.out.println("Parent func");
        }
    
        public Parent() {
            System.out.println("Before Parent constructor");
            func();
            System.out.println("After Parent constructor");
        }
    }
    Output:
    Before Parent constructor
    Child func, i = 0
    After Parent constructor
    Before Child constructor, i = 100
    Child func, i = 100
    After Child constructor, i = 100

    コンストラクタガイドラインの作成:
  • は、可能な限り簡単な方法でオブジェクトを正常な状態にし、できれば他の方法の呼び出しを避ける.
  • がコンストラクタで唯一安全に呼び出すことができるのは、これらの方法が上書きされないため、ベースクラスのfinalまたはprivateの方法である.上記のコードでParentfunc()privateにすると、異なる結果が得られます.

  • 8.4コヒーレント戻りタイプ
    子クラスが親クラスを上書き(書き換え)する方法では、親クラスが返すタイプの子クラスを返すことができます.これはJSE 5以降に追加された機能で、以下のようになります.Childfunc()は、親戻りタイプListの子ArrayListを返します.
    class Child extends Parent {
        @Override
        ArrayList func() {
            return null;
        }
    }
    
    class Parent {
        List func() {
            return null;
        }
    }

    8.5継承による設計
    準則:継承表現動作間の差異を用い、フィールドで状態上の変化を表す.
    8.5.1純継承と拡張
    じゅん継承
  • は、ベースクラスで確立された方法のみが、純粋な“is-a”の関係を導出クラスでカバーすることができる.

  • 拡張
  • extendsキーワードの意味から分かるように,ベースクラスに存在しないメソッドを増やすことをベースクラスに拡張したいようであり,これを“”is-like-a“”の関係と呼ぶことができる.
  • のような欠点は、拡張部分がベースクラスにアクセスできないことであり、主にアップグレードされたときである.

  • 8.5.2ダウンシフトとランタイムタイプ識別(RTTI)
  • のアップグレードは、ベースクラスがエクスポートクラスよりも大きなインタフェースを持たないため、安全です.
  • ダウンシフト時にランタイムタイプ識別(Run-Time Type Identification)メカニズムがタイプをチェックし、シフトに失敗した場合、ランタイム異常が放出されます(ClassCastException).
  • RTTIのコンテンツには、トランスフォーム処理だけでなく、オブジェクトタイプも表示されます.