Java —— 多态 ,你不知道的多态都在这里了 !!
Java —— 多态

每博一文案
很喜欢苏曾说的一句话,懂你的人会用你所需要的方式去爱你,
不懂你的人,会用他所需要的方式去爱你。
心与心的距离,有时候可以很近,有时候会很远,
可以近在咫尺,可以千山万水。
都抵不过一个懂得的距离,每个人心中都有这样的一个期待,
期待有那么一个能彼此懂得,不懂你的人,无法理解说的话,
以及你心中的追求和热爱,也无法理解你意识里的胆怯和位置。
长期以往,只会让心与心的距离。越来越远
,世上最棒的事莫过于一个真正懂你的人,与你一起分享生命
的美妙和感动。—————————————— 一禅心灵庙语
文章目录
- Java —— 多态
- 每博一文案
- 多态
- 向上转型
- 动态绑定
- 重写
- 向下转型
- 在构造方法中调用重写的方法(天坑)
- 理解多态
- 多态数组的结合
- 补充:
- 最后:
多态
- 多态是初学者比较难理解的一个知识点,我们可以用一个生活中的一个小案例来引入该知识点,生活中我们存在着许多的 动物 ,有地上走的 老虎 ,水里游的 鲸鱼 ,空中飞的 鹰 ,那么我们可以把 动物 作为 父类 ,地上的老虎,水里的鲸鱼 ,空中的鹰 作为 动物的 子类 。如下图所示:

