文章目录
1. 多态
1.1 多态的概念
去完成某个行为,不同的对象去完成会产生不同的状态。例如,猫和狗都在叫,而猫的叫的是喵喵,狗叫的是汪汪。
在讲解多态之前我们先学习什么是向上转型、向下转型和方法重写。
2. 方法的重写
重写(override),也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写。
构成方法重写的条件:
- 方法名相同
- 方法的参数列表相同(个数、顺序、类型)
- 方法的返回值相同
例如:
上述两个 bark 方法,实现了方法的重写。重写的方法通常会有 @Override 来修饰,表示方法的重写,可以用来提示重写的方法是否出现错误,如果出现错误@Override处会报错。
注意:
- 静态方法不能被重写
- 被private修饰的方法不能被重写
- 被final修饰的方法不能被重写
- 如果方法被重写,子类的访问权限要大于等于父类的访问权限
private < 包访问权限 < protected < public
跟重载是有区别的,注意区分哦
3. 向上转型
3.1
语法格式:父类类型 对象名 = new 子类类型()
public class Animal {
public String name;
public int age;
public Animal(){
}
public Animal(String name,int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(this.name + "正在吃饭");
}
public void bark(){
System.out.println(this.name + "正在叫");
}
}
public class Cat extends Animal{
public Cat(){
}
public Cat(String name,int age){
super(name,age);
}
public void bark(){
System.out.println(this.name + "正在喵喵叫");
}
}
public class Dog extends Animal{
public Dog(){
}
public Dog(String name,int age){
super(name, age);
}
@Override
public void bark() {
System.out.println(this.name + "正在汪汪叫");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Dog();//实现了向上转型
animal.bark();
}
}
观察上述代码,Cat 类和 Dog 类中都重写了 bark 方法,肯定有人觉得main 方法中的 bark 方法可有可无,当我们删除这个 bark 方法时,animal无法访问bark这个方法。
无法访问的原因是在Animal当中没有该方法,通过父类引用访问的时候,只能访问父类自己特有的,所以我们不能删除。
是不是在等向下转型,因为向下转型不常用,所以放在最后面了
3.2 发生向上转型的时机
- 直接赋值
public class Main {
public static void main(String[] args) {
Animal animal = new Cat("zaizai",2);
}
}
- 方法传参的时候
public class Main {
public static void func(Animal animal){
}
public static void main(String[] args) {
func(new Cat());
}
}
- 返回值的时候
public class Main {
public static Animal func(){
return new Cat() ;
}
public static void main(String[] args) {
func();
}
}
不是只有直接赋值一种哦,要注意啦
向上转型的优点:让代码的实现更加简单
向上转型的缺点:无法调用到子类特有的方法
4. 动态绑定和静态绑定
当父类引用子类的对象(即向上转型)时,通过父类引用、调用重写的方法就实现了动态绑定。
动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。
静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。
5. 什么是多态
5.1
回归正题,什么是多态呢
其实,上述代码就发生了多态,当 animal 引用的对象不一样,调用 eat 方法,表现的行为不一样,此时就叫多态。(同一个引用调用同一个方法表现的行为不一样)
5.2 多态的优缺点
优点:
- 降低圈复杂度(是一种用于评估代码复杂性的软件度量方法,代码的分支 / 判断越多,圈复杂度越高),避免使用大量的 if-else
例如:
public class Shape {
public void draw(){
System.out.println("画一个图形");
}
}
public class Rectangle extends Shape{
@Override
public void draw() {
System.out.println("画一个矩形");
}
}
public class Triangle extends Shape{
@Override
public void draw() {
System.out.println("画一个三角形");
}
}
public class Circle extends Shape {
@Override
public void draw() {
System.out.println("画一个圆");
}
}
public class Main {
public static void drawMap(Shape shape){
shape.draw();
}
public static void main(String[] args) {
drawMap(new Triangle());
drawMap(new Rectangle());
drawMap(new Circle());
}
}
例如,我们现在需要打印多个形状. 如果不基于多态, 实现代码如下
public static void drawMap() {
Rectangle rectangle = new Rectangle();
Triangle triangle = new Triangle();
Circle circle = new Circle();
String[] shapes = {"Rectangle","Circle","Circle","Triangle","Rectangle"};
for(String shape:shapes){
if(shape.equals("Rectangle"))
rectangle.draw();
else if(shape.equals("Circle"))
circle.draw();
else if(shape.equals("Triangle"))
triangle.draw();
}
}
基于多态
public static void drawMap(){
Shape Rectangle = new Rectangle();
Shape Triangle = new Triangle();
Shape Circle = new Circle();
Shape[] shapes = {Rectangle,Circle,Circle,Triangle,Rectangle};
for(Shape shape:shapes){
shape.draw();
}
}
果然基于多态方便了许多,没白学
不基于多态,圈复杂度高。基于多态,圈复杂度低,代码更简单。
- 可扩展性强
当我们新增一个图形,创建一个新的实例就可以,改动代码较少。
缺点:
-
属性没有多态性
当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性 -
构造方法没有多态性
6. 避免在构造方法中调用重写的方法
当我们在构造方法中调用重写的方法的代码如下:
public class B {
public B(){
func();
}
public void func(){
System.out.println("B:func()");
}
}
class D extends B{
private int num = 1;
public void func(){
System.out.println("D:func()" + num);
}
}
class Main{
public static void main(String[] args) {
D d = new D();
}
}
//运行结果
D:func()0
为什么会调用 D 的 func 方法呢?而且 num = 0 ,让我们来看一下它具体是怎么执行的
鸡爪子画图,宣!
步骤
- 构造D对象的同时,调用B的无参构造方法。
- B的构造方法中调用了 func 方法,此时触发动态绑定,调用 D 的 func 方法。
- 此时,D 对象自身还未构造,num 处于未初始化的状态,所以 num = 0
结论:尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题。
7. 向下转型
语法格式:子类类型 对象名 = (子类类型)new 父类类型()
需要强转
例如:
Dog dog = (Dog)new Animal();
那么不强转为什么会报错呢?
我们可以理解为不是所有的动物都是狗,所以会报错。
但是向下转型非常不安全,例如
Animal animal = new Cat();
Dog dog = (Dog)new Animal();
dog.bark();
虽然代码在编译层次上不会报错,但是运行结果为类型转换异常
Animal animal = new Cat();
Cat cat = (Cat)animal;
cat.bark();
因为animal引用的本身就是Cat对象,所以不会报错。
Java中为了提高向下转型的安全性,引入了 instanceof ,如果该表达式为true,则可以安全转换。
Animal animal = new Cat();
if(animal instanceof Cat){
Cat cat = (Cat)animal;
cat.bark();
}
上述语句表达的意思是,如果 animal 引用的是 Cat 类型……
虽然向下转型不安全,但是可以使用上述语句来解决问题。
终于完事了,我要奖励自己一下了