JavaRush /Java Blog /Random-JA /Javaのポリモーフィズム

Javaのポリモーフィズム

Random-JA グループに公開済み
OOP に関する質問は、IT 企業における Java 開発者のポジションを決定する技術面接で不可欠な部分です。この記事では、OOP の原則の 1 つであるポリモーフィズムについて説明します。面接でよく聞かれる点に焦点を当て、わかりやすくするために小さな例も示します。

ポリモーフィズムとは何ですか?

ポリモーフィズムとは、オブジェクトの特定の型に関する情報がなくても、同じインターフェイスを持つオブジェクトを同じように使用できるプログラムの機能です。ポリモーフィズムとは何かという質問にこのように答えると、おそらく何を意味するのか説明を求められるでしょう。もう一度言いますが、追加の質問をたくさんすることなく、面接官のためにすべてを整理してください。

面接での Java のポリモーフィズム - 1
OOP アプローチには、クラスに基づくオブジェクトの相互作用に基づいて Java プログラムを構築することが含まれるという事実から始めることができます。クラスは、プログラム内のどのオブジェクトが作成されるかに応じて、事前に作成された図面 (テンプレート) です。さらに、クラスには常に特定の型があり、適切なプログラミング スタイルを使用すると、その名前によって目的が「わかります」。さらに、Java は厳密に型指定された言語であるため、プログラム コードは変数を宣言するときに常にオブジェクトの型を示す必要があることに注意してください。これに加えて、厳密な型指定によりコードの安全性とプログラムの信頼性が向上し、コンパイル段階で型の非互換性エラー (文字列を数値で割ろうとするなど) を防ぐことができます。当然のことながら、コンパイラは宣言された型を「知っている」必要があります。これは、JDK のクラスでも、自分で作成したクラスでも構いません。インタビュアーに注意していただきたいのは、プログラム コードを扱うときは、宣言時に割り当てた型のオブジェクトだけでなく、その子孫も使用できるということです。これは重要な点です。 (それらの型が基本型から派生している限り) 多くの型を 1 つであるかのように扱うことができます。これは、スーパークラス型の変数を宣言したら、その変数にその子孫の 1 つの値を割り当てることができることも意味します。例を挙げると面接官は喜ぶでしょう。オブジェクトのグループに共通 (ベース) となるオブジェクトを選択し、そこからいくつかのクラスを継承します。基本クラス:
public class Dancer {
    private String name;
    private int age;

    public Dancer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void dance() {
        System.out.println(toString() + "I dance like everyone else.");
    }

    @Override
    public String toString() {
        return "Я " + name + ", to me " + age + " years. " ;
    }
}
子孫で、基本クラスのメソッドをオーバーライドします。
public class ElectricBoogieDancer extends Dancer {
    public ElectricBoogieDancer(String name, int age) {
        super(name, age);
    }
// overriding the base class method
    @Override
    public void dance() {
        System.out.println( toString() + "I dance electric boogie!");
    }
}

public class BreakDankDancer extends Dancer{

    public BreakDankDancer(String name, int age) {
        super(name, age);
    }
// overriding the base class method
    @Override
    public void dance(){
        System.out.println(toString() + "I'm breakdancing!");
    }
}
Java におけるポリモーフィズムとプログラム内でのオブジェクトの使用の例:
public class Main {

