java-面向对象编程--多态

继承是为了重用父类代码。两个类若存在IS-A的关系就可使用继承。同时继承也为实现多态作了铺垫。php

多态

多态就是指程序中定义的引用变量所指向的具体类型和经过该引用变量发出的方法调用在编程时并不肯定,而是在程序运行期间才肯定,即一个引用变量倒底会指向哪一个类的实例对象,该引用变量发出的方法调用究竟是哪一个类中实现的方法,必须在由程序运行期间才能决定。由于在程序运行时才肯定具体的类,这样,不用修改源程序代码,就可让引用变量绑定到各类不一样的类实现上,从而致使该引用调用的具体方法随之改变,即不修改程序代码就能够改变程序运行时所绑定的具体代码,让程序能够选择多个运行状态,这就是多态性。java

多态,简而言之就是同一个行为具备多个不一样表现形式或形态的能力。好比说,有一杯水,我不知道它是温的、冰的仍是烫的,可是我一摸我就知道了。我摸水杯这个动做,对于不一样温度的水,就会获得不一样的结果。这就是多态。
编程

多态的条件

  1. 继承。在多态中必须存在有继承关系的子类和父类。
  2. 重写。子类对父类的默些方法从新定义,在调用这些方法的时候就会调用子类的方法。
  3. 向上转型。在多态中须要将子类的引用赋值给父类对象,只有这样该引用才能具有技能调用父类的方法和子类的方法。
只有知足了上述三个条件,咱们才能实现多态。

对于java而言,多态的实现机制遵循一个原则:当父类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,可是这个被调用的方法必须是在父类中定义过的,也就是被子类覆盖的方法。bash

向上转型

子类引用的对象转换成父类类型成为向上转型。通俗的说就是将子类对象转成父类对象。此处父类对象能够是接口。ui

public class Animal {
public void eat(){
System.out.println("animal eatting...");
}
}

public class Cat extends Animal{

    public void eat(){

        System.out.println("猫吃鱼");
    }
}
public class Dog extends Animal{

public void eat(){
System.out.println("狗吃骨头");
}

public void run(){
System.out.println("我会跑");
    }
}

public class Main {

public static void main(String[] args) {
animal = new Dog();//向上转型
animal.eat();
}

}复制代码

输出:狗吃骨头复制代码

这就是向上转型,Animal animal = new Dog();将子类对象Dog转化为父类对象Animal。这个时候animal这个引用调用的方法是子类方法。
this

转型过程当中须要注意的问题

  • 向上转型时,子类单独定义的方法会丢失。好比上面Dog类中定义的run方法,当animal引用指向Dog类实例时是访问不到run方法的,animal.run()会报错。
  • 子类引用不能指向父类对象。Dog d = (Dog)new Animal()这样是不行的。

向上转型的好处

  • 减小重复代码,使代码变得简洁。
  • 提升系统扩展性。

向下转型(有坑)

与向上转型相对应的就是向下转型了。向下转型是把父类对象转为子类对象。(请注意!这里是有坑的。)
spa

//仍是上面的animal和cat dog
Animal a = new Cat();
Cat c = ((Cat) a);
c.eat();
//输出  我吃鱼
Dog d = ((Dog) a);
d.eat();
// 报错 : java.lang.ClassCastException:com.chengfan.animal.Cat cannot be cast to com.chengfan.animal.Dog
Animal a1 = new Animal();
Cat c1 = ((Cat) a1);
c1.eat();
// 报错 : java.lang.ClassCastException:com.chengfan.animal.Animal cannot be cast to com.chengfan.animal.Cat
复制代码

为何第一段代码不报错呢,由于a自己就是Cat对象,因此固然能够转型为Cat,由于是Cat因此不能转为Dog。code

而a1是Anmail对象,它不能向下转型Wie任何子类对象。好比发现一个古生物化石,知道它是一种动物,但你不能直接说他是猫或者他是狗。对象

向下转型注意事项继承

  • 向下转型的前提是父类对象指向的是子类对象(也就是说,在向下转型以前,它得先向上转型)
  • 向下转型只能转型为本类对象(猫是不能变成狗的)。

instanceof 关键字:instanceof是Javaphp的一个二元操做符(运算符),和==,>,<是同一类东西。因为它是由字母组成的,因此也是Java的保留关键字。它的做用是判断其左边对象是否为其右边类的实例,返回boolean类型的数据。能够用来判断继承中的子类的实例是否为父类的实现。

