Java 多态

1.什么是多态?
  多态是属于面向对象的三大特征之一,他的前提是封装形成独立体,独立体之间存在继承的关系,从而产生多态机制。
  多态是同一个行为具有多个不同表现形式或者说形态的能力。
 
2.多态就是“同一个行为”发生在“不同的对象上”会产生不同的效果。那么在java中多态是如何体现的呢?
  在java中允许这样的两种语法出现:
  一种是向上转型(Upcasting),向上转型是指子类型转换为父类型,又被称为自动类型转换
  一种是向下转型(Downcasting),向下转型是指父类型转换为子类型,又被称为强制类型转换。

 

在java语言中有这样的一个规定:

  无论是向上转型还是向下转型,两种类型之间必须要有继承关系,没有继承关系情况下进行向上转型或向下转型的时候编译器都会报错,这一点需要死记硬背! 
 
例子: 
  创建一个动物类
public class Animal {
    public void move(){
        System.out.println("动物正在在移动!");
    }
}

  猫类继承动物类

public class Cat extends Animal{

    //方法覆盖
    public void move(){
        System.out.println("猫咪正在移动!");
    }

    //特有方法抓老鼠
    public void catchMouse(){
        System.out.println("猫咪会抓老鼠");
    }
}

  鸟类继承动物类

public class Bird extends Animal{
    @Override
    public void move() {
        System.out.println("鸟儿在移动!");
    }
    //特有方法
    public void fly(){
        System.out.println("鸟儿在飞翔!");
    }
}

  测试:

public class Test {
    public static void main(String[] args) {
        Animal a1 = new Cat();
        a1.move();

        Animal a2 = new Bird();
        a2.move();

    }
} 
  运行结果是:
        猫咪正在移动!
        鸟儿在移动!
 
以上程序演示的就是多态,多态就是“同一个行为(move)”作用在“不同的对象上”会有不同的表现结果。
java中之所以有多态机制,是因为java允许一个父类型的引用指向一个子类型的对象。
也就是说允许这种写法:
  Animal a2 = new Bird(),
因为
  Bird is a Animal是能够说通的。
 
其中
  Animal a1 = new Cat()
  Animal a2 = new Bird()     
都是父类型引用指向了子类型对象,都属于向上转型(Upcasting),或者叫做自动类型转换。
 
我来解释一下这段代码片段
 
  Animal a1 = new Cat();
  a1.move(); 
 
  Java程序包括编译和运行两个阶段,分析Java程序一定要先分析编译阶段,然后再分析运行阶段,在编译阶段编译器只知道a1变量的数据类型是Animal,那么此时编译器会去Animal.class字节码中查找move()方法,发现Animal.class字节码中存在move()方法,然后将该move()方法绑定到a1引用上,编译通过了,这个过程我们可以理解为“静态绑定”阶段完成了。
  紧接着程序开始运行,进入运行阶段,在运行的时候实际上在堆内存中new的对象是Cat类型,也就是说真正在move移动的时候,是Cat猫对象在移动,所以运行的时候就会自动执行Cat类当中的move()方法,这个过程可以称为“动态绑定”。
  但无论是什么时候,必须先“静态绑定”成功之后才能进入“动态绑定”阶段。
public class Test {
    public static void main(String[] args) {
         Animal a = new Cat();
         a.catchMouse();
    }
}

  

  有人认为Cat猫是可以抓老鼠的呀,为什么会编译报错呢?
  那是因为“Animal a = new Cat();”在编译的时候,编译器只知道a变量的数据类型是Animal,也就是说它只会去Animal.class字节码中查找catchMouse()方法,结果没找到,自然“静态绑定”就失败了,编译没有通过。就像以上描述的错误信息一样:在类型为Animal的变量a中找不到方法catchMouse()。
 
public class Test {

    public static void main(String[] args) {
        //向上转型
        Animal a = new Cat();
        //向下转型,为了调用子类中的特有方法
        Cat c = (Cat)a;
        c.catchMouse();
    }
}
运行结果:
 
  
  
  我们可以看到直接使用a引用是无法调用catchMouse()方法的
  因为这个方法属于子类Cat中特有的行为,不是所有Animal动物都可以抓老鼠的,要想让它去抓老鼠,就必须做向下转型(Downcasting),也就是使用强制类型转换将Animal类型的a引用转换成Cat类型的引用c(Cat c = (Cat)a;),使用Cat类型的c引用调用catchMouse()方法。
  通过这个案例,可以得出:只有在访问子类型中特有数据的时候,需要先进行向下转型。向下转型会有什么风险嘛?
  来看这个例子:
public class Test {

