面向对象编程有三大特征:封装、继续、多态。
封装隐蔽了类的内部完成机制,可以在不影响运用的情况下转变类的内部构造,同时也庇护了数据。对外界罢了它的内部细节是隐蔽的,暴露给外界的只是它的接见要领。
继续是为了重用父类代码。两个类若存在IS-A的关联便可以运用继续。,同时继续也为完成多态做了铺垫。那末什么是多态呢?多态的完成机制又是什么?请看我逐一为你揭开:
所谓多态就是指顺序中定义的援用变量所指向的细致范例和经由过程该援用变量发出的要领挪用在编程时并不肯定,而是在顺序运转时期才肯定,即一个援用变量倒底会指向哪一个类的实例对象,该援用变量发出的要领挪用究竟是哪一个类中完成的要领,必需在由顺序运转时期才决议。因为在顺序运转时才肯定细致的类,如许,不必修正源顺序代码,便可以让援用变量绑定到种种差别的类完成上,从而致使该援用挪用的细致要领随之转变,即不修正顺序代码便可以转变顺序运转时所绑定的细致代码,让顺序可以挑选多个运转状况,这就是多态性
比方你是一个酒神,对酒情有独钟。某日回家发明桌上有几个杯子内里都装了白酒,从表面看我们是不可能晓得这是些什么酒,只要喝了以后才够猜出来是何种酒。你一喝,这是剑南春、再喝这是五粮液、再喝这是酒鬼酒….在这里我们可以形貌成以下:
酒 a = 剑南春
酒 b = 五粮液
酒 c = 酒鬼酒
…
这里所表现的的就是多态。剑南春、五粮液、酒鬼酒都是酒的子类,我们只是经由过程酒这一个父类便可以援用差别的子类,这就是多态——我们只要在运转的时刻才会晓得援用变量所指向的细致实例对象。
固然,要邃晓多态我们就必需要邃晓什么是“向上转型”。在继续中我们简朴引见了向上转型,这里就在烦琐下:在上面的饮酒例子中,酒(Win)是父类,剑南春(JNC)、五粮液(WLY)、酒鬼酒(JGJ)是子类。我们定义以下代码:
JNC a = new JNC();
关于这个代码我们异常轻易邃晓不过就是实例化了一个剑南春的对象嘛!然则如许呢?
Wine a = new JNC();
在这里我们如许邃晓,这里定义了一个Wine 范例的a,它指向JNC对象实例。因为JNC是继续与Wine,所以JNC可以自动向上转型为Wine,所以a是可以指向JNC实例对象的。如许做存在一个异常大的优点,在继续中我们晓得子类是父类的扩大,它可以供应比父类越发壮大的功用,假如我们定义了一个指向子类的父类援用范例,那末它除了可以援用父类的共性外,还可以运用子类壮大的功用。
然则向上转型存在一些缺憾,那就是它必定会致使一些要领和属性的丧失,而致使我们不可以猎取它们。所以父类范例的援用可以挪用父类中定义的一切属性和要领,关于只存在与子类中的要领和属性它就瞠乎其后了---1。
public class Wine { public void fun1(){ System.out.println("Wine 的Fun....."); fun2(); } public void fun2(){ System.out.println("Wine 的Fun2..."); } } public class JNC extends Wine{ /** * @desc 子类重载父类要领 * 父类中不存在该要领,向上转型后,父类是不能援用该要领的 * @param a * @return void */ public void fun1(String a){ System.out.println("JNC 的 Fun1..."); fun2(); } /** * 子类重写父类要领 * 指向子类的父类援用挪用fun2时,必定是挪用该要领 */ public void fun2(){ System.out.println("JNC 的Fun2..."); } } public class Test { public static void main(String[] args) { Wine a = new JNC(); a.fun1(); } } ------------------------------------------------- Output: Wine 的Fun..... JNC 的Fun2...
从顺序的运转效果中我们发明,a.fun1()起首是运转父类Wine中的fun1().然后再运转子类JNC中的fun2()。
剖析:在这个顺序中子类JNC重载了父类Wine的要领fun1(),重写fun2(),而且重载后的fun1(String a)与 fun1()不是统一个要领,因为父类中没有该要领,向上转型后会丧失该要领,所以实行JNC的Wine范例援用是不能援用fun1(String a)要领。而子类JNC重写了fun2() ,那末指向JNC的Wine援用会挪用JNC中fun2()要领。
所以关于多态我们可以总结以下:
指向子类的父类援用因为向上转型了,它只能接见父类中具有的要领和属性,而关于子类中存在而父类中不存在的要领,该援用是不能运用的,只管是重载该要领。若子类重写了父类中的某些要领,在挪用该些要领的时刻,必定是运用子类中定义的这些要领(动态衔接、动态挪用)。
关于面向对象罢了,多态分为编译时多态和运转时多态。个中编辑时多态是静态的,主如果指要领的重载,它是依据参数列表的差别来辨别差别的函数,经由过程编辑以后会变成两个差别的函数,在运转时谈不上多态。而运转时多态是动态的,它是经由过程动态绑定来完成的,也就是我们所说的多态性。
多态的完成
2.1完成前提
在刚刚最先就提到了继续在为多态的完成做了预备。子类Child继续父类Father,我们可以编写一个指向子类的父类范例援用,该援用既可以处置惩罚父类Father对象,也可以处置惩罚子类Child对象,当雷同的音讯发送给子类或许父类对象时,该对象就会依据本身所属的援用而实行差别的行动,这就是多态。即多态性就是雷同的音讯使得差别的类做出差别的相应。
Java完成多态有三个必要前提:继续、重写、向上转型。
继续:在多态中必需存在有继续关联的子类和父类。
重写:子类对父类中某些要领举行从新定义,在挪用这些要领时就会挪用子类的要领。
向上转型:在多态中需要将子类的援用赋给父类对象,只要如许该援用才够具有妙技挪用父类的要领和子类的要领。
只要满足了上述三个前提,我们才够在统一个继续构造中运用一致的逻辑完成代码处置惩罚差别的对象,从而到达实行差别的行动。
关于Java而言,它多态的完成机制遵照一个准绳:当超类对象援用变量援用子类对象时,被援用对象的范例而不是援用变量的范例决议了挪用谁的成员要领,然则这个被挪用的要领必需是在超类中定义过的,也就是说被子类掩盖的要领。
2.2完成情势
在Java中有两种情势可以完成多态。继续和接口。
2.2.1、基于继续完成的多态
基于继续的完成机制重要表如今父类和继续该父类的一个或多个子类对某些要领的重写,多个子类对统一要领的重写可以表现出差别的行动。
public class Wine { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public Wine(){ } public String drink(){ return "喝的是 " + getName(); } /** * 重写toString() */ public String toString(){ return null; } } public class JNC extends Wine{ public JNC(){ setName("JNC"); } /** * 重写父类要领,完成多态 */ public String drink(){ return "喝的是 " + getName(); } /** * 重写toString() */ public String toString(){ return "Wine : " + getName(); } } public class JGJ extends Wine{ public JGJ(){ setName("JGJ"); } /** * 重写父类要领,完成多态 */ public String drink(){ return "喝的是 " + getName(); } /** * 重写toString() */ public String toString(){ return "Wine : " + getName(); } } public class Test { public static void main(String[] args) { //定义父类数组 Wine[] wines = new Wine[2]; //定义两个子类 JNC jnc = new JNC(); JGJ jgj = new JGJ(); //父类援用子类对象 wines[0] = jnc; wines[1] = jgj; for(int i = 0 ; i < 2 ; i++){ System.out.println(wines[i].toString() + "--" + wines[i].drink()); } System.out.println("-------------------------------"); } } OUTPUT: Wine : JNC--喝的是 JNC Wine : JGJ--喝的是 JGJ -------------------------------
在上面的代码中JNC、JGJ继续Wine,而且重写了drink()、toString()要领,顺序运转效果是挪用子类中要领,输出JNC、JGJ的称号,这就是多态的表现。差别的对象可以实行雷同的行动,然则他们都需要经由过程本身的完成体式格局来实行,这就要得益于向上转型了。
我们都晓得一切的类都继续自超类Object,toString()要领也是Object中要领,当我们如许写时:
Object o = new JGJ(); System.out.println(o.toString());
输出的效果是Wine : JGJ。
Object、Wine、JGJ三者继续链关联是:JGJ—>Wine—>Object。所以我们可以如许说:当子类重写父类的要领被挪用时,只要对象继续链中的最末端的要领才会被挪用。然则注重假如如许写:
Object o = new Wine(); System.out.println(o.toString());
输出的效果应该是Null,因为JGJ并不存在于该对象继续链中。
所以基于继续完成的多态可以总结以下:关于援用子类的父类范例,在处置惩罚该援用时,它实用于继续该父类的一切子类,子类对象的差别,对要领的完成也就差别,实行雷同行动发生的行动也就差别。
假如父类是笼统类,那末子类必需要完成父类中一切的笼统要领,如许该父类一切的子类肯定存在一致的对外接口,但其内部的细致完成可以各别。如许我们便可以运用顶层类供应的一致接口来处置惩罚该条理的要领。
2.2.2、基于接口完成的多态
继续是经由过程重写父类的统一要领的几个差别子类来表现的,那末便可就是经由过程完成接口并掩盖接口中统一要领的几差别的类表现的。
在接口的多态中,指向接口的援用必需是指定这完成了该接口的一个类的实例顺序,在运转时,依据对象援用的现实范例来实行对应的要领。
继续都是单继续,只能为一组相干的类供应一致的效劳接口。然则接口可所以多继续多完成,它可以应用一组相干或许不相干的接口举行组合与扩大,可以对外供应一致的效劳接口。所以它相干于继续来讲有更好的灵活性。
三、典范实例。
经由过程上面的报告,可以说是对多态有了肯定的相识。如今一气呵成,看一个实例。该实例是有关多态的典范例子,
public class A { public String show(D obj) { return ("A and D"); } public String show(A obj) { return ("A and A"); } } public class B extends A{ public String show(B obj){ return ("B and B"); } public String show(A obj){ return ("B and A"); } } public class C extends B{ } public class D extends B{ } public class Test { public static void main(String[] args) { A a1 = new A(); A a2 = new B(); B b = new B(); C c = new C(); D d = new D(); System.out.println("1--" + a1.show(b)); System.out.println("2--" + a1.show(c)); System.out.println("3--" + a1.show(d)); System.out.println("4--" + a2.show(b)); System.out.println("5--" + a2.show(c)); System.out.println("6--" + a2.show(d)); System.out.println("7--" + b.show(b)); System.out.println("8--" + b.show(c)); System.out.println("9--" + b.show(d)); } }
运转效果:
1--A and A 2--A and A 3--A and D 4--B and A 5--B and A 6--A and D 7--B and B 8--B and B 9--A and D
在这里看效果1、2、3还好邃晓,从4最先就最先糊涂了,关于4来讲为何输出不是“B and B”呢?
起首我们先看一句话:当超类对象援用变量援用子类对象时,被援用对象的范例而不是援用变量的范例决议了挪用谁的成员要领,然则这个被挪用的要领必需是在超类中定义过的,也就是说被子类掩盖的要领。这句话对多态举行了一个归纳综合。实在在继续链中对象要领的挪用存在一个优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
剖析:
从上面的顺序中我们可以看出A、B、C、D存在以下关联。
起首我们剖析5,a2.show(c),a2是A范例的援用变量,所以this就代表了A,a2.show(c),它在A类中找发明没有找到,因而到A的超类中找(super),因为A没有超类(Object除外),所以跳到第三级,也就是this.show((super)O),C的超类有B、A,所以(super)O为B、A,this一样是A,这里在A中找到了show(A obj),同时因为a2是B类的一个援用且B类重写了show(A obj),因而终究会挪用子类B类的show(A obj)要领,效果也就是B and A。
根据一样的要领我也可以确认其他的答案。
要领已找到了然则我们这里照样存在一点疑问,我们照样来看这句话:当超类对象援用变量援用子类对象时,被援用对象的范例而不是援用变量的范例决议了挪用谁的成员要领,然则这个被挪用的要领必需是在超类中定义过的,也就是说被子类掩盖的要领。这我们用一个例子来讲明这句话所代表的寄义:a2.show(b);
这里a2是援用变量,为A范例,它援用的是B对象,因而根据上面那句话的意义是说有B来决议挪用谁的要领,所以a2.show(b)应该要挪用B中的show(B obj),发生的效果应该是“B and B”,然则为何会与前面的运转效果发生差别呢?这里我们疏忽了背面那句话“然则这儿被挪用的要领必需是在超类中定义过的”,那末show(B obj)在A类中存在吗?基础就不存在!所以这句话在这里不实用?那末岂非是这句话错误了?非也!实在这句话还隐含这这句话:它依然要根据继续链中挪用要领的优先级来确认。所以它才会在A类中找到show(A obj),同时因为B重写了该要领所以才会挪用B类中的要领,不然就会挪用A类中的要领。
所以多态机制遵照的准绳归纳综合为:当超类对象援用变量援用子类对象时,被援用对象的范例而不是援用变量的范例决议了挪用谁的成员要领,然则这个被挪用的要领必需是在超类中定义过的,也就是说被子类掩盖的要领,然则它依然要依据继续链中要领挪用的优先级来确认要领,该优先级为:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
以上就是java中多态的深切剖析(代码示例)的细致内容,更多请关注ki4网别的相干文章!