最近では、Java を知っていると言うなら、ポリモーフィズムを知らなければなりません。

coldplay.xixi
リリース: 2020-09-19 09:06:20
転載
2481 人が閲覧しました

最近では、Java を知っていると言うなら、ポリモーフィズムを知らなければなりません。

関連学習の推奨事項: java 基礎チュートリアル

今日、私はいつものように会社を訪問しました。ワークステーションに座ってコンピューターの電源を入れます。「今日もレンガを動かす日です」。考えた後、手際よくIdeaを開いて今日の要件を見てコーディングを開始しました。ねえ、これらのコードは誰が書いたのでしょうか。なぜそれらが私のコードに表示されるのですか。そして、まだ提出を待っているのです。私はこれらのコードを書いたことがないことを覚えているので、興味深くそれらを眺めました:

最近では、Java を知っていると言うなら、ポリモーフィズムを知らなければなりません。
これはポリモーフィックではないでしょうか? 私のコンピューターでテストを書いた人は誰でも不思議に思わずにはいられませんでした。

「これの出力を見てみませんか?」

後ろから音が出ました。出力のことを考えていたので、音の発生源は気にしていませんでした。コードを見た後、

    polygon() before cal()
    square.cal(), border = 2
    polygon() after cal()
    square.square(), border = 4复制代码
ログイン後にコピー

という結論に達しました。少なくとも、あなたは Java 開発エンジニアですよね? 普段はたくさんの仕事をしていますが、それでもいくつかの基本的なスキルは持っています。ちょっと誇らしく感じずにはいられませんでした~

「これが答えですか?あなたも調子が悪そうですね」

また突然声が響いた、今度はもう冷静ではありませんでした、ニマ!私もこの答えを頭の中で考えました、わかりました?誰がそれを見ることができますか?さらに、人々は Awei の 18 スタイルのセットを演奏したくなります。

「あなたは誰ですか?」

彼は疑いと怒りを込めて頭を振りました。なぜ誰もいないのですか?いかなる疑念も許せませんでした。 「シャオカイ、起きて、なぜ勤務時間中に眠ってしまったのですか?」勤務時間中に眠ってしまいましたか?目を開けて周囲を見渡すと、それは夢だったことが分かり、安堵のため息をつきました。周りを見渡すと、目の前に部長が立っているんですが、勤務時間中寝てるんですが、体調不良か何かですか?昨日はたくさんのバグを書きましたが修正せず、今日はめちゃくちゃなものを提出しました。今月のあなたのパフォーマンスは私が望むものではないと思います。あなたのパフォーマンスに基づいて、部門のためにそれについて考え始めなければなりません。

「そうではありません、そうではありません、なぜ眠ってしまったのかわかりません。私の説明を聞いてください!」

この文を言う前に、心の中で感じた 花を持って帰りたい 本物でも偽物でも深夜のバーで 思う存分振って彼のことを忘れてください あなたが一番魅力的です目覚ましベルが鳴りました 立ち上がった背中は少し濡れていて額にはうっすらと汗ばんでいました 携帯を確認すると土曜日の8時半でした それは夢だったことが分かりました! 奇妙なことに、どうしてこんな奇妙な夢を見ることができるのでしょうか?とても怖いです。そこで夢の中でコードの一部を思い出したのですが、私の結果は間違っているでしょうか?記憶に基づいて、コンピュータで再度入力したところ、結果は次のようになります。

/*
    polygon() before cal()
    square.cal(), border = 0
    polygon() after cal()
    square.square(), border = 4
*/复制代码
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

square.cal()、border

の結果は、実際には 2 ではなく 0 です。今ではポリモーフィズムも苦手なのでしょうか?パソコンや携帯電話の前では、正解かどうかわかりません。持っている人も持っていない人も、Xiao Caiと一緒にポリモーフィズムを復習しましょう!

友達の中には、square.cal(), border

の結果が 0 であることだけでなく、なぜ

square.square(), border = ではないのかについて混乱している人もいるかもしれません。 4 まず疑問をアウトプットします。それでは、疑ってみましょう! ポリモーフィズム

