必须学习的Java多态知识


向上转型

父类的引用指向了一个子类的对象(看起来就好像把)

Cat cat=new Cat();
Animal animal=null;
//向上转型
animal=cat;

上述代码相当于:

Animal animal=new Cat();

向上转型,也可能发生在方法传参的过程中

public class Test {
    
     
	 public static void main(String[] args) {
    
     
	 	Bird bird = new Bird("圆圆"); 
	 	feed(bird); 
	 } 
	 public static void feed(Animal animal) {
    
     
	 	animal.eat("谷子"); 
	 } 
}

也可能发生在方法返回的时候

public class Test {
    
     
	 public static void main(String[] args) {
    
     
	 Bird bird = new Bird("圆圆"); 
	 	feed(bird); 
	 } 
	 public static void feed(Animal animal) {
    
     
	 	animal.eat("谷子"); 
	 } 
}

动态绑定

如果父类中包含的方法在子类中有对应的同名同参数的方法,就会进行动态绑定
在这里插入图片描述
如果eat方法只在父类中存在,此时调用的eat就是父类的这个eat方法(不涉及动态绑定)

public class Animal {
    
    
    public void eat(String food){
    
    
        System.out.println("Animal正在吃"+food);
    }
}
public class Cat extends Animal{
    
    
//    public void eat(String food){
    
    
//        System.out.println("Cat正在吃"+food);
//    }
}
public class Test {
    
    
    public static void main(String[] args) {
    
    
        Animal animal=new Cat();
        animal.eat("鱼");
    }
}

如果eat方法只在子类中存在,此时调用的eat就会编译报错(也不涉及动态绑定)

public class Animal {
    
    
//   public void eat(String food){
    
    
//      System.out.println("Animal正在吃"+food);
//    }
}
public class Cat extends Animal{
    
    
    public void eat(String food){
    
    
        System.out.println("Cat正在吃"+food);
    }
}
public class Test {
    
    
    public static void main(String[] args) {
    
    
        Animal animal=new Cat();
        animal.eat("鱼");//出错
    }
}

如果 eat方法在父类和子类中都存在,并且参数也相同,此时调用eat就会涉及到"动态绑定"

public class Animal {
    
    
    public void eat(String food){
    
    
        System.out.println("Animal正在吃"+food);
    }
}
public class Cat extends Animal{
    
    
    public void eat(String food){
    
    
        System.out.println("Cat正在吃"+food);
    }
}
public class Test {
    
    
    public static void main(String[] args) {
    
    
    	//Animal animal=new Animal();
        Animal animal=new Cat();
        animal.eat("鱼");
    }
}

在程序运行时,看animal究竟是指向一个父类的实例还是子类的实例,指向父类实例就调用父类版本的eat方法,指向子类实例就调用子类版本的eat方法
如果eat方法在父类和子类中都存在,并且参数不同,此时调用eat,也不涉及"动态绑定",看起来像重载,但又不太一样
在这里插入图片描述
注解:需要给子类的方法上加上注解,显示的告诉编译器,当前这个子类的方法是重写了父类的方法 ,如果没有这个注解,其实代码也能编译运行
加了这个注解之后,编译器就能给我们检查和校验工作
在这里插入图片描述

方法重写

针对刚才的 eat 方法来说:
子类实现父类的同名方法, 并且参数的类型和个数完全相同, 这种情况称为 覆写/重写/覆盖(Override).
关于重写的注意事项
1. 重写和重载完全不一样. 不要混淆
2. 普通方法可以重写, static 修饰的静态方法不能重写.
比特科技
3. 重写中子类的方法的访问权限不能低于父类的方法访问权限.
4. 重写的方法返回值类型不一定和父类的方法相同(但是建议最好写成相同, 特殊情况除外).

不相同的有些情况也可以,但是返回值类型差别太大就报错

多态

当类的调用者在编写 draw 这个方法的时候, 参数类型为 Shape (父类), 此时在该方法内部并不知道, 也不关注当前的 shape 引用指向的是哪个类型(哪个子类)的实例. 此时 shape 这个引用调用 draw 方法可能会有多种不同的表现(和 shape 对应的实例相关), 这种行为就称为多态

public class Shape {
    
    
    public void draw(){
    
    

    }
}
public class Circle extends Shape{
    
    
    public void draw(){
    
    
        System.out.println("o");
    }
}
public class Rect extends Shape{
    
    
    public void draw(){
    
    
        System.out.println("□");
    }
}
public class Flower extends Shape{
    
    
    public void draw(){
    
    
        System.out.println("❀");
    }
}
public class Test {
    
    
    public static void main(String[] args) {
    
    
        Shape  shape1=new Circle();
        Shape  shape2=new Rect();
        Shape  shape3=new Flower();

        draw(shape1);
        draw(shape2);
        draw(shape3);
    }
    public static void draw(Shape shape){
    
    
        shape.draw();
    }
}

使用多态的好处是什么?

  1. 类调用者对类的使用成本进一步降低.
  2. 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else
    3)可扩展能力更强

向下转型

应用场景:有些方法只是在子类中存在,但是父类中不存在,此时使用多态的方式就无法执行到对应的子类的方法了,就必须把父类引用先转回成子类的引用,然后再调用对应的方法

// Animal.java 
public class Animal {
    
     
 		protected String name; 
 	public Animal(String name) {
    
     
 		this.name = name; 
 	} 
 	public void eat(String food) {
    
     
 		System.out.println("我是一只小动物"); 
 		System.out.println(this.name + "正在吃" + food); 
 	} 
} 
// Bird.java 
public class Bird extends Animal {
    
     
	 public Bird(String name) {
    
     
	 	super(name); 
	 } 
	 public void eat(String food) {
    
     
	 	System.out.println("我是一只小鸟"); 
	 	System.out.println(this.name + "正在吃" + food); 
	 } 
	 
	 public void fly() {
    
     
	 	System.out.println(this.name + "正在飞");
	 }
 }
animal.fly(); 
// 编译出错
//找不到 fly 方法

注意事项
编译过程中, animal 的类型是 Animal, 此时编译器只知道这个类中有一个 eat 方法, 没有 fly 方法.
虽然 animal 实际引用的是一个 Bird 对象, 但是编译器是以 animal 的类型来查看有哪些方法的.
对于 Animal animal = new Bird(“圆圆”) 这样的代码,
编译器检查有哪些方法存在, 看的是 Animal 这个类型
执行时究竟执行父类的方法还是子类的方法, 看的是 Bird 这个类型.
那么想实现刚才的效果, 就需要向下转型

// (Bird) 表示强制类型转换
Bird bird = (Bird)animal; 
bird.fly(); 
// 执行结果
圆圆正在飞

所以, 为了让向下转型更安全, 我们可以先判定一下看看 animal 本质上是不是一个 Bird 实例, 再来转换

Animal animal=new Cat("小猫");
if(animal instanceof Bird){
    
    
	Bird bird=animal;
	bird.fly();
}

instanceof 可以判定一个引用是否是某个类的实例. 如果是, 则返回 true. 这时再进行向下转型就比较安全了

对象之间的比较有三种形式:
1.比较值 String,equals
2.比较身份 ==
3.比较类型 instanceof

猜你喜欢

转载自blog.csdn.net/Hedenghui777/article/details/121588511