Java 多态(什么是多态?)

27 篇文章 2 订阅
14 篇文章 0 订阅


承接上篇 链接: link.

向上转型

通常我们在实例化对象的时候都是通过new关键字来做的,来调用类内的方法。

public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal("animal");
        animal.eat();
        Dog dog = new Dog("dog");
        dog.eat();
        dog.bark();
        Cat cat = new Cat("cat");
        cat.eat();
        Bird bird = new Bird("bird");
        bird.eat();
        bird.fly();
    }
}

//执行结果
animal正在吃
dog正在吃
dog正在汪汪汪
cat正在吃
bird正在吃
bird正在飞 ︿( ̄︶ ̄)︿

那么什么是向上转型呢?

直接赋值法

public class Main {
    public static void main(String[] args) {
        Animal bird = new Bird("Bird"); //可以new Bird对象用Animal接收
        Animal dog = new Dog("Dog");
        
        /* 也可以这样直接赋值
		Bird bird = new Bird("Bird");
		Animal bird1 = bird;
		Dog dog = new Dog("Dog");
		Animal dog1 = dog;
        /*
        此时 bird和 dog 是一个父类 (Animal) 的引用, 指向一个子类 Bird 与 dog的实例. 这种写法称为向上转型
         */
        bird.eat();//Bird正在吃
        dog.eat();//Dog正在吃
        dog.fly(); //报错
        bird.bark(); //报错
        /*
        为啥会报错,因为此时是父类Animal的引用,fly与bark是bird与dog特有的属性方法,通过animal引用是不能够访问的。
        */
    }
}

方法传参法

public class Main {
    public static void func(Animal animal) {
        animal.eat();
    }
    public static void main(String[] args) {

        Dog dog = new Dog("狗狗");
        Bird bird = new Bird("鸟");
        func(dog);
        func(bird);
//执行结果
狗狗正在吃
鸟正在吃
    }
}

方法返回

public class Main {
    public static Animal func() {
        Bird bird = new Bird("鸟");
        return bird;
    }
    public static Animal func1() {
        Dog dog = new Dog("狗狗");
        return dog;
    }
    public static void main(String[] args) {

        Animal bird = func();
        Animal dog = func1();
        bird.eat();
        dog.eat();
        
//执行结果
鸟正在吃
狗狗正在吃
    }
}

向下转型

向上转型是子类对象转成父类对象, 向下转型就是父类对象转成子类对象. 相比于向上转型来说, 向下转型没那么常见,但是也有一定的用途。