instanceof通常放在类型转换的前面,合理规避异常产生!

public void eat(Animal a){
/* 
         * 向下转型(强制转型) 
         * 子类引用指向父类的实例(对象),此处必须今习惯强制类型转换 
         * 该实例能够调用子类特有的方法 
         * 必须知足转型条件才能转换(),子类间不能随意强制转换,可是子类引用指向父类实例,能够强制转换 
         * instanceof运算符:返回true/false, 
 */  

    if(a instanceof Dog){  
        Dog d = (Dog)a;
        d.eat();
        d.run();//狗有一个跑的方法      
    } 
    if(a instanceof Cat){  
        Cat c = (Cat)a;
        c.eat();
        System.out.println("我也想跑,可是不会"); //猫会抱怨    
    } 
    a.eat();//其余动物只会吃
}

eat(new Cat());
eat(new Cat());
eat(new Dog());复制代码

花木兰替父从军

向下转型花木兰替父亲花弧从军。那么这时候花木兰是子类,花弧是父类。花弧有本身的成员属性年龄,姓名,性别。花木兰也有这些属性,可是很明显两者的属性彻底不同。花弧有本身的非静态成员方法‘骑马杀敌’,一样花木兰也遗传了父亲同样的方法‘骑马杀敌’。花弧还有一个静态方法‘自我介绍’,每一个人均可以问花弧姓甚名谁。同时花木兰还有一个本身特有的非静态成员方法‘涂脂抹粉’。可是,如今花木兰替父从军,女扮男装。这时候至关于父类的引用(花弧这个名字)指向了子类对象(花木兰这我的),那么在其余类(其余的人)中访问子类对象(花木兰这我的)的成员属性(姓名,年龄,性别)时,其实看到的都是花木兰她父亲的名字(花弧)、年龄(60岁)、性别(男)。当访问子类对象(花木兰这我的)的非静态成员方法(骑马打仗)时,其实都是看到花木兰本身运用十八般武艺在骑马打仗。当访问花木兰的静态方法时(自我介绍),花木兰本身都是用她父亲的名字信息在向别人做自我介绍。而且这时候花木兰不能使用本身特有的成员方法‘涂脂抹粉’。

-----多态中的向上转型 那么终于一将功成万骨枯,打仗旗开得胜了,花木兰告别了战争生活。有一天,遇到了本身心爱的男人,这时候爱情的力量将父类对象的引用(花弧这个名字)强制转换为子类对象原本的引用(花木兰这个名字),那么花木兰又重新成为了她本身,这时候她彻底是她本身了。名字是花木兰,年龄是28,性别是女,打仗依然那样生猛女汉子,自我介绍则堂堂正正地告诉别人我叫花木兰。OMG!终于,终于可使用本身特有的成员方法‘涂脂抹粉’了。今后,花木兰彻底回到了替父从军前的那个花木兰了。而且和本身心爱的男人幸福的过完了一辈子。-----多态中的向下转型

你们记得哈,向上转型向下转型必定是在多态这个前提下哈,不然强制将女儿变成父亲,或者将父亲变成女人,就变成东方不败了,系统此时就会报错非法类型转换。哈哈哈哈哈。另外开发中通常是利用多态声明形式参数,并将建立子类的匿名对象做为实际参数。以上。 

多态成员访问特色

  • 成员变量 编译看左边(父类),运行看左边(父类) 
  • 成员方法 编译看左边(父类),运行看右边(子类)。动态绑定 
  • 静态方法 编译看左边(父类),运行看左边(父类)。
静态和类相关,算不上重写,因此,访问仍是左边的,只有非静态的成员方法,编译看左边,运行看右边  

经典案例分析

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复制代码

分析这个题须要记住如下几点

当父类对象引用变量引用子类对象时,被引用对象的类型决定了调用谁的成员方法,
引用变量类型决定可调用的方法。若是子类中没有覆盖该方法,那么会去父类中寻找。复制代码

先看一个例子

class X {
    public void show(Y y){
        System.out.println("x and y");
    }

    public void show(){
        System.out.println("only x");
    }
}

class Y extends X {
    public void show(Y y){
        System.out.println("y and y");
    }
    public void show(int i){
        
    }
}

class main{
    public static void main(String[] args) {
        X x = new Y();
        x.show(new Y());
        x.show();
    }
}
//结果
//y and y
//only x
复制代码

