继承是为了重用父类代码两个类若存在 IS-A 的关系就可以使用继承同时继承也为实现多态做了铺垫
多态
多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定, 而是在程序运行期间才确定, 即一个引用变量倒底会指向哪个类的实例对象, 该引用变量发出的方法调用到底是哪个类中实现的方法, 必须在由程序运行期间才能决定因为在程序运行时才确定具体的类, 这样, 不用修改源程序代码, 就可以让引用变量绑定到各种不同的类实现上, 从而导致该引用调用的具体方法随之改变, 即不修改程序代码就可以改变程序运行时所绑定的具体代码, 让程序可以选择多个运行状态, 这就是多态性
多态, 简而言之就是同一个行为具有多个不同表现形式或形态的能力比如说, 有一杯水, 我不知道它是温的冰的还是烫的, 但是我一摸我就知道了我摸水杯这个动作, 对于不同温度的水, 就会得到不同的结果这就是多态
多态的条件
继承在多态中必须存在有继承关系的子类和父类
重写子类对父类的默些方法重新定义, 在调用这些方法的时候就会调用子类的方法
向上转型在多态中需要将子类的引用赋值给父类对象, 只有这样该引用才能具备技能调用父类的方法和子类的方法
只有满足了上述三个条件, 我们才能实现多态
对于 java 而言, 多态的实现机制遵循一个原则: 当父类对象引用变量引用子类对象时, 被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法, 但是这个被调用的方法必须是在父类中定义过的, 也就是被子类覆盖的方法
向上转型
子类引用的对象转换成父类类型成为向上转型通俗的说就是将子类对象转成父类对象此处父类对象可以是接口
- public class Animal {
- public void eat() {
- System.out.println("animal eatting...");
- }
- }
- public class Cat extends Animal {
- public void eat() {
- System.out.println("猫吃鱼");
- }
- }
- public class Dog extends Animal {
- public void eat() {
- System.out.println("狗吃骨头");
- }
- public void run() {
- System.out.println("我会跑");
- }
- }
- public class Main {
- public static void main(String[] args) {
- animal = new Dog(); // 向上转型
- animal.eat();
- }
- }
输出: 狗吃骨头
这就是向上转型, Animal animal = new Dog(); 将子类对象 Dog 转化为父类对象 Animal 这个时候 animal 这个引用调用的方法是子类方法
转型过程中需要注意的问题
向上转型时, 子类单独定义的方法会丢失比如上面 Dog 类中定义的 run 方法, 当 animal 引用指向 Dog 类实例时是访问不到 run 方法的, animal.run()会报错
子类引用不能指向父类对象
Dog d = (Dog)new Animal()
这样是不行的
向上转型的好处
减少重复代码, 使代码变得简洁
提高系统扩展性
向下转型(有坑)
与向上转型相对应的就是向下转型了向下转型是把父类对象转为子类对象(请注意! 这里是有坑的)
- // 还是上面的 animal 和 cat dog
- Animal a = new Cat();
- Cat c = ((Cat) a);
- c.eat();
- // 输出 我吃鱼
- Dog d = ((Dog) a);
- d.eat();
- // 报错 : java.lang.ClassCastException:com.chengfan.animal.Cat cannot be cast to com.chengfan.animal.Dog
- Animal a1 = new Animal();
- Cat c1 = ((Cat) a1);
- c1.eat();
- // 报错 : java.lang.ClassCastException:com.chengfan.animal.Animal cannot be cast to com.chengfan.animal.Cat
为什么第一段代码不报错呢, 因为 a 本身就是 Cat 对象, 所以当然可以转型为 Cat, 因为是 Cat 所以不能转为 Dog
而 a1 是 Anmail 对象, 它不能向下转型 Wie 任何子类对象比如发现一个古生物化石, 知道它是一种动物, 但你不能直接说他是猫或者他是狗
向下转型注意事项
向下转型的前提是父类对象指向的是子类对象(也就是说, 在向下转型之前, 它得先向上转型)
向下转型只能转型为本类对象(猫是不能变成狗的)
instanceof 关键字: instanceof 是 Javaphp 的一个二元操作符(运算符), 和 ==,>,<是同一类东西由于它是由字母组成的, 所以也是 Java 的保留关键字它的作用是判断其左边对象是否为其右边类的实例, 返回 boolean 类型的数据可以用来判断继承中的子类的实例是否为父类的实现
instanceof 一般放在类型转换的前面, 合理规避异常产生!
- public void eat(Animal a){
- /*
- * 向下转型(强制转型)
- * 子类引用指向父类的实例(对象), 此处必须今习惯强制类型转换
- * 该实例可以调用子类特有的方法
- * 必须满足转型条件才能转换(), 子类间不能随意强制转换, 但是子类引用指向父类实例, 可以强制转换
- * instanceof 运算符: 返回 true/false,
- */
- if(a instanceof Dog){
- Dog d = (Dog)a;
- d.eat();
- d.run();// 狗有一个跑的方法
- }
- if(a instanceof Cat){
- Cat c = (Cat)a;
- c.eat();
- System.out.println("我也想跑, 但是不会"); // 猫会抱怨
- }
- a.eat();// 其他动物只会吃
- }
- eat(new Cat());
- eat(new Cat());
- eat(new Dog());
花木兰替父从军
向下转型花木兰替父亲花弧从军那么这时候花木兰是子类, 花弧是父类花弧有自己的成员属性年龄, 姓名, 性别花木兰也有这些属性, 但是很明显二者的属性完全不一样花弧有自己的非静态成员方法骑马杀敌, 同样花木兰也遗传了父亲一样的方法骑马杀敌花弧还有一个静态方法自我介绍, 每个人都可以问花弧姓甚名谁同时花木兰还有一个自己特有的非静态成员方法涂脂抹粉但是, 现在花木兰替父从军, 女扮男装这时候相当于父类的引用 (花弧这个名字) 指向了子类对象 (花木兰这个人), 那么在其他类(其他的人) 中访问子类对象 (花木兰这个人) 的成员属性 (姓名, 年龄, 性别) 时, 其实看到的都是花木兰她父亲的名字 (花弧) 年龄 (60 岁) 性别 (男) 当访问子类对象 (花木兰这个人) 的非静态成员方法 (骑马打仗) 时, 其实都是看到花木兰自己运用十八般武艺在骑马打仗当访问花木兰的静态方法时(自我介绍), 花木兰自己都是用她父亲的名字信息在向别人作自我介绍并且这时候花木兰不能使用自己特有的成员方法涂脂抹粉
----- 多态中的向上转型 那么终于一将功成万骨枯, 打仗旗开得胜了, 花木兰告别了战争生活有一天, 遇到了自己心爱的男人, 这时候爱情的力量将父类对象的引用 (花弧这个名字) 强制转换为子类对象本来的引用(花木兰这个名字), 那么花木兰又从新成为了她自己, 这时候她完全是她自己了名字是花木兰, 年龄是 28, 性别是女, 打仗依然那样生猛女汉子, 自我介绍则堂堂正正地告诉别人我叫花木兰 OMG! 终于, 终于可以使用自己特有的成员方法涂脂抹粉了从此, 花木兰完全回到了替父从军前的那个花木兰了并且和自己心爱的男人幸福的过完了一生 ----- 多态中的向下转型
大家记得哈, 向上转型向下转型一定是在多态这个前提下哈, 否则强制将女儿变成父亲, 或者将父亲变成女人, 就变成东方不败了, 系统此时就会报错非法类型转换哈哈哈哈哈另外开发中一般是利用多态声明形式参数, 并将创建子类的匿名对象作为实际参数以上
多态成员访问特点
成员变量 编译看左边(父类), 运行看左边(父类)
成员方法 编译看左边 (父类), 运行看右边(子类) 动态绑定
静态方法 编译看左边(父类), 运行看左边(父类)
静态和类相关, 算不上重写, 所以, 访问还是左边的, 只有非静态的成员方法, 编译看左边, 运行看右边
经典案例分析
- public class A {
- public String show(D obj) {
- return ("A and D");
- }
- public String show(A obj) {
- return ("A and A");
- }
- }
- public class B extends A{
- public String show(B obj){
- return ("B and B");
- }
- public String show(A obj){
- return ("B and A");
- }
- }
- public class C extends B{
- }
- public class D extends B{
- }
- public class Test {
- public static void main(String[] args) {
- A a1 = new A();
- A a2 = new B();
- B b = new B();
- C c = new C();
- D d = new D();
- System.out.println("1--" + a1.show(b));
- System.out.println("2--" + a1.show(c));
- System.out.println("3--" + a1.show(d));
- System.out.println("4--" + a2.show(b));
- System.out.println("5--" + a2.show(c));
- System.out.println("6--" + a2.show(d));
- System.out.println("7--" + b.show(b));
- System.out.println("8--" + b.show(c));
- System.out.println("9--" + b.show(d));
- }
- }
- --A and A
- --A and A
- --A and D
- --B and A
- --B and A
- --A and D
- --B and B
- --B and B
- --A and D
分析这个题需要记住以下几点
当父类对象引用变量引用子类对象时, 被引用对象的类型决定了调用谁的成员方法,
引用变量类型决定可调用的方法如果子类中没有覆盖该方法, 那么会去父类中寻找
先看一个例子
- class X {
- public void show(Y y){
- System.out.println("x and y");
- }
- public void show(){
- System.out.println("only x");
- }
- }
- class Y extends X {
- public void show(Y y){
- System.out.println("y and y");
- }
- public void show(int i){
- }
- }
- class main{
- public static void main(String[] args) {
- X x = new Y();
- x.show(new Y());
- x.show();
- }
- }
- // 结果
- //y and y
- //only x
其实在继承链中对象方法的调用存在一个优先级: this.show(O)super.show(O)
this.show((super)O)super.show((super)O)
Y 继承了 X, 覆盖了 X 中的 show(Y y)方法, 但是没有覆盖 show()方法
这个时候, 引用类型为 X 的 x 指向的对象为 Y, 这个时候, 调用的方法由 Y 决定, 会先从 Y 中寻找执行 x.show(new Y());, 该方法在 Y 中定义了, 所以执行的是 Y 里面的方法;
但是执行 x.show(); 的时候, 有的人会说, Y 中没有这个方法啊? 它好像是去父类中找该方法了, 因为调用了 X 中的方法
事实上, Y 类中是有 show()方法的, 这个方法继承自 X, 只不过没有覆盖该方法, 所以没有在 Y 中明确写出来而已, 看起来像是调用了 X 中的方法, 实际上调用的还是 Y 中的
这个时候再看上面那句难理解的话就不难理解了吧 X 是引用变量类型, 它决定哪些方法可以调用; show()和 show(Y y)可以调用, 而 show(int i)不可以调用 Y 是被引用对象的类型, 它决定了调用谁的方法: 调用 y 的方法
abcd 的关系是这样的: C/D ---> B ---> A
a1 是 A 类的一个实例化对象, 所以 this 指向 A, 然后查找 this.show(b), 由于没有这个方法, 所以到 super.show(b), 但是由于 A 类没有父类了, 所以到 this.show(super b), 由于 b 的父类是 A, 所以相当于 this.show(A), 然后在 A 类中查找到了这个方法, 于是输出 A and A
a1 是 A 类的实例化对象, 所以 this 指向 A, 然后在 A 类中查找 this.show(C)方法, 由于没有这个方法, 所以到了 super.show(C), 由于 A 类的父类里面找, 但是 A 没有父类, 所以到了 this.show(super C), 由于 C 的父类是 B 所以在 A 类里面查找 this.show(B)方法, 也没找到, 然后 B 也有父类, 就是 A, 所以查找 this.show(A), 找到了, 于是输出 A and A;
a1 是 A 类的实例化对象, 所以 this 指向 A, 然后在 A 类中找到 this.show(D)方法, 找到了, 所以就输出 A and D;
a2 是 B 类的引用对象, 类型为 A, 所以 this 指向 A 类, 然后在 A 类里面找 this.show(B)方法, 没有找到, 所以到了 super.show(B), 由于 A 类没有父类, 所以到了 this.show(super B),B 的父类是 A, 即 super B = A, 所以执行方法 thisshow(A), 在 A 方法里面找 show(A), 找到了, 但是由于 a2 是一个类 B 的引用对象, 而 B 类里面覆盖了 A 类的 show(A)方法, 所以最终执行的是 B 类里面的 show(A)方法, 即输出 B and A;
a2 是 B 类的引用对象, 类型为 A, 所以 this 指向 A 类, 然后在 A 类里面找 this.show(C)方法, 没有找到, 所以到了 super.show(C)方法, 由于 A 类没有父类, 所以到了 this.show(super C),C 的父类是 B, 所以在 A 类里面找 show(B), 同样没有找到, 发现 B 还有父类, 即 A, 所以还继续在 A 类里面找 show(A)方法, 找到了, 但是由于 a2 是一个类 B 的引用对象, 而 B 类里面覆盖了 A 类的 show(A)方法, 所以最终执行的是 B 类里面的 show(A)方法, 即输出 B and A;
a2 是 B 类的引用对象, 类型为 A, 所以 this 指向 A 类, 然后在 A 类里面找 this.show(D)方法, 找到了, 但是由于 a2 是一个类 B 的引用对象, 所以在 B 类里面查找有没有覆盖 show(D)方法, 没有, 所以执行的是 A 类里面的 show(D)方法, 即输出 A and D;
b 是 B 类的一个实例化对象, 首相执行 this.show(B), 在 B 类里面找 show(B)方法, 找到了, 直接输出 B and B;
b 是 B 类的一个实例化对象, 首相执行 this.show(C), 在 B 类里面找 show(C)方法, 没有找到, 所以到了 super.show(c),B 的父类是 A, 所以在 A 类中找 show(C)方法, 没有找到, 于是到了 this.show(super C),C 的父类是 B, 所以在 B 类中找 show(B)f 方法, 找到了, 所以执行 B 类中的 show(B)方法输出 B and B;
b 是 B 类的一个实例化对象, 首相执行 this.show(D), 在 B 类里面找 show(D)方法, 没有找到, 于是到了 super.show(D),B 的父类是 A 类, 所以在 A 类里面找 show(D)方法, 找到了, 输出 A and D;
总结
本篇文章的内容大体上就是这些了我们来总结一下
多态, 简而言之就是同一个行为具有多个不同表现形式或形态的能力
多态的分类: 运行时多态和编译时多态
运行时多态的前提: 继承(实现), 重写, 向上转型
向上转型与向下转型
继承链中对象方法的调用的优先级: this.show(O)super.show(O)this.show((super)O)super.show((super)O)
来源: https://juejin.im/post/5aaf21806fb9a028cd44e304