    public static void main(String[] args) {
        Dancer dancer = new Dancer("Anton", 18);

        Dancer breakDanceDancer = new BreakDankDancer("Alexei", 19);// upcast to base type
        Dancer electricBoogieDancer = new ElectricBoogieDancer("Igor", 20); // upcast to base type

        List<Dancer> discotheque = Arrays.asList(dancer, breakDanceDancer, electricBoogieDancer);
        for (Dancer d : discotheque) {
            d.dance();// polymorphic method call
        }
    }
}
メソッド コードに次の行の内容をmain示します 。
Dancer breakDanceDancer = new BreakDankDancer("Alexei", 19);
Dancer electricBoogieDancer = new ElectricBoogieDancer("Igor", 20);
スーパークラス型変数を宣言し、それに子孫の 1 つの値を割り当てました。Java には厳密な型付けがあるため、代入記号の左側と右側で宣言された型の不一致についてコンパイラがなぜ不一致を指摘しないのかを尋ねられるでしょう。ここでは上向きの型変換が機能することを説明します。オブジェクトへの参照は基本クラスへの参照として解釈されます。さらに、コンパイラは、コード内でそのような構造に遭遇すると、これを自動的かつ暗黙的に実行します。コード例に基づいて、代入記号の左側で宣言されたクラス型にはDancer、右側で宣言されたいくつかの形式 (型) があることがわかりますBreakDankDancerElectricBoogieDancer各フォームは、スーパークラス メソッドで定義された共通機能に対して独自の固有の動作を持つことができますdance。つまり、スーパークラスで宣言されたメソッドは、その子孫では別の方法で実装される可能性があります。この場合、メソッドのオーバーライドを扱っており、これがまさにさまざまな形式 (動作) を作成するものです。これは、メイン メソッド コードを実行して実行すると確認できます。 プログラムの出力 私はアントン、18 歳です。私は他のみんなと同じように踊ります。私はアレクセイ、19歳です。私はブレイクダンスです!私はイゴール、20歳です。エレクトリックブギを踊ります! 子孫でオーバーライドを使用しない場合、異なる動作は得られません。たとえば、クラスのメソッドをコメントアウトするBreakDankDancerと、プログラムの出力は次のようになります: 私はアントン、18 歳です。私は他のみんなと同じように踊ります。私はアレクセイ、19歳です。私は他のみんなと同じように踊ります。私はイゴール、20歳です。私は他のみんなと同じように踊ります。これは、新しいクラス を作成することにまったく意味がないことを意味します。 Java ポリモーフィズムの原理とは正確には何でしょうか? 特定の型を知らずにプログラム内でオブジェクトを使用することはどこに隠されているのでしょうか? この例では、これはタイプのオブジェクトに対するメソッド呼び出しです。Java ポリモーフィズムは、プログラムがオブジェクトまたはオブジェクトがどのような型になるかを知る必要がないことを意味します。重要なことは、それが クラス の子孫であるということです。また、子孫について話す場合、Java の継承は だけでなく もあることに注意する必要があります。Java は多重継承をサポートしていないことを思い出してください。各型は 1 つの親 (スーパークラス) と無制限の数の子孫 (サブクラス) を持つことができます。したがって、インターフェイスはクラスに複数の機能を追加するために使用されます。インターフェイスは、継承に比べてオブジェクトの親への結合を軽減するため、非常に広く使用されています。Java では、インターフェイスは参照型であるため、プログラムは型をインターフェイス型の変数として宣言できます。ここで例を挙げてみましょう。インターフェイスを作成しましょう。 ElectricBoogieDancerdanceBreakDankDancerElectricBoogieDancerd.dance()dDancerBreakDankDancerElectricBoogieDancerDancerextendsimplements
public interface Swim {
    void swim();
}
わかりやすくするために、関連性のない別のオブジェクトを取り上げ、それらのオブジェクトにインターフェイスを実装してみましょう。
public class Human implements Swim {
    private String name;
    private int age;

    public Human(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public void swim() {
        System.out.println(toString()+"I swim with an inflatable ring.");
    }

    @Override
    public String toString() {
        return "Я " + name + ", to me " + age + " years. ";
    }

}

public class Fish implements Swim{
    private String name;

    public Fish(String name) {
        this.name = name;
    }

    @Override
    public void swim() {
        System.out.println("I'm a fish " + name + ". I swim by moving my fins.");

    }

public class UBoat implements Swim {

    private int speed;

    public UBoat(int speed) {
        this.speed = speed;
    }

    @Override
    public void swim() {
        System.out.println("The submarine is sailing, rotating the propellers, at a speed" + speed + " knots.");
    }
}
方法main
public class Main {

