夯实Java基础(四)——面向对象之多态

一、多态介绍

面向对象三大特征:封装、继承、多态。多态是Java面向对象最核心,最难以理解的内容。从必定角度来看,封装和继承几乎都是为多态而准备的。java

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

好比有一种动物,藏在某个看不见得地方,你知道它是一种动物,可是不知道具体是哪一种动物,只有等它发出叫声才能辨别,是猫——喵喵喵,是狗——旺旺旺,你一听就知道这是什么动物,对于不一样的动物会有不一样的结果,能够理解为多态。测试

Java多态也能够用一句话表示:父类的引用指向子类的对象。ui

多态存在的三个必要条件this

一、继承:在多态中必须存在有继承关系的子类和父类。spa

二、重写:子类对父类中某些方法进行从新定义,在调用这些方法时就会调用子类的方法。.net

三、向上转型:在多态中须要将子类的引用赋给父类对象,只有这样该引用才可以具有技能调用父类的方法和子类的方法。code

二、多态的实现

 开头说了这么多Java多态的基本概念,那么到底用Java代码怎么体现出来呢?下面咱们直接看代码。对象

public class Animal {

    public void eat(){
        System.out.println("动物吃东西");
    }

    public void skill(){
        System.out.println("动物有本领");
    }
}

class Dog extends Animal{

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

    public void skill(){
        System.out.println("狗会看家");
    }
}

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

    public void skill(){
        System.out.println("猫会抓老鼠");
    }
}

//测试类
public class AnimalTest {
    
    public static void main(String[] args) {
        Animal dog=new Dog();
        dog.eat();
        dog.skill();
        Animal cat=new Cat();
        cat.eat();
        cat.skill();
    }
}

运行结果:blog

从上面的测试类来看,咱们都是建立不一样子类的对象,相同的父类引用,却表现出它们不一样的特征,这就是体现了Java的多态性。

若是你认为这样仍是不能体现多态的好处,咱们在AnimalTest类中添加一个show()方法,来体会Java多态的好处。

若是Java中没有多态的特性会是什么样的,下面咱们来看一下。

public class AnimalTest {

    public static void main(String[] args) {

        AnimalTest animalTest=new AnimalTest();
        animalTest.show(new Animal());
        animalTest.show(new Dog());
        animalTest.show(new Cat());
    }

    public void show(Animal animal){
        animal.eat();
        animal.skill();
    }

    public void show(Dog dog){
        dog.eat();
        dog.skill();
    }

    public void show(Cat cat){
        cat.eat();
        cat.skill();
    }
}

能够发如今show()方法中的形参都传入了对象,并且重载3个相同的show()方法,若是咱们有十个百个千个类须要传入方法,那么岂不是要重载上千个方法,可见这样代码的冗余很是的大,很是不利于代码的维护。

而若是有多态的话,只需写一个show()方法便可。

public class AnimalTest {

    public static void main(String[] args) {

        AnimalTest animalTest=new AnimalTest();
        animalTest.show(new Animal());
        animalTest.show(new Dog());
        animalTest.show(new Cat());
    }

    public void show(Animal animal){
        animal.eat();
        animal.skill();
    }
}

从这里能够看出来多态的优势:

一、减小重复代码,使代码变得简洁(由继承保证)。

二、提升了代码的扩展性(由多态保证)。

可是也有缺点:子类单独定义的方法会丢失。后面的向上转型会介绍到。

多态实际上是一种虚拟方法调用。在编译期间,只能调用父类中声明的方法,可是在运行期间,实际执行的是子类重写父类的方法。

总结为一句话:编译看左边,运行看右边(可能看到这句话会有点头晕,可是理解下面向上转型的概念就应该可以理解这句话了)。

三、向上转型

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

咱们用前面多态的例子举例,只是在子类中添加了它们本身的方法,父类中没有定义,以下。

public class Animal {

    public void eat(){
        System.out.println("动物吃东西");
    }

    public void skill(){
        System.out.println("动物有本领");
    }
}

class Dog extends Animal{

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

    public void skill(){
        System.out.println("狗会看家");
    }
    //新添加的方法
    public void run(){
        System.out.println("狗跑得快");
    }
}

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

    public void skill(){
        System.out.println("猫会抓老鼠");
    }
    //新添加的方法
    public void life(){
        System.out.println("猫有九条命");
    }
}

//测试类
public class AnimalTest {
    public static void main(String[] args) {
        Animal dog=new Dog();//向上转型成Animal
        dog.eat();
        dog.skill();
        //dog.run();//Cannot resolve method 'run()'
        Animal cat = new Cat();//向上转型成Animal
        cat.eat();
        cat.skill();
        //cat.life();//Cannot resolve method 'life()'
    }
}

这里就产生了向上转型,Animal dog= new Dog();Animal cat= new Cat();将子类对象Dog和Cat转化为父类对象Animal。这个时候Animal这个引用调用的都是子类方法。再去调用子类单独的方法就会报错。若是非要调用也不是说不能够,那就要强转了。既然如今已是父类了,那就强转为子类呗。

((Dog) dog).run();

((Cat) cat).life();

这样也能够调用。可是千万要注意,不能这样转,子类引用不能指向父类对象,Dog dog=(Dog)newAnimal();Cat cat = (Cat)new Animal();这样是绝对不行的。就好像儿子能够生出爸爸同样,这样不和常理。

若是在向上转型时,子类并无重写父类的方法,那么调用的就是父类中的方法。

到此为止,也能够证实前面说的一个结论:向上转型会使子类单独定义的方法会丢失。

四、向下转型

与向上转型相对应的就是向下转型了。向下转型是把父类对象转为子类对象。这里咱们就会想到,即然子类向上转型为了父类,而子类又继承了父类的属性和方法,为何还要将父类转型为子类。是由于对象的多态性只适用于方法,而不适用于属性。因此当咱们在使用多态的时候,就不能调用子类中的属性和特有的方法了,因此须要向下转型。(内存中其实是加载了子类所特有的属性和方法,可是因为变量声明的是父类类型,致使在编译时只能调用父类中声明的属性和方法,子类特有的属性和方法不能调用)

咱们仍是使用向上转型那里的代码为例(Anima、Dog、Cat类):

public class AnimalTest {
    public static void main(String[] args) {
        Animal dog=new Dog();//Dog向上转型成Animal
        Dog dog1= (Dog) dog;//向下转型为Dog
        dog1.eat();
        dog1.skill();

        Cat cat=(Cat) dog;//java.lang.ClassCastException: com.thr.java2.Dog cannot be cast to com.thr.java2.Cat
        cat.eat();
        cat.skill();
    }
}

运行结果:

咱们能够发现向下转型为Dog没有报错,可是转型为Cat却报错了,这个倒不难理解,由于开始向上转型原本是Dog,而后再变回Dog,总不能Dog变成Cat吧。因此会报类型转换错误。

向下转型注意事项

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

向下转型咱们通常会使用 instanceof 关键字来判断:

使用方法:a instanceof A:判断对象a是否为对象A的实例,若是是,返回true,若是不是,则返回false。

public class AnimalTest {
    public static void main(String[] args) {
        AnimalTest test=new AnimalTest();
        test.show(new Animal());
        test.show(new Dog());
        test.show(new Cat());
    }

    public void show(Animal animal){
        if (animal instanceof Dog){
            Dog dog= (Dog) animal;
            dog.eat();
            dog.skill();
            dog.run();
        }
    }
}

运行结果:

咱们能够发现测试方法调用三次show方法,分别传入了Animal、Dog、Cat对象,因为show()方法只判断了Dog是不是该对象,因此Dog返回了true,输出了Dog的信息,而其余的返回了false,则没有输出而后信息。

五、经典案例

 Java的多态和转型都了解之后,如今趁热打铁,来点网上多态很是经典的例题:

来源:http://www.javashuo.com/article/p-qfgsjwub-hv.html

    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));        
        }  
    }  

运行的结果:

前面3个强行发现还能获得答案,可是从第4个以后就有点头晕。

咱们来慢慢分析第4个:首先是子类B类向上转型为父类A,而子类B有重写了父类A中的show(A obj)方法,因此a2变量能调用的只有父类A类中的show(D obj)和子类B中的show(A obj),而B是继承自A类的,D继承自B类,因此不可能调用show(D obj)方法,因此结果是4--B and A;剩下的依次类推。

当父类对象变量引用子类对象时,被引用对象的类型决定了调用谁的成员方法,引用变量类型决定可调用的方法。若是子类中没有覆盖该方法,那么会去父类中寻找。可是它仍然要根据继承链中方法调用的优先级来确认方法,该优先级为:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。

六、总结

经过上面对多态的学习,能够小结一下概念:

一、Java多态也能够用一句话表示:父类的引用指向子类的对象。

二、运行时多态的前提:继承,重写,向上转型。

三、多态可以减小重复代码,使代码变得简洁;提升了代码的扩展性。

四、多态实际上是一种虚拟方法调用。归结为一句话:编译看左边,运行看右边。

五、向上转型就是是将子类对象转为父类对象

六、继承链中对象方法的调用的优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。

相关文章
相关标签/搜索