オブジェクト指向プログラミング言語では、ポリモーフィズムはデータの抽象化と継承に続く 3 番目の基本機能です。

ポリモーフィズムは、コードの構成と読みやすさを向上させるだけでなく、スケーラブルなプログラムを作成することもできます。ポリモーフィズムの機能は、型間の 結合関係

を排除することです。

1. 上方変換

リヒター置換原理

によると、基本クラスが出現できる場所には必ずサブクラスが出現する可能性があります。

オブジェクトは、独自の型または基本型として使用できます。オブジェクトへの参照をその基本型への参照として扱うこのメソッドは、Upcast

と呼ばれます。親クラスは子クラスの上にあるため、子クラスは親クラスを参照する必要があるため、

上方変換と呼ばれます。

public class Animal {    void eat() {
        System.out.println("Animal eat()");
    }
}class Monkey extends Animal {    void eat() {
        System.out.println(" Monkey eat()");
    }
}class test {    public static void start(Animal animal) {
        animal.eat();
    }    public static void main(String[] args) {
        Monkey monkey = new Monkey();
        start(monkey);
    }
}/* OUTPUT:
Monkey eat()
*/复制代码
ログイン後にコピー
上記の test

クラスの

start() メソッドは Animal への参照を受け取りますが、当然のことながら から受け取ることもできます。 Animal のエクスポートされたクラス。 eat() メソッドを呼び出す場合、Monkey で定義された eat() メソッドが型変換なしで自然に使用されます。 Monkey から Animal にアップグレードすると、インターフェイスの数が減るだけで、インターフェイスの数が Animal よりも減るわけではないためです。 特に適切ではない例え: あなたの父親の財産はあなたに相続され、あなたの財産は依然としてあなたのものです。一般的に、あなたの財産はあなたの父親の財産を下回ることはありません。

最近では、Java を知っていると言うなら、ポリモーフィズムを知らなければなりません。

忘记对象类型

test.start()方法中,定义传入的是 Animal 的引用,但是却传入Monkey,这看起来似乎忘记了Monkey 的对象类型,那么为什么不直接把test类中的方法定义为void start(Monkey monkey),这样看上去难道不会更直观吗。

直观也许是它的优点,但是就会带来其他问题:Animal不止只有一个Monkey的导出类,这个时候来了个pig ,那么是不是就要再定义个方法为void start(Monkey monkey),重载用得挺溜嘛小伙子,但是未免太麻烦了。懒惰才是开发人员的天性。

因此这样就有了多态的产生

2.显露优势

方法调用中分为 静态绑定动态绑定。何为绑定:将一个方法调用同一个方法主体关联起来被称作绑定。

  • 静态绑定:又称为前期绑定。是在程序执行前进行把绑定。我们平时听到"静态"的时候,不难免想到static关键字,被static关键字修饰后的变量成为静态变量,这种变量就是在程序执行前初始化的。前期绑定是面向过程语言中默认的绑定方式,例如 C 语言只有一种方法调用,那就是前期绑定。

引出思考:

public static void start(Animal animal) {
    animal.eat();
}复制代码
ログイン後にコピー

start()方法中传入的是Animal 的对象引用,如果有多个Animal的导出类,那么执行eat()方法的时候如何知道调用哪个方法。如果通过前期绑定那么是无法实现的。因此就有了后期绑定

  • 动态绑定:又称为后期绑定。是在程序运行时根据对象类型进行绑定的,因此又可以称为运行时绑定。而 Java 就是根据它自己的后期绑定机制,以便在运行时能够判断对象的类型,从而调用正确的方法。

小结:

Java 中除了 staticfinal 修饰的方法之外,都是属于后期绑定

合理即正确

显然通过动态绑定来实现多态是合理的。这样子我们在开发接口的时候只需要传入 基类 的引用,从而这些代码对所有 基类 的 导出类 都可以正确的运行。

最近では、Java を知っていると言うなら、ポリモーフィズムを知らなければなりません。

其中MonkeyPigDog皆是Animal的导出类