    public static void main(String[] args) {
        Swim human = new Human("Anton", 6);
        Swim fish = new Fish("whale");
        Swim boat = new UBoat(25);

        List<Swim> swimmers = Arrays.asList(human, fish, boat);
        for (Swim s : swimmers) {
            s.swim();
        }
    }
}
インターフェイスで定義された多態性メソッドの実行結果から、そのインターフェイスを実装する型の動作の違いを確認できます。それらは、メソッド実行のさまざまな結果で構成されますswim。私たちの例を検討した後、面接官はコードを実行するときにその理由を尋ねるかもしれません。main
for (Swim s : swimmers) {
            s.swim();
}
これらのクラスで定義されたメソッドはオブジェクトに対して呼び出されますか? プログラムを実行するときに、メソッドの目的の実装をどのように選択しますか? これらの質問に答えるには、遅延 (動的) バインディングについて説明する必要があります。バインドとは、メソッド呼び出しとクラス内のその特定の実装との間の接続を確立することを意味します。基本的に、コードは、クラスで定義された 3 つのメソッドのうちどれを実行するかを決定します。Java はデフォルトで遅延バインディング (早期バインディングの場合のようにコンパイル時ではなく実行時) を使用します。これは、コードをコンパイルするときに、
for (Swim s : swimmers) {
            s.swim();
}
コンパイラは、コードがどのクラスのものであるかHumanFishまたはUboatで実行されるかどうかをまだ知りませんswim。これは、動的ディスパッチのメカニズムのおかげで、プログラムが実行されるときにのみ決定されます。プログラムの実行中にオブジェクトのタイプがチェックされ、このタイプに必要なメソッドの実装が選択されます。これがどのように実装されているかと尋ねられたら、オブジェクトをロードして初期化するときに、JVM がメモリ内にテーブルを構築し、その中で変数をその値に、オブジェクトをそのメソッドに関連付けると答えることができます。さらに、オブジェクトが継承されている場合、またはインターフェイスを実装している場合は、そのクラス内にオーバーライドされたメソッドが存在するかどうかが最初にチェックされます。存在する場合は、この型に関連付けられます。存在しない場合は、1 つ上のレベル (親) のクラスで定義されているメソッドが、マルチレベル階層のルートまで検索されます。OOP のポリモーフィズムとそのプログラム コードでの実装について言えば、抽象クラスとインターフェイスを使用して基本クラスを定義するために抽象記述を使用することが良い方法であることに注意してください。この実践は、抽象化の使用に基づいています。つまり、共通の動作とプロパティを分離して抽象クラス内に囲むか、共通の動作のみを分離します。この場合、インターフェイスを作成します。インターフェイスとクラス継承に基づいてオブジェクトの階層を構築および設計することは、OOP ポリモーフィズムの原則を満たすための前提条件です。Java のポリモーフィズムとイノベーションの問題に関して言えば、Java 8 以降、抽象クラスとインターフェイスを作成するときに、キーワードを使用して基底クラスに抽象メソッドのデフォルト実装を記述できるようになりましたdefault。例えば:
public interface Swim {
    default void swim() {
        System.out.println("Just floating");
    }
}
場合によっては、ポリモーフィズムの原則に違反しないように、基本クラスでメソッドを宣言するための要件について尋ねられることがあります。ここではすべてが単純です。これらのメソッドはstaticprivate 、およびFinalであってはなりません。Private は、メソッドをクラス内でのみ使用できるようにし、子孫内でメソッドをオーバーライドすることはできません。静的では、メソッドがオブジェクトではなくクラスのプロパティになるため、スーパークラス メソッドが常に呼び出されます。Final はメソッドを不変にし、継承者から隠蔽します。

ポリモーフィズムは Java に何をもたらしますか?

ポリモーフィズムの使用によって何が得られるのかという疑問もおそらく生じるでしょう。ここでは、あまり深く掘り下げることなく、簡単に答えることができます。
  1. オブジェクトの実装を置き換えることができます。これがテストの基礎となります。
  2. プログラムの拡張性を提供します。将来に向けた基盤の構築がはるかに簡単になります。既存の型に基づいて新しい型を追加することは、OOP スタイルで記述されたプログラムの機能を拡張する最も一般的な方法です。
  3. 共通の型または動作を持つオブジェクトを 1 つのコレクションまたは配列に結合し、それらを均一に管理できます (例のように、全員を踊らせる (メソッド)danceまたは泳ぐ - メソッドswim)。
  4. 新しい型を作成するときの柔軟性: 親からメソッドを実装するか、子でオーバーライドするかを選択できます。

旅の別れの言葉

ポリモーフィズムの原理は非常に重要かつ広範なトピックです。Java の OOPのほぼ半分と、言語の基礎の大部分をカバーしています。面接でこの原則を定義せずにはいられないでしょう。無知または誤解があると、面接が終了する可能性が高くなります。したがって、テスト前に知識を確認し、必要に応じて知識を更新することを怠らないでください。
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION