Java 深入理解多态(一)多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定
将改变的事物与未变的事物分离开来—-多态
多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性
Example:Instrument include Wind
///:~
class Instrument{
public void paly(){
System.out.println("Instrument.play()");
}
public void fix(){
System.out.println("Instrument.play()");
}
}
///:~
class Wind extends Instrument{
@Override
public void paly() {
System.out.println("Wind.play()");
}
public void fix(String s) {
System.out.println("Wind.fix()");
}
}
///:~
public class Main {
public static void main(String[] args) {
//...
Wind wind = new Wind();
tune(wind);
}
public static void tune(Instrument i){
i.paly();
i.fix();
}
}
/*Output:
Wind.play()
Instrument.play()
*///:~
向上转型:对某个对象的引用(Wind)视为对其基类型的引用(Instrument)的做法称为~
在Main.tune()函数中 我们将一个Instrument类型的i 指向了Wind对象实例, 由于Wind是继承Instrument,所谓Instrument的接口必存在于Wind,所以Wind可以自动向上转型为Instrument,所以i是可以指向Wind实例对象的,从Wind向上转型Instrument可能会“缩小”接口,但是不会比Instrument的全部接口更窄,父类类型的引用可以调用父类中定义的所有属性和方法,对于只存在与子类中的方法和属性它就望尘莫及了。
指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用该些方法的时候,必定是使用子类中定义的这些方法(动态连接、动态调用),由于paly()是重写,fix()是重载,由于父类中没fix(String)方法,所以Wind的Instrument类型引用不能引用fix(String)方法,而子类Wind重写了play(),那么指向Wind的Instrument引用会调用Wind中的play()方法。
对于面向对象而言,多态分为编译时多态和运行时多态。其中编辑时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。
多态的实现
条件
Java多态的实现有三个必要条件:继承,重写,向上转型
多态的实现机制遵循原则:
当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。
实现形式
继承和接口的形式实现多态(两种)
基于继承实现
基于继承的实现机制主要表现在父类和继承该父类的一个或多个子类对某些方法的重写,多个子类对同一方法的重写可以表现出不同的行为。
class Instrument{
public void paly(){
System.out.println("Instrument.play()");
}
public void fix(){
System.out.println("Instrument.play()");
}
}
///:~
class Wind extends Instrument{
@Override
public void paly() {
System.out.println("Wind.play()");
}
public void fix() {
System.out.println("Wind.fix()");
}
}
///:~
class Stringed extends Instrument{
@Override
public void paly() {
System.out.println("Stringed.play()");
}
@Override
public void fix() {
System.out.println("Stringed.play()");
}
}
///:~
public class Main {
public static void main(String[] args) {
//...
Instrument[] i = {new Stringed(),new Wind()};
for (Instrument instrument:i) {
instrument.paly();
instrument.fix();
}
}
}
/*Output:
Stringed.play()
Stringed.play()
Wind.play()
Wind.fix()
*///:~
上面代码Wind,Stringed继承Instrument,并且重写了play(),fix()方法,程序运行的结果是调用子类中的方法,这就是多态的表现,不同的对象可以执行相同的行为,但是他们都需要通过自己的实现方式来执行,这就要得益于向上转型了。
例如超类Object 假如超类中拥有paly()方法当我们这样写的时候
///:~
///:~
///:~
Object o = new Wind()
System.out.println(o.play());
/*Output:
Wind.play()
*///:~
///:~
///:~
///:~
Object o = new Instrument()
System.out.println(o.play());
/*Output:
Instrument.play()
*///:~
当子类重写父类的方法被调用时,只有对象继承链中的最末端的方法才会被调用。
所以基于继承实现的多态可以总结如下:对于引用子类的父类类型,在处理该引用时,它适用于继承该父类的所有子类,子类对象的不同,对方法的实现也就不同,执行相同动作产生的行为也就不同。
如果父类是抽象类,那么子类必须要实现父类中所有的抽象方法,这样该父类所有的子类一定存在统一的对外接口,但其内部的具体实现可以各异。这样我们就可以使用顶层类提供的统一接口来处理该层次的方法。
基于接口实现
继承是通过重写父类的同一方法的几个不同子类来体现的,那么就可就是通过实现接口并覆盖接口中同一方法的几不同的类体现的。
在接口的多态中,指向接口的引用必须是指定这实现了该接口的一个类的实例程序,在运行时,根据对象引用的实际类型来执行对应的方法。
继承都是单继承,只能为一组相关的类提供一致的服务接口。但是接口可以是多继承多实现,它能够利用一组相关或者不相关的接口进行组合与扩充,能够对外提供一致的服务接口。所以它相对于继承来说有更好的灵活性。
引用实例
class A {
public String show(D obj) {
return ("A and D");
}
public String show(A obj) {
return ("A and A");
}
}
class B extends A{
public String show(B obj){
return ("B and B");
}
public String show(A obj){
return ("B and A");
}
}
class C extends B{
}
class D extends B{
}
public class Main {
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));
}
}
/*Output:
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
*///:~
首先我们先看一句话:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。这句话对多态进行了一个概括。其实在继承链中对象方法的调用存在一个优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
首先我们分析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。
比如4,a2.show(b),a2是一个引用变量,类型为A,则this为a2,b是B的一个实例,于是它到类A里面找show(B obj)方法,没有找到,于是到A的super(超类)找,而A没有超类,因此转到第三优先级this.show((super)O),this仍然是a2,这里O为B,(super)O即(super)B即A,因此它到类A里面找show(A obj)的方法,类A有这个方法,但是由于a2引用的是类B的一个对象,B覆盖了A的show(A obj)方法,因此最终锁定到类B的show(A obj),输出为”B and A”。
再比如8,b.show(c),b是一个引用变量,类型为B,则this为b,c是C的一个实例,于是它到类B找show(C obj)方法,没有找到,转而到B的超类A里面找,A里面也没有,因此也转到第三优先级this.show((super)O),this为b,O为C,(super)O即(super)C即B,因此它到B里面找show(B obj)方法,找到了,由于b引用的是类B的一个对象,因此直接锁定到类B的show(B obj),输出为”B and B”。
方法已经找到了但是我们这里还是存在一点疑问,我们还是来看这句话:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。这我们用一个例子来说明这句话所代表的含义: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中除了static和final方法其他的方法都是后期绑定
缺陷:域和静态方法
class Super{
public int field = 0;
public int getField(){
return field;
}
}
class Sub extends Super{
public int field =1;
public int getField() {
return field;
}
public int getSuperField() {
return super.field;
}
}
public class Main {
public static void main(String[] args) {
//...
Super sup = new Sub();
System.out.println(sup.field+" "+sup.getField());
Sub sub = new Sub();
System.out.println(sub.field+" "+sub.getField()+" "+sub.getSuperField());
}
}
/*Output:
0 1
1 1 0
*///:~
当Sub对象转型为Super引用时,任何域操作都将由编译器解析,因此不是多态的
class Instrument{
public static void paly(){
System.out.println("Instrument.play()");
}
public void fix(){
System.out.println("Instrument.play()");
}
}
///:~
class Wind extends Instrument{
public static void paly() {
System.out.println("Wind.play()");
}
public void fix() {
System.out.println("Wind.fix()");
}
}
///:~
public class Main {
public static void main(String[] args) {
//...
Instrument instrument = new Wind();
instrument.fix();
instrument.paly();
}
}
/*Output:
Wind.fix()
Instrument.play()
*///:~
如果某个方法是静态的,他的行为不具有多态性,静态方法是与类,而非与单个的对象相关联的
转载自:https://juejin.cn/post/6844903501349257223