Animal bird = new Bird("Bird"); //可以new Bird对象用Animal接收
Animal dog = new Dog("Dog");
dog.fly(); //报错
bird.bark(); //报错

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

    public static void main(String[] args) {
        Animal animal = new Bird("鸟");
        Bird bird = (Bird)animal;//强制类型转换
        bird.fly();
        bird.eat();
        Animal animal1 = new Dog("狗狗");
        Dog dog = (Dog)animal1;强制类型转换
        dog.bark();
        dog.eat();

// 执行结果
鸟正在飞 ︿( ̄︶ ̄)︿
鸟正在吃
狗狗正在汪汪汪
狗狗正在吃

//一下示例错误写法
//编译的时候不会报错  但是运行时出错了
    Bird bird = (Bird) new Animal("鸟");
    bird.eat();
    bird.fly();
    Dog dog = (Dog) new Animal("狗狗");
    dog.eat();
    dog.bark();
    
//Exception in thread "main" java.lang.ClassCastException: animals.Animal cannot be cast to animals.Bird
	//at animals.Main.main(Main.java:22)

//还有这样也是错误的
Animal animal = new Dog("狗狗");
Bird bird = (Bird)animal;
bird.fly();

// 执行结果, 抛出异常
Exception in thread "main" java.lang.ClassCastException: Dog cannot be cast to Bird
at Test.main(Test.java:35)

方法重写

针对刚才的 eat 方法来说:
子类实现父类的同名方法, 并且参数的类型个数完全相同, 这种情况称为 覆写/重写/覆盖(Override)。

  1. 重写和重载完全不一样. 不要混淆
  2. 普通方法可以重写, static 修饰的静态方法不能重写.
  3. 重写中子类的方法的访问权限不能低于父类的方法访问权限.
  4. 重写的方法返回值类型不一定和父类的方法相同(但是建议最好写成相同, 特殊情况除外).
public class Animal {
	public void eat() {
	...
	}
}

public class Bird extends Animal {
	public void eat() {
	...
	}
}

public class Dog extends Animal {
	public void eat() {
	...
	}
}


//方法权限示例: 将子类的 eat 改成 private
public class Bird extends Animal {
	// 将子类的 eat 改成 private
	private void eat() {
	...
	}
}

// 编译出错
Error:(8, 10) java: com.bit.Bird中的eat(java.lang.String)无法覆盖com.bit.Animal中的
eat(java.lang.String)
//正在尝试分配更低的访问权限; 以前为public


//另外, 针对重写的方法, 可以使用 @Override 注解来显式指定.
// Bird.java
public class Bird extends Animal {
	@Override
	private void eat(String food) {
	...
	}
}
/*
有了这个注解能帮我们进行一些合法性校验. 例如不小心将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发
现父类中没有 aet 方法, 就会编译报错, 提示无法构成重写.
我们推荐在代码中进行重写方法时显式加上 @Override 注解.
*/

在这里插入图片描述

super 关键字

前面的代码中由于使用了重写机制, 调用到的是子类的方法. 如果需要在子类内部调用父类方法怎么办? 可以使用super 关键字

public class Animal {
    protected String name;

    public Animal(String name) {
        this.name = name;
    }

    public void eat() {
        System.out.println( "animal 正在吃");
    }

}

public class Bird extends Animal{
    //使用了 super 来调用父类的构造器
    public Bird(String name) {
        super(name);
    }
    
    @Override
	public void eat() {
	// 修改代码, 让子调用父类的接口.
	super.eat(food);
	System.out.println("我是一只小鸟");
	System.out.println(this.name + "正在吃" + food);
	/*
	在这个代码中, 如果在子类的 eat 方法中直接调用 eat (不加super), 那么此时就认为是调用子类自己的 eat (也就是递
归了). 而加上 super 关键字, 才是调用父类的方法
	*/
	}
}

public class Main {
    public static void main(String[] args) {

        Bird bird = new Bird("鸟");
        bird.eat();
    }
}    
//执行结果
animal 正在吃  //调用父类的方法
我是一只小鸟
鸟正在吃

在这里插入图片描述

理解多态

例:打印多种形状

class Shape {
    //啥都不用写
    public void draw() {

    }
}
class Cycle extends Shape {
    @Override
    public void draw() {
        System.out.println("○");
    }
}
class Rect extends Shape {
    @Override
    public void draw() {
        System.out.println("□");
    }
}
class Flower extends Shape {
    @Override
    public void draw() {
        System.out.println("♣");
    }
}
public class Test {
    // 打印单个图形
    public static void drawMap(Shape shape) {
        shape.draw();
    }
    public static void main(String[] args) {
        Shape shape1 = new Flower();
        Shape shape2 = new Cycle();
        Shape shape3 = new Rect();
        drawMap(shape1);
        drawMap(shape2);
        drawMap(shape3);
    }
}

//执行结果
♣
○
□

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

使用多态的好处是什么?

  1. 类调用者对类的使用成本进一步降低.
    封装是让类的调用者不需要知道类的实现细节.
    多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可.
    因此, 多态可以理解成是封装的更进一步, 让类调用者对类的使用成本进一步降低.
    这也贴合了 <<代码大全>> 中关于 “管理代码复杂程度” 的初衷.
  2. 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else

例如我们现在需要打印的不是一个形状了, 而是多个形状. 如果不基于多态, 实现代码如下:

public class Test {
    
    public static void drawShapes() {
        Rect rect = new Rect();
        Cycle cycle = new Cycle();
        Flower flower = new Flower();
        String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"};
        for (String shape : shapes) {
            if (shape.equals("cycle")) {
                cycle.draw();
            } else if (shape.equals("rect")) {
                rect.draw();
            } else if (shape.equals("flower")) {
                flower.draw();
            }
        }
    }
    public static void main(String[] args) {
        drawShapes();
    }
}

//执行结果
○
□
○
□
♣

如果使用使用多态, 则不必写这么多的 if - else 分支语句, 代码更简单

public class Test {

    public static void drawShapes() {
        // 我们创建了一个 Shape 对象的数组.
        Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),
                new Rect(), new Flower()};
        for (Shape shape : shapes) {
            shape.draw();
        }
    }
    public static void main(String[] args) {
        drawShapes();
    }
}

//执行结果
○
□
○
□
♣
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值