OOP 関連の質問は、IT 企業の Java 開発者職の技術面接で不可欠な部分です。この記事では、OOP の原理の 1 つであるポリモーフィズムについて説明します。面接でよく聞かれる点に焦点を当て、わかりやすくするためにいくつかの例も示します。
OOP アプローチには、クラスに基づくオブジェクト間の対話に基づいて Java プログラムを構築することが含まれるという事実から始めることができます。クラスは、プログラム内でオブジェクトを作成するために使用される、事前に作成されたブループリント (テンプレート) です。さらに、クラスには常に特定の型があり、適切なプログラミング スタイルを使用すると、その目的を示唆する名前が付けられます。さらに、Java は厳密に型指定されているため、変数を宣言するときにプログラム コードで常にオブジェクト タイプを指定する必要があることに注意してください。これに加えて、厳密な型指定によりコードのセキュリティと信頼性が向上し、コンパイル時であっても型の非互換性によるエラー (文字列を数値で割ろうとするなど) を防ぐことが可能になります。当然のことながら、コンパイラは「知っている」必要があります。宣言された型 – JDK のクラス、または自分で作成したクラスにすることができます。私たちのコードでは、宣言で示された型のオブジェクトだけでなく、その子孫も使用できることを面接官に指摘してください。これは重要な点です。 (これらの型が基本型から派生している場合に限り) 多くの異なる型を 1 つの型として扱うことができます。これは、型がスーパークラスである変数を宣言した場合、その子孫の 1 つのインスタンスをその変数に割り当てることができることも意味します。例を挙げると面接官は喜ぶでしょう。複数のクラス (の基本クラス) で共有できるクラスを選択し、いくつかのクラスにそれを継承させます。基本クラス:
Javaのポリモーフィズムとは何ですか?
ポリモーフィズムとは、オブジェクトの特定の型に関する情報がなくても、同じインターフェイスを持つオブジェクトを同じ方法で処理するプログラムの機能です。ポリモーフィズムとは何かという質問に答えると、おそらく何を意味するのか説明を求められるでしょう。追加の質問をたくさん出さずに、面接官にもう一度すべてを説明します。
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 "I'm " + name + ". I'm " + age + " years old.";
}
}
サブクラスで、基本クラスのメソッドをオーバーライドします。
public class ElectricBoogieDancer extends Dancer {
public ElectricBoogieDancer(String name, int age) {
super(name, age);
}
// Override the method of the base class
@Override
public void dance() {
System.out.println(toString () + " I dance the electric boogie!");
}
}
public class Breakdancer extends Dancer {
public Breakdancer(String name, int age) {
super(name, age);
}
// Override the method of the base class
@Override
public void dance() {
System.out.println(toString() + " I breakdance!");
}
}
ポリモーフィズムの例と、これらのオブジェクトがプログラム内でどのように使用されるか:
public class Main {
public static void main(String[] args) {
Dancer dancer = new Dancer("Fred", 18);
Dancer breakdancer = new Breakdancer("Jay", 19); // Widening conversion to the base type
Dancer electricBoogieDancer = new ElectricBoogieDancer("Marcia", 20); // Widening conversion to the base type
List<dancer> disco = Arrays.asList(dancer, breakdancer, electricBoogieDancer);
for (Dancer d : disco) {
d.dance(); // Call the polymorphic method
}
}
}
mainメソッド で、次の行が表示されることを示します。
Dancer breakdancer = new Breakdancer("Jay", 19);
Dancer electricBoogieDancer = new ElectricBoogieDancer("Marcia", 20);
スーパークラスの変数を宣言し、その子孫の 1 つのインスタンスであるオブジェクトをそれに割り当てます。おそらく、代入演算子の左側と右側で宣言された型の不一致をコンパイラが反転させない理由を尋ねられるでしょう。結局のところ、Java は厳密に型指定されています。ここでは拡張型変換が機能していることを説明します。オブジェクトへの参照は、その基本クラスへの参照のように扱われます。さらに、コード内でそのような構造が見つかると、コンパイラーは自動的かつ暗黙的に変換を実行します。サンプル コードは、代入演算子の左側で宣言された型 ( Dancer ) に複数の形式 (型) があり、それらが右側 ( Breakdancer、 ElectricBoogieDancer ) で宣言されていることを示しています。)。各フォームは、スーパークラス (ダンスメソッド)で定義された一般的な機能に関して、独自の固有の動作を持つことができます。つまり、スーパークラスで宣言されたメソッドは、その子孫では異なる方法で実装される可能性があります。この場合、メソッドのオーバーライドを扱っています。これはまさに複数のフォーム (動作) を作成するものです。これは、main メソッドでコードを実行すると確認できます。 プログラム出力: 私は Fred です。私は18歳です。私は他のみんなと同じように踊ります。私はジェイです。私は19歳です。私はブレイクダンスです!私はマルシアです。私は20歳です。エレクトリックブギを踊ります! サブクラスのメソッドをオーバーライドしない場合、異なる動作は得られません。例えば、ElectricBoogieDancerクラスの場合、プログラムの出力は次のようになります: 私はフレッドです。私は18歳です。私は他のみんなと同じように踊ります。私はジェイです。私は19歳です。私は他のみんなと同じように踊ります。私はマルシアです。私は20歳です。私は他のみんなと同じように踊ります。これは、 Breakdancer クラスとElectricBoogieDancerクラス を作成することは単純に意味がないことを意味します。ポリモーフィズムの原理は具体的にどこに現れるのでしょうか? 特定の型を知らないオブジェクトがプログラムのどこで使用されているのでしょうか? この例では、Dancer dオブジェクトでdance()メソッドが呼び出されたときにこれが発生します。Java では、ポリモーフィズムは、プログラムがオブジェクトがオブジェクトであるかどうかを知る必要がないことを意味します。 ブレイクダンサーまたはエレクトリックブギーダンサー。重要なのは、それがDancerクラスの子孫であるということです。また、子孫について言及する場合、Java の継承はextendsだけでなく、implement も含まれることに注意する必要があります。。ここで、Java は多重継承をサポートしていないことを説明します。各型は 1 つの親 (スーパークラス) と無制限の数の子孫 (サブクラス) を持つことができます。したがって、インターフェイスは、複数の関数セットをクラスに追加するために使用されます。サブクラス (継承) と比較して、インターフェイスは親クラスとあまり結合されていません。それらは非常に広く使用されています。Java では、インターフェイスは参照型であるため、プログラムはインターフェイス型の変数を宣言できます。ここで例を示します。インターフェースを作成します。
public interface CanSwim {
void swim();
}
わかりやすくするために、さまざまな無関係なクラスを取り上げ、それらにインターフェイスを実装させます。
public class Human implements CanSwim {
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 inflated tube.");
}
@Override
public String toString() {
return "I'm " + name + ". I'm " + age + " years old.";
}
}
public class Fish implements CanSwim {
private String name;
public Fish(String name) {
this.name = name;
}
@Override
public void swim() {
System.out.println("I'm a fish. My name is " + name + ". I swim by moving my fins.");
}
public class UBoat implements CanSwim {
private int speed;
public UBoat(int speed) {
this.speed = speed;
}
@Override
public void swim() {
System.out.println("I'm a submarine that swims through the water by rotating screw propellers. My speed is " + speed + " knots.");
}
}
主なメソッド:
public class Main {
public static void main(String[] args) {
CanSwim human = new Human("John", 6);
CanSwim fish = new Fish("Whale");
CanSwim 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();
}
どのクラス ( Human、Fish、Uboat ) に水泳時に実行されるコードがあるかは わかりません。メソッドが呼び出されます。これは、動的バインディング メカニズム (実行時にオブジェクトの型をチェックし、この型に適切な実装を選択する) のおかげで、プログラムの実行時にのみ決定されます。これがどのように実装されているかと尋ねられたら、オブジェクトをロードして初期化するときに、JVM がメモリ内にテーブルを構築し、変数とその値、オブジェクトとそのメソッドをリンクすると答えることができます。その際、クラスが継承されている場合、またはインターフェイスが実装されている場合は、最初にオーバーライドされたメソッドの存在を確認する必要があります。存在する場合、それらはこのタイプにバインドされます。そうでない場合、一致するメソッドの検索は 1 つ上のクラス (親) に移動し、複数レベルの階層のルートまで同様に行われます。OOP のポリモーフィズムとそのコード実装に関して言えば、基本クラスの抽象定義を提供するには、抽象クラスとインターフェイスを使用することをお勧めします。この実践は、共通の動作とプロパティを特定して抽象クラスに入れる、または共通の動作のみを特定してインターフェイスに入れるという抽象化の原則に従っています。ポリモーフィズムを実装するには、インターフェイスとクラスの継承に基づいてオブジェクト階層を設計および作成する必要があります。Java のポリモーフィズムとイノベーションに関しては、Java 8 以降、抽象クラスとインターフェイスを作成するときに、または、一般的な動作のみを特定し、それをインターフェイスに組み込むこともできます。ポリモーフィズムを実装するには、インターフェイスとクラスの継承に基づいてオブジェクト階層を設計および作成する必要があります。Java のポリモーフィズムとイノベーションに関しては、Java 8 以降、抽象クラスとインターフェイスを作成するときに、または、一般的な動作のみを特定し、それをインターフェイスに組み込むこともできます。ポリモーフィズムを実装するには、インターフェイスとクラスの継承に基づいてオブジェクト階層を設計および作成する必要があります。Java のポリモーフィズムとイノベーションに関しては、Java 8 以降、抽象クラスとインターフェイスを作成するときに、基本クラスの抽象メソッドのデフォルト実装を記述するためのdefaultキーワード。例えば:
public interface CanSwim {
default void swim() {
System.out.println("I just swim");
}
}
面接官は、ポリモーフィズムの原則に違反しないように、基底クラスのメソッドをどのように宣言する必要があるかについて質問することがあります。答えは簡単です。これらのメソッドはstatic、private、finalであってはなりません。Private はメソッドをクラス内でのみ使用できるようにするため、サブクラスでメソッドをオーバーライドすることはできません。静的では、メソッドがオブジェクトではなくクラスに関連付けられるため、スーパークラスのメソッドが常に呼び出されます。そして、final はメソッドを不変にし、サブクラスから隠蔽します。
ポリモーフィズムは私たちに何をもたらしますか?
また、ポリモーフィズムがどのようなメリットをもたらすかについても尋ねられるでしょう。複雑な詳細に囚われることなく、これに簡単に答えることができます。- これにより、クラスの実装を置き換えることが可能になります。テストはそれに基づいて構築されます。
- 拡張性が容易になり、将来その上に構築できる基盤の作成がはるかに簡単になります。既存の型に基づいて新しい型を追加することは、OOP プログラムの機能を拡張する最も一般的な方法です。
- これにより、共通の型または動作を共有するオブジェクトを 1 つのコレクションまたは配列に結合し、それらを均一に処理できます (例のように、全員にdance()またはswim()を強制しました:)
- 新しい型を柔軟に作成できる: メソッドの親の実装を選択することも、サブクラスでそれをオーバーライドすることもできます。
いくつかの別れの言葉
ポリモーフィズムは非常に重要かつ広範なトピックです。これは、Java の OOP に関するこの記事のほぼ半分の主題であり、言語の基礎の大部分を形成します。面接ではこの原則を定義することを避けることはできません。知らない、理解できないと面接は終わってしまうでしょう。したがって、怠け者にならないでください。面接前に自分の知識を評価し、必要に応じて知識を更新してください。
さらに読む: |
---|
GO TO FULL VERSION