多态

  • 多态
  • 转型
  • 向上转型
  • 直接赋值
  • 方法传参
  • 方法返回值
  • 向下转型
  • 绑定
  • 转载这篇绑定
  • 动态绑定/运行时绑定
  • 静态绑定
  • 重写和重载
  • 重写
  • 定义
  • 基本规则
  • ==注意==
  • 重载(Overload)
  • 定义
  • 规则
  • 注意
  • ==重载和重写区别==
  • ==理解多态==
  • 多态的好处

多态

多态是同一个行为具有多个不同表现形式或形态的能力。
多态就是同一个接口,使用不同的实例而执行不同操作

转型

父子对象之间的转换分为了向上转型和向下转型,它们区别如下:
向上转型 : 通过子类对象(小范围)实例化父类对象(大范围),这种属于自动转换
向下转型 : 通过父类对象(大范围)实例化子类对象(小范围),这种属于强制转换

向上转型

父类的引用 引用子类对象

// Animal.java
class Animal {
    
}
// Bird.java
class Bird extends Animal{
  
}

Bird bird=new Bird();//一般定义
Animal annimal=new Bird();//向上转型

转型的表现方式:

  1. 直接赋值
  2. 方法传参
  3. 方法返回
直接赋值
Animal annimal=new Bird();

子类的实例赋给父类的引用

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

feed方法形参annimal的1类型是Annimal(父类),实际传入的是Bird(子类)的实例

方法返回值
public class Test { 
 public static void main(String[] args) { 
 Animal animal = findMyAnimal(); 
 } 
 public static Animal findMyAnimal() { 
 Bird bird = new Bird("圆圆"); 
 return bird; 
 } 
}

findMyAnimal方法返回的是一个Annimal的引用。实际我们返回的是子类Bird的一个实例

向下转型

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

Animal animal = new Cat("小猫"); 
// 为了让向下转型更安全, 我们可以先判定一下看看 animal 本质上是不是一个 Bird 实例, 再来转换
if (animal instanceof Bird) { 
 Bird bird = (Bird)animal; 
 bird.fly(); 
}

instanceof 可以判定一个引用是否是某个类的实例.

绑定

当子类和父类(接口和实现类)存在同一个方法时,子类重写父类(接口)方法时,程序在运行时调用的方法时,是调用父类(接口)的方法呢?还是调用子类的方法呢?我们将确定这种调用何种方法的操作称之为绑定。

绑定又分为静态绑定和动态绑定。

动态绑定/运行时绑定

动态绑定:在编译的时候调用的是父类自己的方法,而运行时调用的是子类重写的这个方法(前提就是子类重写了),叫动态绑定也叫运行时多态

public class TestBangDing {
    public static void main(String[] args) {
        Animal animal1 = new Animal("圆圆");
        animal1.eat("谷子");
        Animal animal2 = new Bird("扁扁");
        animal2.eat("谷子");
    }
}
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
class Bird extends Animal {
    public Bird(String name) {
        super(name);
    }
    public void eat(String food) {
        System.out.println("我是一只小鸟");
        System.out.println(this.name + "正在吃" + food);
    }
}

运行截图

JAVA面向对象的多态表现 面向对象编程 多态_子类

  1. animal1 和 animal2 虽然都是 Animal 类型的引用, 但是 animal1 指向 Animal 类型的实例, animal2 指向Bird 类型的实例.
  2. 针对 animal1 和 animal2 分别调用 eat 方法, 发现 animal1.eat() 实际调用了父类的方法, 而
    annimal.eat()实际调用了子类的方法

idea反汇编

  1. 点击Terminal(在控制台处找)
  2. JAVA面向对象的多态表现 面向对象编程 多态_JAVA面向对象的多态表现_02

  3. 进入src目录
  4. JAVA面向对象的多态表现 面向对象编程 多态_子类_03

  5. javac 类文件名(一定要带.java)
  6. JAVA面向对象的多态表现 面向对象编程 多态_JAVA面向对象的多态表现_04

  7. 反汇编javap -c 类名(不要.java)
  8. JAVA面向对象的多态表现 面向对象编程 多态_JAVA面向对象的多态表现_05

  9. 显示汇编代码
  10. JAVA面向对象的多态表现 面向对象编程 多态_多态_06

编译时调用的是父类的方法,运行时就调用的子类重写的方法这就实现了动态绑定

动态绑定发生时机

  1. 父类引用引用子类的对象
  2. 通过父类引用调用子类重写f父类的那个方法
静态绑定

静态绑定是在程序执行前就已经被绑定了(也就是在程序编译过程中就已经知道这个方法是哪个类中的方法)

重写和重载

重写
定义

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

基本规则
  1. 方法名相同
  2. 参数列表相同(参数的个数,参数的类型)
  3. 返回值要相同
  4. 继承关系中
注意
  1. 普通方法可以重写, static 修饰的静态方法不能重写.
  2. final修饰的方法不能被重写
  3. 重写中子类的方法的访问权限不能低于父类的方法访问权限.
  4. 重写的方法返回值类型不一定和父类的方法相同(但是建议最好写成相同, 特殊情况除外)
  5. 父类因引用引用子类对象的时候只能访问父类自己特有的(不能访问子类没有重写的方法)
重载(Overload)
定义

重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同

规则
  1. 方法名相同
  2. 参数列表不同
  3. 返回值不做要求
  4. 在同一个类中
注意
  1. 被重载的方法必须改变参数列表(参数个数或类型不一样);
  2. 被重载的方法可以改变返回类型;
  3. 被重载的方法可以改变访问修饰符;
  4. 被重载的方法可以声明新的或更广的检查异常;
  5. 方法能够在同一个类中或者在一个子类中被重载。
  6. 无法以返回值类型作为重载函数的区分标准。
重载和重写区别

JAVA面向对象的多态表现 面向对象编程 多态_子类_07

理解多态

代码层次说多态:父类的引用 引用了子类的对象,父类盒子类有同名的覆盖方法(重写的方法,通过父类的引用来调用同名的覆盖方法时,可能表现出不同的行为,把表现不同行为的过程或者思想就叫做多态)

示例

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 方法可能会有多种不同的表现
(和 shape对应的实例相关),这种行为叫做多态

总而言之,就是一个引用能表现出多种不同形态

多态的好处
  1. 类调用者对类的使用成本进一步降低.

封装是让类的调用者不需要知道类的实现细节.
多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可.
因此, 多态可以理解成是封装的更进一步, 让类调用者对类的使用成本进一步降低

  1. 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else

圈复杂度是一种描述一段代码复杂程度的方式. 一段代码如果平铺直叙, 那么就比较简单容易理解. 而如果有很
多的条件分支或者循环语句, 就认为理解起来更复杂.
因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 “圈复杂度”. 如果一
个方法的圈复杂度太高, 就需要考虑重构.

  1. 可扩展能力更强