Animal animal = new Monkey() 看上去不正确的赋值,但是上通过继承,Monkey就是一种Animal,如果我们调用animal.eat()方法,不了解多态的小伙伴常常会误以为调用的是Animaleat()方法,但是最终却是调用了Monkey自己的eat()方法。

Animal作为基类,它的作用就是为导出类建立公用接口。所有从Animal继承出去的导出类都可以有自己独特的实现行为。

可扩展性

有了多态机制,我们可以根据自己的需求对系统添加任意多的新类型,而不需要重载void start(Animal animal)方法。

在一个设计良好的OOP程序中,大多数或者所有方法都会遵循start()方法的模型,只与基类接口同行,这样的程序就是具有可扩展性的,我们可以通过从通用的基类继承出新的数据类型,从而添加一些功能,那些操纵基类接口的方法就不需要任何改动就可以应用于新类。

失灵了?

我们先来复习一下权限修饰符:

##√√##プライベート√ ×##############################################
  • public:所有类可见
  • protected:本类、本包和子类都可见
  • default:本类和本包可见
  • private:本类可见

私有方法带来的失灵

复习完我们再来看一组代码:

public class PrivateScope {    private void f() {
        System.out.println("PrivateScope f()");
    }    public static void main(String[] args) {
        PrivateScope p = new PrivateOverride();
        p.f();
    }
}class PrivateOverride extends PrivateScope {    private void f() {
        System.out.println("PrivateOverride f()");
    }
}/* OUTPUT
 PrivateScope f()
*/复制代码
ログイン後にコピー

是否感到有点奇怪,为什么这个时候调用的f()是基类中定义的,而不像上面所述的那样,通过动态绑定,从而调用导出类PrivateOverride中定义的f()。不知道心细的你是否发现,基类中f()方法的修饰是private。没错,这就是问题所在,PrivateOverride中定义的f()方法是一个全新的方法,因为private的缘故,对子类不可见,自然也不能被重载。

结论

只有非 private 修饰的方法才可以被覆盖

我们通过 Idea 写代码的时候,重写的方法头上可以标注@Override注解,如果不是重写的方法,标注@Override注解就会报错:

最近では、Java を知っていると言うなら、ポリモーフィズムを知らなければなりません。

这样也可以很好的提示我们非重写方法,而是全新的方法。

域带来的失灵

当小伙伴看到这里,就会开始认为所有事物(除private修饰)都可以多态地发生。然而现实却不是这样子的,只有普通的方法调用才可以是多态的。这边是多态的误区所在。

让我们再看看下面这组代码:

class Super {    public int field = 0;    public int getField() {        return field;
    }
}class Son extends Super {    public int field = 1;    public int getField() {        return field;
    }    public int getSuperField() {        return super.field;
    }
}class FieldTest {    public static void main(String[] args) {
        Super sup = new Son();
        System.out.println("sup.field:" + sup.field + " sup.getField():" + sup.getField());

        Son son = new Son();
        System.out.println("son.field:" + son.field + " son.getField:" + son.getField() + " son.getSupField:" + son.getSuperField());
    }
}/* OUTPUT
sup.field:0 sup.getField():1
son.field:1 son.getField:1 son.getSupField:0
*/复制代码
ログイン後にコピー

从上面代码中我们看到sup.field输出的值不是 Son 对象中所定义的,而是Super本身定义的。这与我们认识的多态有点冲突。

最近では、Java を知っていると言うなら、ポリモーフィズムを知らなければなりません。

其实不然,当Super对象转型为Son引用时,任何域访问操作都将由编译器解析,因此不是多态的。在本例中,为Super.fieldSon.field分配了不同的存储空间,而Son类是从Super类导出的,因此,Son实际上是包含两个称为field的域:它自己的+Super

虽然这种问题看上去很令人头痛,但是我们开发规范中,通常会将所有的域都设置为 private,这样就不能直接访问它们,只能通过调用方法来访问。

static 带来的失灵

看到这里,小伙伴们应该对多态有个大致的了解,但是不要掉以轻心哦,还有一种情况也是会出现失灵的,那就是如果某个方法是静态的,那么它的行为就不具有多态性。

老规矩,我们看下这组代码:

