ポリモーフィズムの概念
一般に、それはさまざまな形式ですが、具体的な点は、特定の動作を完了することであり、異なるオブジェクトがそれを完了すると、異なる状態が生成されます。
ポリモーフィック実装条件
次の条件を満たす必要があり、どれも欠けることはできません。
- 相続制度が適用されている必要がある
- サブクラスは親クラスのメソッドをオーバーライドする必要があります
- 親クラスへの参照を介してオーバーライドされたメソッドを呼び出す
// Animal.java
public class Animal {
String name;
int age;
public Animal(String name, int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(name + "吃饭");
}
}
// Cat.java
public class Cat extends Animal{
public Cat(String name, int age){
super(name, age);
}
@Override
public void eat(){
System.out.println(name+"吃鱼~~~");
}
}
// Dog.java
public class Dog extends Animal {
public Dog(String name, int age){
super(name, age);
}
@Override
public void eat(){
System.out.println(name+"吃骨头~~~");
}
}
テストクラス TestAnimal.java を定義する
public class TestAnimal {
public static void eat(Animal animal){
animal.eat();
}
public static void main(String[] args) {
Cat cat = new Cat("小橘",2);
Dog dog = new Dog("小黑", 1);
eat(cat);
eat(dog);
}
}
コンパイラがコードをコンパイルするとき、Dog または Cat のどちらの Eat メソッドを呼び出すかはわかりません。プログラムの実行後、どちらのメソッドを呼び出すかがわかる前に、仮パラメータ Animal によって参照される特定のオブジェクトが決定されます。このとき、eat メソッドを呼び出すアニマル参照は、(アニマル参照のインスタンスに関連して) さまざまな動作をすることがあり、このような動作をポリモーフィズムと呼びます。
注: ここでの仮パラメータの型は、親クラスの型である必要があります。
リライト
オーバーライド: 上書きとも呼ばれます。書き換えとは、親クラスの非静的、非プライベート変更、非最終変更、非コンストラクターメソッドの実装処理をサブクラスが書き換えることであり、戻り値や仮パラメータの変更はできません。つまり、シェルは変更されず、コアが書き換えられます。オーバーライドの利点は、サブクラスが必要に応じて独自の動作を定義できることです。つまり、サブクラスは必要に応じて親クラスのメソッドを実装できます。
【メソッド書き換えのルール】
- サブクラスが親クラスのメソッドをオーバーライドする場合、通常は親クラスのメソッドのプロトタイプと一貫性がなければなりません。戻り値の型のメソッド名 (パラメータ リスト) は完全に一貫していなければなりません。
- オーバーライドされたメソッドの戻り値の型は異なっていてもかまいませんが、親子関係がなければなりません。
- アクセス権限は、親クラスでオーバーライドされたメソッドのアクセス権限より低くすることはできません。例: 親クラスのメソッドが public によって変更された場合、サブクラスでオーバーライドされたメソッドを保護として宣言することはできません。
- 親クラスの static または private によって変更されたメソッドとコンストラクターはオーバーライドできません。
- オーバーライドされたメソッドは @Override アノテーションを使用して明示的に指定でき、このアノテーションは正当性チェックの実行に役立ちます。たとえば、誤ってメソッド名のスペルを間違えた場合 (たとえば、aet と書いた場合)、コンパイラは親クラスに aet メソッドが存在しないことを検出し、コンパイルしてエラーを報告し、メソッドを構成できないことを示します。オーバーライド。
【書き換えとオーバーロードの違い】
違い | オーバーライド | オーバーライド |
---|---|---|
パラメータリスト | 変更してはなりません | 変更する必要があります |
戻り値の型 | 変更してはいけません(親子関係が形成されている場合を除く) | 変更できるかどうか |
アクセス修飾子 | サブカテゴリ ≥ 親カテゴリ | 変更できるかどうか |
メソッドのオーバーロードはクラスの多態性の表現ですが、メソッドのオーバーライドはサブクラスと親クラスの多態性の表現です。
上方変換と下方変換
上向きの変革
上方変換: 実際にサブクラス オブジェクトを作成し、それを親クラス オブジェクトとして使用します。
父类类型 对象名 = new 子类类型();
例えば:
Animal animal = new Cat("小橘",2);
Animal は親クラス型ですが、狭い範囲から広い範囲に変換されるため、サブクラスのオブジェクトを参照できます。
public class TestAnimal {
// 方法传参:形参为父类型引用,可以接收任意子类的对象
public static void eatFood(Animal animal){
animal.eat();
}
// 作返回值:返回任意子类对象
public static Animal buyAnimal(String var){
if("狗".equals(var) ){
return new Dog("dog",1);
}else if("猫" .equals(var)){
return new Cat("cat", 1);
}else{
return null;
}
}
public static void main(String[] args) {
// 直接赋值:子类对象赋值给父类对象
Animal cat = new Cat("小橘",2);
Dog dog = new Dog("小黑", 1);
eatFood(cat);
eatFood(dog);
Animal animal = buyAnimal("狗");
animal.eat();
animal = buyAnimal("猫");
animal.eat();
}
}
上向き変換の利点: コードの実装がよりシンプルかつ柔軟になります。
上方変換の欠点: サブクラスに固有のメソッドを呼び出すことができません。
下方変換
上方変換後にサブクラス オブジェクトを親クラスのメソッドとして使用すると、サブクラスのメソッドを呼び出すことができなくなります。ただし、サブクラス固有のメソッドを呼び出す必要がある場合があります。この場合は、親クラスの参照をサブクラスに復元します。オブジェクト、つまり下方変換です。
public class Animal {
String name;
int age;
public Animal(String name, int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(name + "吃饭");
}
}
// Cat.java
public class Cat extends Animal{
public Cat(String name, int age){
super(name, age);
}
public void mew(){
System.out.println(name+"喵喵叫");
}
@Override
public void eat(){
System.out.println(name+"吃鱼~~~");
}
}
// Dog.java
public class Dog extends Animal {
public Dog(String name, int age){
super(name, age);
}
public void bark(){
System.out.println(name+"汪汪叫");
}
@Override
public void eat(){
System.out.println(name+"吃骨头~~~");
}
}
// TestAnimal.java
public class TestAnimal {
public static void main(String[] args) {
Cat cat = new Cat("小橘",2);
Dog dog = new Dog("小黑", 1);
// 向上转型
Animal animal = cat;
animal.eat();
animal = dog;
animal.eat();
// 向下转型
cat = (Cat)animal;
cat.mew();
// animal本来指向的就是狗,因此将animal还原为狗也是安全的
dog = (Dog)animal;
dog.bark();
}
}
コンストラクター内でオーバーライドされたメソッドの呼び出しを避ける
落とし穴のあるコードですが、B が親クラス、D がサブクラスの 2 つのクラスを作成します。D の func メソッドをオーバーライドします。そして、B のコンストラクターで func を呼び出します。
class B {
public B() {
func();
}
public void func() {
System.out.println("B.func()");
}
}
class D extends B {
private int num = 1;
@Override
public void func() {
System.out.println("D.func() " + num);
}
}
public class Test {
public static void main(String[] args) {
B b = new D(); //执行结果:D.func() 0
}
}
理由: D オブジェクトを構築するときに、B のコンストラクター メソッドが呼び出されます。B のコンストラクターで func メソッドが呼び出されます。このとき、動的バインディングがトリガーされ、D の func が呼び出されます。このとき、D オブジェクト自体はまだ構築されていません。このとき、num は初期化されていない状態であり、値は 0 です。
結論: コンストラクター内でメソッドを呼び出さないようにしてください。このメソッドがサブクラスによってオーバーライドされると、動的バインディングがトリガーされます。ただし、この時点では、サブクラス オブジェクトはまだ構築されておらず、非常に困難な隠れた問題がいくつかあります。見つかる可能性がございます。