其实在继承链中对象方法的调用存在一个优先级:this.show(O)、super.show(O)、
this.show((super)O)、super.show((super)O)。复制代码

Y继承了X,覆盖了X中的show(Y y)方法,可是没有覆盖show()方法。

 这个时候,引用类型为X的x指向的对象为Y,这个时候,调用的方法由Y决定,会先从Y中寻找。执行x.show(new Y());,该方法在Y中定义了,因此执行的是Y里面的方法; 

可是执行x.show();的时候,有的人会说,Y中没有这个方法啊?它好像是去父类中找该方法了,由于调用了X中的方法。 

事实上,Y类中是有show()方法的,这个方法继承自X,只不过没有覆盖该方法,因此没有在Y中明确写出来而已,看起来像是调用了X中的方法,实际上调用的仍是Y中的。

 这个时候再看上面那句难理解的话就不难理解了吧。X是引用变量类型,它决定哪些方法能够调用;show()和show(Y y)能够调用,而show(int i)不能够调用。Y是被引用对象的类型,它决定了调用谁的方法:调用y的方法。 


abcd的关系是这样的:C/D ---> B ---> A

  1.  a1是A类的一个实例化对象,因此this指向A,而后查找this.show(b),因为没有这个方法,因此到super.show(b),可是因为A类没有父类了,因此到this.show(super b),因为b的父类是A,因此至关于this.show(A),而后在A类中查找到了这个方法,因而输出A and A。 
  2.  a1是A类的实例化对象,因此this指向A,而后在A类中查找this.show(C)方法,因为没有这个方法,因此到了super.show(C),因为A类的父类里面找,可是A没有父类,因此到了this.show(super C),因为C的父类是B因此在A类里面查找this.show(B)方法,也没找到,而后B也有父类,就是A,因此查找this.show(A),找到了,因而输出A and A;
  3. a1是A类的实例化对象,因此this指向A,而后在A类中找到this.show(D)方法,找到了,因此就输出A and D;
  4.  a2是B类的引用对象,类型为A,因此this指向A类,而后在A类里面找this.show(B)方法,没有找到,因此到了super.show(B),因为A类没有父类,因此到了this.show(super B),B的父类是A,即super B = A,因此执行方法this。show(A),在A方法里面找show(A),找到了,可是因为a2是一个类B的引用对象,而B类里面覆盖了A类的show(A)方法,因此最终执行的是B类里面的show(A)方法,即输出B and A; 
  5.  a2是B类的引用对象,类型为A,因此this指向A类,而后在A类里面找this.show(C)方法,没有找到,因此到了super.show(C)方法,因为A类没有父类,因此到了this.show(super C),C的父类是B,因此在A类里面找show(B),一样没有找到,发现B还有父类,即A,因此还继续在A类里面找show(A)方法,找到了,可是因为a2是一个类B的引用对象,而B类里面覆盖了A类的show(A)方法,因此最终执行的是B类里面的show(A)方法,即输出B and A;
  6. a2是B类的引用对象,类型为A,因此this指向A类,而后在A类里面找this.show(D)方法,找到了,可是因为a2是一个类B的引用对象,因此在B类里面查找有没有覆盖show(D)方法,没有,因此执行的是A类里面的show(D)方法,即输出A and D;
  7. b是B类的一个实例化对象,首相执行this.show(B),在B类里面找show(B)方法,找到了,直接输出B and B;
  8. b是B类的一个实例化对象,首相执行this.show(C),在B类里面找show(C)方法,没有找到,因此到了super.show(c),B的父类是A,因此在A类中找show(C)方法,没有找到,因而到了this.show(super C),C的父类是B,因此在B类中找show(B)f方法,找到了,因此执行B类中的show(B)方法输出B and B;
  9.  b是B类的一个实例化对象,首相执行this.show(D),在B类里面找show(D)方法,没有找到,因而到了super.show(D),B的父类是A类,因此在A类里面找show(D)方法,找到了,输出A and D;

总结

本篇文章的内容大致上就是这些了。咱们来总结一下。

  1. 多态,简而言之就是同一个行为具备多个不一样表现形式或形态的能力。
  2. 多态的分类:运行时多态和编译时多态。
  3. 运行时多态的前提:继承(实现),重写,向上转型
  4. 向上转型与向下转型。
  5. 继承链中对象方法的调用的优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
相关文章
相关标签/搜索