目录
1. 能够降低代码的 "圈复杂度", 避免使用大量的 if - else
1.概念
多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
多态的概念是有些抽象的,先了解下面的一些知识点,再回来重新阅读概念,会有新的理解。
2.多态发生的条件
知识准备:什么是重写?
重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程 进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
拿动物类举例:
public class Animal {
public int age;
public String name;
public Animal(int age, String name) {
this.age = age;
this.name = name;
}
public void eat(){
System.out.println(this.name+"正在吃饭");
}
}
public class Cat extends Animal{
public Cat(int age, String name) {
super(age, name);
}
@Override
public void eat() {
System.out.println(this.name + "正在吃猫粮");
}
public void mew(){
System.out.println(this.name+"喵喵叫");
}
}
public class Dog extends Animal{
public Dog(int age, String name) {
super(age, name);
}
@Override
public void eat() {
System.out.println(this.name + "正在吃狗粮");
}
public void bark(){
System.out.println(this.name+"汪汪叫");
}
}
public class Test {
public static void main(String[] args) {
Animal animal1 = new Dog(12,"菜询困");
Animal animal2 = new Cat(21,"啥鸟");
animal1.eat();
animal2.eat();//调用了子类重写的方法
}
}
对于Animal类的eat方法进行了重写。
运行结果:
重写的规则:
子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致
被重写的方法返回值类型可以不同,但是必须是具有父子关系的
访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为 protected
父类被static、private修饰的方法、构造方法都不能被重写。
重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心 将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法 构成重写.
静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代
表函数重载。
动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。
了解了什么是重写,看看多态发生的条件。
多态发生的条件有三点:
1.发生在继承体系下
2.存在子类对于父类方法的重写
3.通过父类调用重写的方法
3.向上转型 和 向下转型
3.1 向上转型
向上转型实际上就是通过父类引用接收子类对象来使用。翻译成大白话就是创建一个子类对象,将其当成父类对象来使用。
语法格式:父类类型 对象名 = new 子类类型()
向上转型的使用场景有三种:1.直接复制 2.作为参数传参 3.作为返回值
直接上例子:
public class TestAnimal {
// 2. 方法传参:形参为父类型引用,可以接收任意子类的对象
public static void eatFood(Animal a){
a.eat();
}
// 3. 作返回值:返回任意子类对象
public static Animal buyAnimal(String var){
if("狗".equals(var) ){
return new Dog("狗狗",1);
}else if("猫" .equals(var)){
return new Cat("猫猫", 1);
}else{
return null;
}
}
public static void main(String[] args) {
Animal cat = new Cat("元宝",2); // 1. 直接赋值:子类对象赋值给父类对象
Dog dog = new Dog("小七", 1);
eatFood(cat);
eatFood(dog);
Animal animal = buyAnimal("狗");
animal.eat();
animal = buyAnimal("猫");
animal.eat();
}
}
向上转型的优点:让代码实现更简单灵活。
向上转型的缺陷:不能调用到子类特有的方法。
n3.2 向下转型
当使用向上转型后是无法再调用子类独有的方法的,这个时候就用到向下转型了。此时:将父类引用再还原为子类对象即可,即向下转换。
看代码:
public class Test {
public static void main(String[] args) {
Animal animal1 = new Dog(12,"菜询困");
Animal animal2 = new Cat(21,"啥鸟");
animal1.eat();
animal2.eat();//调用了子类重写的方法
//animal1.bark();
//报错原因:向上转型无法调用子类独有的方法
//报错信息:java: 找不到符号 符号: 方法 bark() 位置: 类型为demo1.Animal的变量
//animal1
//向下转型
Dog dog = (Dog)animal1;
dog.bark();
}
}
//执行结果
//菜询困正在吃狗粮
//啥鸟正在吃猫粮
//菜询困汪汪叫
向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入 了 instanceof ,如果该表达式为true,则可以安全转换。
public class Test {
public static void main(String[] args) {
Animal animal1 = new Dog(12,"菜询困");
Animal animal2 = new Cat(21,"啥鸟");
animal1.eat();
animal2.eat();//调用了子类重写的方法
//animal1.bark();
//报错原因:向上转型无法调用子类独有的方法
//报错信息:java: 找不到符号 符号: 方法 bark() 位置: 类型为demo1.Animal的变量 animal1
//向下转型
Dog dog = (Dog)animal1;
dog.bark();
//dog.mew(); 报错animal1实际指向Dog,故不能调用Cat里的独有方法
//为了解决这类问题:引入关键字 instanceof
Cat cat = (Cat)animal2;
if(animal2 instanceof Dog){
dog.bark();
}else{
cat.mew();;
}
}
}
4.多态的优缺点
4.1 优点
1. 能够降低代码的 "圈复杂度", 避免使用大量的 if - else
看个例子:
我们三个类:
public class shape {
public void draw(){
System.out.println("画图形!");
}
}
public class Cycle extends shape{
@Override
public void draw() {
System.out.println("●");
}
}
public class Rect extends shape{
public void draw(){
System.out.println("♦");
}
}
站在调用者的角度,不使用多态的思想就是这样:
public class Test {
public static void main(String[] args) {
Cycle cycle = new Cycle();
Rect rect = new Rect();
String[] strings = {"cycle","rect","rect","cycle"};
for (String string:
strings) {
if(string.equals("cycle")){
cycle.draw();
}
else if(string.equals("rect")){
rect.draw();
}
}
}
}
使用多态的思想:
public class Test {
public static void main(String[] args) {
Cycle cycle = new Cycle();
Rect rect = new Rect();
shape[] shapes = {cycle,rect,rect,cycle};
for (shape shape:
shapes) {
shape.draw();
}
}
}
它们的运行结果相同,但是后者明显更简便、更清晰。
2.可扩展能力更强
如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低.
4.2 缺点
1.代码的运行效率降低。
1. 属性没有多态性
当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性
2. 构造方法没有多态性