class Animal {protected String name;public Animal(String name) {this.name = name;}public void trait() {}
}class Tiger extends Animal {public Tiger(String name) {super(name); // 调用父类的构造方法,放在第一行}@Override // 重写,注解: 错误提醒,人和编译器都可以读懂的注释public void trait() {System.out.println(super.name+" 跑得飞快");}
}class Whale extends Animal {public Whale(String name) {super(name);}@Overridepublic void trait() {System.out.println(super.name+" 游得飞快");}
}class Eagle extends Animal {public Eagle(String name) {super(name);}@Overridepublic void trait() {System.out.println(super.name+" 飞得飞快");}
}public class Blog05 {public static void func(Animal animal) {animal.trait();}public static void main(String[] args) {Animal animal = new Tiger("老虎");Animal animal1 = new Whale("鲸鱼");Animal animal2 = new Eagle("鹰");func(animal);func(animal1);func(animal2);}
}
运行结果

- 多态的实现的必备步骤:
- 存在父类子类的继承关系:上面的实例中 Tiger类、Whale类、Eagle类作为子类,分别都是继承了 Animal父类
- 子类重写了父类中的方法:上面的实例中 Tiger类、Whale类、Eagle类的子类,都 重写 了父类中的 trait() 中的方法
- 父类引用指向子类对象,向上转型:如:Animal animal = new Tiger(“老虎”)、Animal animal1 = new Whale(“鲸鱼”)、Animal animal2 = new Eagle(“鹰”) 。等号左边父类是编译时对应的类型,等号右边类型是运行时对应的类型
- 以上步骤时多态实现的必备步骤,缺一不可,没有继承就没有重写,没有重写父类引用指向子类对象就没有意义,一环扣一环
向上转型
-
我们学过了,基本数据类型之间可以通过 自动类型的转换 或 强制类型转换 的形式进行转换。引用数据类型之间同样也是可以进行类型的转换的,分为:向上转型、向下转型 两种
-
所谓的 向上转型 :就是父类引用子类的对象,和我们的基本数据类型的自动转换(小转大),是自动发生的,可以直接将子类对象赋值给父类引用,虽然该父类的引用是指向子类对象的,但是父类是无法访问除了重写方法以为的其他子类中的方法和子段的
-
多态的表现形式存在 三种
-
第一种:直接赋值 :向上转型的形式:直接通过 父类引用子类的对象的,实例化访问,对应的重写的方法
// 沿用上面 动物多态的实例:
public class Blog05 {public static void main(String[] args) {Animal animal = new Tiger("老虎");Animal animal1 = new Whale("鲸鱼");Animal animal2 = new Eagle("鹰");// 直接赋值animal.trait();animal1.trait();animal2.trait();}
}
运行结果

- 第二种:方法传参 父类作为方法的形参,具体传入子类的对象
// 沿用上面 动物多态的实例:
public class Blog05 {public static void func(Animal animal) { // 方法传参,父类类型接受animal.trait(); }public static void main(String[] args) {Animal animal = new Tiger("老虎");Animal animal1 = new Whale("鲸鱼");Animal animal2 = new Eagle("鹰");func(animal);func(animal1); // 方法传参,父类引用子类的对象func(animal2);}
}
运行结果

- 第 三 种:方法的返回值 父类作为方法的返回值,具体返回子类的对象
public class Blog05 {public static Animal func() { // 方法的返回值Animal animal = new Tiger("年轻的老虎");return animal;}public static void main(String args[]) {func().trait();}
}
运行结果

动态绑定
- 其实我们在上面的代码实现中已经 使用了 动态绑定 了
- 注意 构造方法中也是存在着 动态绑定 的后面会有介绍说明的
- 当子类和父类中出现同名方法的时候, 再去调用会出现什么情况呢?
class Animal {protected String name;public Animal(String name) {this.name = name;}public void trait() {}
}class Eagle extends Animal {public Eagle(String name) {super(name);}@Overridepublic void trait() {System.out.println(super.name+" 飞得飞快");}
}public class Blog05 {public static void main(String[] args) {Animal animal = new Eagle("年轻的鹰"); // 方式 一 animal.trait();System.out.println("**********************");Eagle eagle = new Eagle("年轻的鹰"); // 方式 二eagle.trait();}
}
运行结果

- 我们查看结果可以发现它们是一样的,但是它们的调用方法的过程是不一样的
- 方式一:Animal animal = new Eagle(“年轻的鹰”) 是在运行的时候才能确定调用的方法是什么,也就是说,生成的字节码必须在程序运行时能够调用切换调用的方法
- 方式二 :Eagle eagle = new Eagle(“年轻的鹰”) 是在编译时就可以确定调用的方法,也就是直接看源码就可以直接明白,调用的是哪个方法
- 方式一,虽然源程序表面上看来只是 “方法调用” ,但类文件中的字节码需要根据条件切换调用的方法,比较复杂。因此,与 方式二 相比较,程序的运行效率较低 (虽然只是低一点点)
- 从编译器的角度来看,简单的方式二更好,但 编译器实际上生成的字节码是 方式一的
- 由于 方式一 是在运行时确定调用的方法,因此这种调用结构被称为 动态联编(dynamic binding) 或 后期联编(late binding) 、binding 也可以翻译为 绑定 ,又可以称为:动态绑定 或 后期绑定
- 由于 方式二 是在编译时确定要调用的方法,因此这种调用结构被称为:静态联编 或 前期联编
class Animal {protected String name;public Animal(String name) {this.name = name;}public void trait() {}
}class Whale extends Animal {public Whale(String name) {super(name);}@Overridepublic void trait() {System.out.println(super.name+" 游得飞快");}
}class Eagle extends Animal {public Eagle(String name) {super(name);}@Overridepublic void trait() {System.out.println(super.name+" 飞得飞快");}
}public class Blog05 {public static void main(String[] args) {Animal animal = new Eagle("年轻的鹰");Animal animal2 = new Whale("年轻的鲸鱼");animal.trait();animal2.trait();}
}
运行结果

-
我们再次观察:
- animal 和 animal2 虽然都是 Animal 的类型的引用,但是 animal 指向 Eagle 类型的实例,animal2 指向 Whale 类型的实例
- 针对 animal 和 animal2 分别调用 trait( ) 的方法,我们可以发现 animal.trait( )实际调用了 Eagle 中的trait( ) 的方法,而 animal2.trait( ) 实际调用的是子类中Whale 的trait( ) 方法
- 因此,在Java 中,调用某个类的方法,究竟执行了那段代码(是父类的方法的代码还是子类方法的代码),要看究竟这个引用指向的是父类对象还是子类对象,这个过程是程序运行时决定的(而不是编译器),因此称为:动态绑定
重写
- 重写 与 重载 的不同

-
重写 的注意事项
-
只有方法可以重写,字段是不可以重写的
-
需要重写的方法,不能是被 final 修饰的,被 final 修饰之后,它就是密封方法,不可以被修改,自然也就无法重写了
class A {public final void demo() {} }class B extends A {@Overridepublic void demo() {} } public class Blog06 {public static void main(String[] args) {} }运行报错结果

- 被重写的方法,访问修饰限定符一定不能是 private 私有的,虽然在重写的时候,不会有报错的提示,但是当你访问的时候,就会提示该 方法被私有了,是无权限访问的
class A {private void demo() {} }class B extends A {@Overridepublic void demo() {} } public class Blog06 {public static void main(String[] args) {} }运行错误结果

- 被 static 修饰的方法是不可以被重写的
class A {public static void demo() {} }class B extends A {@Overridepublic void demo() {} }public class Blog06 {public static void main(String[] args) {} }运行报错结果

- 被重写的方法,子类当中的访问权限限定修饰符必须 **>= ** 父类中的访问权限限定符的( 除了 private 不可以修饰重写的方法) default(默认修饰限定符) < protected < public
class A {public static void demo() {} }class B extends A {@Overridevoid demo() {System.out.println("子类的默认权限限定 default == 父类的默认权限修饰符 default");} }public class Blog06 {public static void main(String[] args) {A a = new B();a.demo();} }运行结果

class A {public static void demo() {} }class B extends A {@Overridepublic void demo() {System.out.println("子类的权限限定 public > 父类的默认权限修饰符 default");}}public class Blog06 {public static void main(String[] args) {A a = new B();a.demo();} }运行结果

class A {void demo() {} }class B extends A {@Overrideprotected void demo() {System.out.println("子类的权限限定 protected > 父类的默认权限修饰符 default");} }public class Blog06 {public static void main(String[] args) {A a = new B();a.demo();} }运行结果

- 当需要重写的方法的 权限限定修饰符是 public ,那么重写的方法只能是 public == 等于父类的权限限定符了,因为 public 它是最大的了,没有之一
- 我们在重写方法的时候,加上 @Override 注解来显式指定.
- 有了这个注解能帮我们进行一些合法性校验. 例如不小心将 方法名(demo) 字拼写错了 (比如写成 deom ), 那么此时编译器就会发现父类中没有 demo 方法, 就会编译报错, 提示无法构成重写.
- 注解:现在简单可以理解为是 人和编译器都可以读懂的 注释
class B extends A {@Override // 注解protected void demo() {System.out.println("子类的权限限定 protected > 父类的默认权限修饰符 default");} } -
向下转型
- 向上转型是子类对象转成父类对象, 向下转型就是父类对象转成子类对象. 相比于向上转型来说, 向下转型没那么常见,但是也有一定的用途,此时如果非想调用子类对象特有的方法/属性,必须进行向下类型转换,类似于基本数据类型的强制类型转换,需要通过 “( )" 来强制完成
class Animal {protected String name;public Animal(String name) {this.name = name;}public void trait() {}
}class Whale extends Animal {public Whale(String name) {super(name);}@Overridepublic void trait() {System.out.println(super.name+" 游得飞快");}}class Eagle extends Animal {public Eagle(String name) {super(name);}@Overridepublic void trait() {System.out.println(super.name+" 飞得飞快");}public void fly() {System.out.println("Whale :: fly");}}
public class Blog05 {public static void main(String[] args) {Animal animal = new Eagle("神雕");Whale whale = (Whale)animal; // 报错的,因为 animal本质上是引用了 Eagle 对象,是不能强转化为 Whale 对象,运行时抛出异常whale.trait();}
}
运行报错结果

报错解析
- 因为 animal本质上是引用了 Eagle 对象,是不能强转化为 Whale 对象,运行时抛出异常
- 所以当我们需要 向下转型 的时候,需要对该类型进行 实例化判断,判断是否为该类型的实例,不是就无法向下转型 ,是,可以
- 这里我们使用 **双目运算符 (instanceof) ** 进行实例化的判断
- 格式如下:
A instanceof B; /* 判断 A 是否是 B 的实例,是返回值为 true,不是返回为 false */
- 改良后
class Animal {protected String name;public Animal(String name) {this.name = name;}public void trait() {}
}class Tiger extends Animal {public Tiger(String name) {super(name); // 调用父类的构造方法,放在第一行}@Override // 重写,注解: 错误提醒,人和编译器都可以读懂的注释public void trait() {System.out.println(super.name+" 跑得飞快");}
}class Whale extends Animal {public Whale(String name) {super(name);}@Overridepublic void trait() {System.out.println(super.name+" 游得飞快");}}class Eagle extends Animal {public Eagle(String name) {super(name);}@Overridepublic void trait() {System.out.println(super.name+" 飞得飞快");}public void fly() {System.out.println("Whale :: fly");}}public class Blog05 {public static void main(String[] args) {Animal animal = new Eagle("神雕");if(animal instanceof Eagle) { // 判断 animal 是否是 Eagle 的实例Eagle eagle = (Eagle)animal;eagle.trait();eagle.fly();} else {System.out.println("NO");}if(animal instanceof Whale) {Whale whale = (Whale)animal;whale.trait();} else {System.out.println("NO NO");}}
}
运行结果

在构造方法中调用重写的方法(天坑)
- 在构造方法中调用重写的方法:发生动态的绑定
class A {public A() {demo(); // 构造方法中调用,重写的方法,会发生动态的绑定}void demo() {System.out.println("A :: demo");}
}class B extends A {@Overridepublic void demo() {System.out.println("B :: demo");}
}public class Blog06 {public static void main(String[] args) {A a = new B();a.demo();}
}
运行结果

- 结果解析
- 构造 A 的对象 的时候,会调用 A 的构造方法
- A 的构造方法中调用了 demo( ) 方法,此时会触发动态绑定,会调用到 B 中 重写了的demo( ) 方法
- 结论 :尽量使用简单的方式使对象 进入可工作状态,尽量不要 触发动态绑定 ,但是此时子类对象还没有构造完成)可能会出现一些隐藏的但是又极难发现的问题
理解多态
class Shape {public void draw() {// 啥也不干}
}class Cycle extends Shape {@Override // 重写;注解:错误提醒,任何编译器都可以读懂的注释public void draw() {System.out.println("画一个 🔴");}
}class React extends Shape {@Override // 重写public void draw() {System.out.println("画一个 🔺");}
}public class Blog05 {public static void func(Shape shape) {shape.draw();}public static void main(String[] args) {Shape shape = new Cycle();Shape shape1 = new React(); // 向上转型 func(shape);func(shape1);}
运行结果:

多态数组的结合
class Shape {public void draw() {// 啥也不干}
}class Cycle extends Shape {@Override // 重写;注解:错误提醒,任何编译器都可以读懂的注释public void draw() {System.out.println("画一个 🔴");}
}class React extends Shape {@Override // 重写public void draw() {System.out.println("画一个 🔺");}
}public class Blog05 {public static void main(String[] args) {Shape[] shapes = new Shape[]{new Cycle(),new React()};for (Shape s: shapes) {s.draw();}}
}
运行结果

- 多态使类的调用者对类的使用成本进一步降低
- 封装是让类的调用者不需要知道类的实现细节
- 多态能让类的调用者,连这个类的类型是什么都不知道,只需要知道这个对象具有某个方法即可
- 因此,多态可以理解成是封装的更进一步,让类调用者对类的使用成本进步一步降低
补充:
- 重写方法中:子类的方法名,返回类型,参数列表必须和父类中被重写的方法一致才能重写/覆盖
- 重写方法中:子类的权限修饰符 >= 父类中被重写的方法的权限修饰符,特殊的:父类是public 子类也必须是public 因为public 权限修饰符是最大的了.父类是private 子类是无法重写的,但是子类继承了该private 修饰的方法但无法访问。
- 重写的方法中:子类的返回类型要与父类被重写的方法的返回类型一致或者是/引用类型的(子类可以算是同属类型):特别的 double 和 int 是不同的数据类型,只是他们之间存在类型转换的关系.
- 重写方法中:子类重写的方法抛出的异常类型 <= 父类被重写的方法抛出的异常类型.
- 重写的是方法,不是属性,属性是不可以重写的
- static 静态方法是不可以重写的,static 是和类一起加载到内存当中的,有且只有一份共有。
- 重写方法就是子类方法覆盖父类方法。所以返回类型需要一致,方法名一致,参数列表一致,访问权限 >= 才能实现覆盖效果。
最后:
限于自身水平,其中存在的错误,希望大家给予指教,韩信点兵 —— 多多指教,谢谢大家,江湖再见,后会有期!
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
