Maison  >  Article  >  Java  >  De nos jours, si vous dites connaître Java, vous devez connaître le polymorphisme.

De nos jours, si vous dites connaître Java, vous devez connaître le polymorphisme.

coldplay.xixi
coldplay.xixiavant
2020-09-15 16:46:242480parcourir

De nos jours, si vous dites connaître Java, vous devez connaître le polymorphisme.

Recommandations d'apprentissage associées : Tutoriel de base Java

Aujourd'hui, j'ai visité l'entreprise comme d'habitude. Asseyez-vous à votre poste de travail et allumez votre ordinateur, "又是搬砖的一天". Après y avoir réfléchi, j'ai "熟练" ouvert Idea, examiné les exigences actuelles et commencé à taper le code. Hé, qui a écrit ces codes, comment se fait-il qu'ils apparaissent dans mon code, et qu'ils attendent toujours d'être soumis. Je me souviens que je ne les ai jamais écrits, alors je les ai regardés avec intérêt :

De nos jours, si vous dites connaître Java, vous devez connaître le polymorphisme..

N'est-ce pas polymorphe ? Celui qui a écrit le test sur mon ordinateur ne peut s'empêcher de se demander.

"你看看这会输出什么结果?"

Un son est venu de derrière. Parce que je pensais aux résultats de sortie, je ne me souciais pas de la source du son et j'ai continué à regarder le code. suis arrivé à la conclusion :

    polygon() before cal()
    square.cal(), border = 2
    polygon() after cal()
    square.square(), border = 4复制代码

Je pensais : C'est ça ? Au moins, vous êtes ingénieur en développement Java, d'accord ? Même si vous faites habituellement beaucoup de choses, vous avez quand même quelques compétences de base. Je n'ai pas pu m'empêcher de me sentir un peu fier~

"这就是你的答案吗?看来你也不咋的"

La voix a soudainement retenti, cette fois je ne suis plus calme, Nima ! J'ai aussi pensé à cette réponse dans mon esprit, d'accord ? Qui peut la voir ? De plus, cela donne envie aux gens d'interpréter un ensemble des dix-huit styles d'Awei. "你是谁啊?"Il tourna la tête avec une pointe de doute et de colère. Pourquoi n'y a-t-il personne ? Vous ne pouvez pas tolérer un seul instant mes doutes, "小菜,醒醒,你怎么上班时间就睡着了"

Vous dormez pendant les heures de travail ? J'ai ouvert les yeux et regardé ce qui m'entourait. Cela s'est avéré être un rêve et j'ai poussé un soupir de soulagement. J'ai regardé autour de moi et j'ai vu le chef de service debout devant moi. Il dormait pendant les heures de travail. Tu ne te sens pas bien ou quelque chose comme ça ? J'ai écrit un tas de bugs hier mais je ne les ai pas corrigés, et j'ai soumis des trucs compliqués aujourd'hui. Je pense que votre performance ce mois-ci n'est pas ce que je veux, et sur la base de votre performance, je dois commencer à y réfléchir pour le département. .

"我不是,我没有,我也不知道怎么就睡着了,你听我解释啊!" Avant de pouvoir dire cela, 心里的花我要带你回家,在那深夜酒吧哪管它是真是假,请你尽情摇摆忘记钟意的他,你是最迷人噶,你知道吗, l'alarme a sonné, je me suis levé immédiatement, mon dos était légèrement mouillé et mon front légèrement en sueur, j'ai regardé mon téléphone. 30, ça s’est avéré être un rêve !

Étrange, comment ai-je pu faire un rêve aussi étrange ? C'est si effrayant. Ensuite, j'ai pensé à la partie du code dans le rêve. Mes résultats sont-ils faux ? D'après ma mémoire, je l'ai retapé sur l'ordinateur, et les résultats sont les suivants : Le résultat de

/*
    polygon() before cal()
    square.cal(), border = 0
    polygon() after cal()
    square.square(), border = 4
*/复制代码

square.cal(), border est en fait 0, pas 2. Ne suis-je même pas bon en polymorphisme maintenant ? Devant votre ordinateur et votre téléphone portable, vous ne savez pas si vous avez trouvé la bonne réponse ! Que vous l'ayez ou non, passons en revue le polymorphisme avec Xiao Cai !

Certains amis peuvent être confus non seulement quant au résultat de square.cal(), border étant 0, mais aussi quant à la raison pour laquelle square.square(), border = 4 n'est pas affiché en premier. Alors commençons par les doutes !

Polymorphisme

Dans les langages de programmation orientés objet, le polymorphisme est la troisième fonctionnalité de base après l'abstraction et l'héritage des données.

Le polymorphisme peut non seulement améliorer l'organisation et la lisibilité du code, mais également créer des programmes évolutifs. La fonction du polymorphisme est d'éliminer 耦合关系 entre les types.

1. Transformation vers le haut

Selon 里氏代换原则 : Partout où une classe de base peut apparaître, une sous-classe peut certainement apparaître.

Un objet peut être utilisé soit comme son propre type, soit comme son type de base. Cette approche consistant à traiter une référence à un objet comme une référence à son type de base est appelée - 向上转型. Étant donné que la classe parent est au-dessus de la classe enfant, la classe enfant doit faire référence à la classe parent, elle est donc appelée 向上转型.

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()
*/复制代码

La méthode test dans la classe start() ci-dessus reçoit une référence de Animal et peut naturellement également recevoir des classes dérivées de Animal. Lors de l'appel de la méthode eat(), la méthode Monkey définie dans eat() est naturellement utilisée sans aucune conversion de type. Parce que la diffusion ascendante de Monkey vers Animal ne peut que réduire les interfaces, mais pas moins d'interfaces que Animal.

Une analogie pas particulièrement appropriée : Les biens de votre père vous seront hérités, et vos biens seront toujours les vôtres. En général, vos biens ne seront pas inférieurs à ceux de votre père.

De nos jours, si vous dites connaître Java, vous devez connaître le polymorphisme.

忘记对象类型

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 修饰的方法之外,都是属于后期绑定

合理即正确

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

De nos jours, si vous dites connaître Java, vous devez connaître le polymorphisme.

其中MonkeyPigDog皆是Animal的导出类

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

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

可扩展性

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

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

失灵了?

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

作用域 当前类 用一个package 子孙类 其他package
public
protected ×
default × ×
private × × ×
  • 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注解就会报错:

De nos jours, si vous dites connaître Java, vous devez connaître le polymorphisme.

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

域带来的失灵

当小伙伴看到这里,就会开始认为所有事物(除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本身定义的。这与我们认识的多态有点冲突。

De nos jours, si vous dites connaître Java, vous devez connaître le polymorphisme.

其实不然,当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的声明是隐式的。

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

De nos jours, si vous dites connaître Java, vous devez connaître le polymorphisme.

其中输出结果是:

/*
    polygon() before cal()
    square.cal(), border = 0
    polygon() after cal()
    square.square(), border = 4
*/复制代码

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

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

De nos jours, si vous dites connaître Java, vous devez connaître le polymorphisme.

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

步骤如下:

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

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

构造器内部的多态行为

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

问题引索

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

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

De nos jours, si vous dites connaître Java, vous devez connaître le polymorphisme.

输出结果是:

/*
    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
  • 按照声明的顺序调用成员的初始化方法
  • 调用导出类的构造器主体

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

De nos jours, si vous dites connaître Java, vous devez connaître le polymorphisme.

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer