Java面向对象的特点:多态

多态

向上转型

Dog dog = new Dog("肉球");

可以写为

Animal dog = new Dog("肉球");

此时dog是一个父类(Animal)的引用,指向一个子类(Dog)的实例,这种写法称为向上转型。
向上转型发生的时机:
●直接赋值
●方法传参
●方法返回

方法传参
public class Test { 
 public static void main(String[] args) { 
 Dog dog = new Dog("肉球"); 
 feed(dog); 
 } 
 public static void feed(Animal animal) { 
 animal.eat("火腿肠"); 
 } 
}

此时形参animal的类型是Animal(基类),实际上对应到Dog(派生类)的实例。

方法返回
public class Test { 
 public static void main(String[] args) { 
 Animal animal = findMyAnimal(); 
 } 
 public static Animal findMyAnimal() { 
 Dog dog = new Dog("肉球"); 
 return dog; 
 } 
}

此时方法findMyAnimal返回的是一个Animal类型的引用,但是实际上对应到Dog的实例。

动态绑定

当子类和父类中出现同名的方法时,再去调用同名方法

// 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 Dog extends Animal { 
 public Dog(String name) { 
 super(name); 
 } 
 public void eat(String food) { 
 System.out.println("我是一只小狗"); 
 System.out.println(this.name + "正在吃" + food); 
 } 
} 
// Test.java 
public class Test { 
 public static void main(String[] args) { 
 Animal animal1 = new Animal("板凳"); 
 animal1.eat("食物"); 
 Animal animal2 = new Dog("肉球"); 
 animal2.eat("狗粮"); 
 } 
}
// 执行结果
我是一只小动物
板凳正在吃食物
我是一只小狗
肉球正在吃狗粮

此时:
animal1和animal2虽然都是Animal类型的引用,但是animal1指向Animal类型的实例,animal2指向Dog类型的实例。
针对animal1和animal2分别调用eat方法,发现animal.eat()实际调用了父类的方法,而animal2.eat()实际调用了子类的方法。

因此,在Java中,调用某个类的方法,究竟执行了哪段代码,要看究竟这个引用指向的是父类对象还是子类对象,这个过程是程序运行时决定的(而不是编译期),因此称为动态绑定。

方法重写

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

关于重写的注意事项

1.重写和重载是完全不同的东西
2.普通方法可以重写,static修饰的静态方法不能重写。
3.重写中子类的方法的访问权限不能低于父类的方法访问权限。
4.一般重写的方法返回值类型和父类的方法相同,特殊情况是:返回值类型为协变类型。
针对重写的方法,可以使用@Override注解来显示指定

// Bird.java 
public class Dog extends Animal { 
 @Override 
 private void eat(String food) { 
 ... 
 } 
}

这个注解可以帮我们进行合法性校验,当未构成重写时会编译报错。

No区别重载重写
1概念方法名称相同,参数的类型及个数不同方法名称、返回值类型、参数的类型及个数完全相同
2范围一个类继承关系
3限制没有权限要求被重写的方法不能拥有比父类更严格的访问控制权限

理解多态

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("♣"); 
 } 
} 
/我是分割线// 
// Test.java 
public class Test { 
 public static void main(String[] args) { 
 Shape shape1 = new Flower(); 
 Shape shape2 = new Cycle(); 
 Shape shape3 = new Rect(); 
 drawMap(shape1); 
 drawMap(shape2); 
 drawMap(shape3); 
 } 
 // 打印单个图形
 public static void drawShape(Shape shape) { 
 shape.draw(); 
 } 
}

在这个代码中分割线上方的代码是类的实现者写的,分割线下方的代码是类的调用者写的。
当类的调用者在编写drawMap这个方法的时候。参数的类型为Shape(父类),此时在该方法内部并不知道,也不关注当前的shape引用指向的是哪个类型的实力。此时shape这个引用调用draw方法可能会有多种不同的表现,这种行为就成为多态。

使用多态的好处

(1)类调用者对类的使用成本进一步降低。
封装是让类的调用者不需要知道类的实现细节。
多态能让类的调用者连这个类的类型是什么都不必知道,只需要知道这个对象具有某个方法即可。
(2)能够降低代码的“圈复杂度”,避免使用大量的if-else。
(3)可扩展能力更强。
如果要新增一种新的形状,使用多态的方式代码改动的成本较低。

class Triangle extends Shape { 
 @Override 
 public void draw() { 
 System.out.println("△"); 
 } 
}

对于类的调用者来说(drawShapes方法),只要创建一个新类的实例就可以了。

向下转型

向下转型就是父类对象转变为子类对象,相比于向上转型来说,向下转型不常见,但有一定的用途。

// 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); 
 } 
} 
// Dog.java 
public class Dog extends Animal { 
 public Dog(String name) { 
 super(name); 
 } 
 public void eat(String food) { 
 System.out.println("我是一只小狗"); 
 System.out.println(this.name + "正在吃" + food); 
 } 
 
 public void run() { 
 System.out.println(this.name + "正在跑");
  } 
}
Animal animal = new Dog("肉球"); 
animal.eat("狗粮"); 
// 执行结果
肉球正在吃狗粮

尝试让肉球跑起来

animal.run(); 
// 编译出错
找不到 run 方法
注意事项

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

// (Bird) 表示强制类型转换
Dog dog = (Dog)animal; 
dog.run(); 
// 执行结果
肉球正在跑

但这样的向下转型是不太可靠的,如

Animal animal = new Snake("蛇"); 
Dog dog = (Dog)animal; 
dog.run(); 
// 执行结果, 抛出异常
Exception in thread "main" java.lang.ClassCastException: Cat cannot be cast to Bird 
 at Test.main(Test.java:35)

animal本质上引用的是一个Snake对象,是不能转成Dog对象的,运行就会抛出异常。
所以为了让向下转型更安全,可以先判断animal本质是不是一个Dog实例。

Animal animal = new Snake("蛇"); 
if (animal instanceof Dog) { 
Dog dog = (Dog)animal; 
dog.run(); 
}

super关键字

如果需要在子类内部调用父类方法怎么办?可以使用super关键字。
super表示获取到父类实例的引用:
(1)使用super来调用父类构造器

public Dog(String name) { 
 super(name); 
}

(2)使用super来调用父类的普通方法

public class Dog extends Animal { 
 public Dog(String name) { 
 super(name); 
 } 
 @Override 
 public void eat(String food) { 
 // 修改代码, 让子调用父类的接口. 
 super.eat(food); 
 System.out.println("我是一只小狗"); 
 System.out.println(this.name + "正在吃" + food); 
 } 
}

如果在子类的eat方法中直接调用eat,那么此时是调用子类自己的eat方法,而加上super关键字,才是调用父类的方法。

No区别thissuper
1概念访问本类中的属性和方法由子类访问父类中的属性和方法
2查找范围先查找本类,如果没有就调用父类不查找本类而直接调用父类
3特殊表示当前对象

如果抛开 Java, 多态其实是一个更广泛的概念, 和 “继承” 这样的语法并没有必然的联系.
C++ 中的 “动态多态” 和 Java 的多态类似. 但是 C++ 还有一种 “静态多态”(模板), 就和继承体系没有关系了.
Python 中的多态体现的是 “鸭子类型”, 也和继承体系没有关系.
Go 语言中没有 “继承” 这样的概念, 同样也能表示多态.
无论是哪种编程语言, 多态的核心都是让调用者不必关注对象的具体类型. 这是降低用户使用成本的一种重要方式.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值