class StaticSuper {    public static void staticTest() {
        System.out.println("StaticSuper staticTest()");
    }

}class StaticSon extends StaticSuper{    public static void staticTest() {
        System.out.println("StaticSon staticTest()");
    }

}class StaticTest {    public static void main(String[] args) {
        StaticSuper sup = new StaticSon();
        sup.staticTest();
    }
}/* OUTPUT
StaticSuper staticTest()
*/复制代码
ログイン後にコピー

静态方法是与类相关联,而非与对象相关联

3.构造器与多态

首先我们需要明白的是构造器不具有多态性,因为构造器实际上是static方法,只不过该static的声明是隐式的。

我们先回到开头的那段神秘代码:

最近では、Java を知っていると言うなら、ポリモーフィズムを知らなければなりません。

其中输出结果是:

/*
    polygon() before cal()
    square.cal(), border = 0
    polygon() after cal()
    square.square(), border = 4
*/复制代码
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

我们可以看到先输出的是基类polygon中构造器的方法。

这是因为基类的构造器总是在导出类的构造过程中被调用,而且是按照继承层次逐渐向上链接,以使每个基类的构造器都能得到调用。

最近では、Java を知っていると言うなら、ポリモーフィズムを知らなければなりません。

因为构造器有一项特殊的任务:检查对象是否能正确的被构造。导出类只能访问它自己的成员,不能访问基类的成员(基类成员通常是private类型)。只有基类的构造器才具有权限来对自己的元素进行初始化。因此,必须令所有构造器都得到调用,否则就不可能正确构造完整对象。

步骤如下:

  • 调用基类构造器,这个步骤会不断的递归下去,首先是构造这种层次结构的根,然后是下一层导出类,...,直到最底层的导出类
  • 按声明顺序调用成员的初始化方法
  • 调用导出类构造其的主体

打个不是特别恰当的比方:你的出现是否先要有你父亲,你父亲的出现是否先要有你的爷爷,这就是逐渐向上链接的方式

构造器内部的多态行为

有没有想过如果在一个构造器的内调用正在构造的对象的某个动态绑定方法,那么会发生什么情况呢? 动态绑定的调用是在运行时才决定的,因为对象无法知道它是属于方法所在的那个类还是那个类的导出类。如果要调用构造器内部的一个动态绑定方法,就要用到那个方法的被覆盖后的定义。然而因为被覆盖的方法在对象被完全构造之前就会被调用,这可能就会导致一些难于发现的隐藏错误。

问题引索

一个动态绑定的方法调用会向外深入到继承层次结构内部,它可以调动导出类里的方法,如果我们是在构造器内部这样做,那么就可能会调用某个方法,而这个方法做操纵的成员可能还未进行初始化,这肯定就会招致灾难的。

敏感的小伙伴是不是想到了开头的那段代码:

最近では、Java を知っていると言うなら、ポリモーフィズムを知らなければなりません。

输出结果是:

/*
    polygon() before cal()
    square.cal(), border = 0
    polygon() after cal()
    square.square(), border = 4
*/复制代码
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

我们在进行square对象初始化的时候,会先进行polygon对象的初始化,在polygon构造器中有个cal()方法,这个时候就采用了动态绑定机制,调用了squarecal(),但这个时候border这个变量尚未进行初始化,int 类型的默认值为 0,因此就有了square.cal(), border = 0的输出。看到这里,小伙伴们是不是有种拨开云雾见青天的感觉!

这组代码初始化的实际过程为:

  • 在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的零
  • 调用基类构造器时,会调用被覆盖后的cal()方法,由于步骤1的缘故,因此 border 的值为 0
  • 按照声明的顺序调用成员的初始化方法
  • 调用导出类的构造器主体

不知道下次又会做什么样的梦~

最近では、Java を知っていると言うなら、ポリモーフィズムを知らなければなりません。

想了解更多编程学习,敬请关注php培训栏目!

以上が最近では、Java を知っていると言うなら、ポリモーフィズムを知らなければなりません。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:juejin.im
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
スコープ現在のクラスパッケージを使用する子孫クラスその他のパッケージ
公開
保護済み×
デフォルト×