    public static void main(String[] args) {
        Animal a = new Bird();
        Cat c = (Cat)a;
    }
}

  以上的异常是很常见的ClassCastException,翻译为类型转换异常,这种异常通常出现在向下转型的操作过程当中,当类型不兼容的情况下进行转型出现的异常,之所以出现此异常是因为在程序运行阶段a引用指向的对象是一只小鸟,然后我们要将一只小鸟转换成一只猫,这显然是不合理的,因为小鸟和猫之间是没有继承关系的。
  为了避免这种异常的发生,建议在进行向下转型之前进行运行期类型判断,这就需要我们学习一个运算符了,它就是instanceof。
  
  instanceof运算符的语法格式是这样的:
  (引用 instanceof 类型)
  instanceof  运算符的运算结果是布尔类型,可能是true,也可能是false,
  假设(c instanceof Cat)结果是true则表示在运行阶段c引用指向的对象是Cat类型,如果结果是false则表示在运行阶段c引用指向的对象不是Cat类型。
  有了instanceof运算符,向下转型就可以这样写了:
public class Test {

    public static void main(String[] args) {
        Animal a = new Bird();
        if(a instanceof Cat) {
            Cat c = (Cat)a;
            c.catchMouse();
        }
    }
}
  以上程序运行之后不再发生异常,并且什么也没有输出,那是因为if语句的条件并没有成立,因为在运行阶段a引用指向的对象不是Cat类型,所以(a instanceof Cat)是false,自然就不会进行向下转型了,也不会出现ClassCastException异常了。在实际开发中,java中有这样一条默认的规范需要大家记住:在进行任何向下转型的操作之前,要使用instanceof进行判断,这是一个很好的编程习惯。
 
总结:
  
  多态存在的三个比必要条件是:
    1.继承
    2.方法覆盖
    3.父类型引用指向子类型对象
  
总结:
  多态显然是离不开方法覆盖机制的,多态就是因为编译阶段绑定父类当中的方法,程序运行阶段自动调用子类对象上的方法,如果子类对象上的方法没有进行重写,这个时候创建子类对象就没有意义了,自然多态也就没有意义了,只有子类将方法重写之后调用到子类对象上的方法产生不同效果时,多态就形成了。
  实际上方法覆盖机制和多态机制是捆绑的,谁也离不开谁,多态离不开方法覆盖,方法覆盖离开了多态也就没有意义了。
  静态方法的“覆盖是没有意义的”,静态方法的执行与对象无关,既然和对象无关,那么就标识和多态无关。
 
那么问题来了,java中多态的作用是什么呢?
  1.降低程序的耦合度,提高程序的扩展力
  2.能使用多态尽量多使用多态
  3.父类型引用指向子类型对象
 
  核心是:面向抽象编程,尽量不要面向具体编程。
 
下面写一个例子:
 
  主人类
public class Master {
    //Master主人类面对的是一个抽象的pet,不面向具体的宠物
    public void feed(Pet pet){
        //Pet pet是一个父类型的引用
        // 传进来的可以是Pet pet = new Cat(),Pet pet = new Dog(),
        //父类型的引用指向了子类型的对象
        System.out.println("主人开始喂食了");
        pet.eat();
        System.out.println("主人喂食完毕了");
    }
}
  宠物类:
public class Pet {
    String name;//宠物有名字
    public void eat(){//宠物有吃的方法
    }
}
  狗类:继承宠物类,并重写eat的方法
public class Dog extends Pet {
    public Dog(String name){
        this.name = name;
    }
    public void eat(){
        System.out.println(this.name +"吃骨头!");
    }
    //小狗特有的run方法
    public void run(){
     System.out.println(this.name + "再跑")       
    }
}
  猫类:继承宠物类,并重写eat方法
public class Cat extends Pet {
    public Cat(String name) {
        this.name = name;
    }
    public void eat(){
        System.out.println(this.name + "正在吃鱼!");
    }
}
  测试类:
  
public class Test {
    public static void main(String[] args) {

          Dog dog = new Dog("小花");
          Master master = new Master();
          master.feed(dog);

          Cat cat1 = new Cat("Tom");
          master.feed(cat1);

          Pig pig1 = new Pig("梨花");
          master.feed(pig1);
    }
}
运行结果:
  主人开始喂食了
  小花吃骨头!
  主人喂食完毕了
 
  主人开始喂食了
  Tom正在吃鱼!
  主人喂食完毕了
 
  主人开始喂食了
  梨花在吃粥!
  主人喂食完毕了
posted @ 2021-10-30 12:18  程序员hg  阅读(266)  评论(0编辑  收藏  举报