Java——多态
目录
3.3 多态
3.3 多态
- 多态,多种形态,指向那个对象就去指向那个对象的方法。
- 实现多态的条件:必须有继承关系、必须要有重写,父类的引用指向子类的对象。
- instanceof 判断xx是否为Child2类型的对象,或者Child2子孙后代对象。
- 强制类型转换
- 基本数据类型强制类型转换不会报错,但是结果可能与预期不符。
- 引用类型数组强制类型转换,前提是有继承关系。
public class Parent {public void eat() {System.out.println("我是Parent");}
}
public class Child1 extends Parent{public void eat() {System.out.println("我是Child1");}
}
public class Child2 extends Parent {public void eat() {System.out.println("我是Child2");}
}
public class Child3 extends Parent{public void eat() {System.out.println("我是Child3");}
}
public class GrandSon1 extends Child1 {public void eat() {System.out.println("我是GrandSon1");}
}
public class Test {public static void main(String[] args) {
// 多态,多种形态,指向那个对象就去指向那个对象的方法。
// 实现多态的条件:必须有继承关系、必须要有重写,父类的引用指向子类的对象/*Parent xx = new Parent();xx.eat();xx = new Child1();xx.eat();xx = new GrandSon1();xx.eat();*/aa(new Parent());aa(new Child1());aa(new Child2());aa(new Child3());aa(new GrandSon1());//强制类型转换//基本数据类型强制类型转换不会报错,但是结果可能与预期不符//引用类型数组强制类型转换,前提是有继承关系Parent xx = new GrandSon1();//GrandSon1是小碗,Parent是大碗,大碗可以装小碗Child1 yy1 = (Child1)xx;//小碗不能装大碗,除非强制类型转换//Child2 yy2 = (Child2)xx;//编译没有错,但是运行会报错//instanceof 判断xx是否为Child2类型的对象,或者Child2子孙后代对象if(xx instanceof Child2) {Child2 yy3 = (Child2)xx;System.out.println("可以转换");}else {System.out.println("不可以转换");}int a = 32;short b = (short)a;int[] arr = {100,245,128,110};for(int i=0;i<arr.length;i++) {char j = (char)arr[i];System.out.println(j);}}public static void aa(Parent xx) {xx.eat();}
}
- Java中,创建对象时,在创建子对象之前会创建它的所有祖先对象,从直接父类开始,一直向上到Object类。
- 这是因为Java 中的继承机制,子类会继承父类的属性和方法,为了正确地初始化对象,需要先初始化父类部分。当创建一个子类对象时,Java 虚拟机会首先调用父类的构造函数(原因见3.1继承概述 代码里关于super()的内容,3.1暂时还没有发,过几天发),如果父类还有父类,会继续向上调用,直到Object类的构造函数被调用,然后再逐步向下执行子类的构造函数,完成对象的创建和初始化。
- 父类的引用指向子类对象的内存图:
对于Animal a1 = new Cat();(下图的代码)
这个语句,会创建两个对象,先创建Animal对象,然后再创建Cat对象。而a1会默认指向创建的Animal对象。如果在Cat中没有重写run方法,此时执行a1.run(),将会调用创建的Animal里的run方法。简略内存图如下:
当重写run方法后,a1指向不会改变,但是创建的Animal对象里的run()方法会指向创建的Cat对象里的run()方法。如果这时执行a1.run(),将会调用创建的Cat里的run方法。
- 例子:

public class Test1 {public static void main(String[] args) {A a1 = new A();A a2 = new B();//a2实际上是一个A的对象,只可以调用B中重写的A中的方法和A中没有被重写的方法//父类的引用指向子类的对象时(多态),只可以调用子类中重写父类的方法和父类中没有在子类重写的方法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));//a1的方法里面两个都能用,但是最后只会用精度最高的那一个//a2只可以调用A的第一个方法B的第二个方法,子类重写父类的方法后,就不可以在调用父类的该方法,只可以调用重写后的方法System.out.println("4:"+a2.show(b));System.out.println("5:"+a2.show(c));System.out.println("6:"+a2.show(d));//b可以调用B中的所有方法和A中的第一个方法System.out.println("7:"+b.show(b));System.out.println("8:"+b.show(c));System.out.println("9:"+b.show(d));}
}
解答:
每个类的关系如下图,最上传的A是祖先。
1:a1无祖先(准确来说其祖先是Object类,但是这里涉及不到,暂时不考虑),所以在创建对象时,只会创建一个(实际上还创建了一个Object的对象,但是,这里不涉及,暂时不考虑,下面同理)。对于第一个打印语句,Show方法传的实参是一个B类型的对象,但是A类的方法里没有形参是B类型对象的方法,此时会用到多态,父类的引用指向子类的对象,对于A类里的两个Show()方法,形参分别是一个D类型的变量,一个A类型的变量。实参b是是B类型,B类型是A的子类,此时会调用形参类型为A的Show()方法。所以最后输出结果是A and A。
2:原理同1。实参c是C类型的对象。C是A的后代。
3:实参d是D类型的对象。D虽然是A的后代。但是因为A类里面有一个形参为D的Show()方法,会优先调用这个方法。即,如果两个方法都可以被调用,会优先调用精度高的。
4:a2只有一个祖先,所以在创建对象时,只会创建2个,一个A型的,一个B型的。因为B类重写了形参为A类型的Show()方法,当调用Show(A)时,调用的是B类的方法。又因为a2是A类型,它指向了它的子类对象,所以,B类型的Show(Object obj)无法被调用。对于第四个打印语句,Show方法传的实参是一个B类型的对象,但是A类的方法里没有形参是B类型对象的方法,此时会用到多态,父类的引用指向子类的对象,对于A类里的两个Show()方法,形参分别是一个D类型的变量,一个A类型的变量。实参b是是B类型,B类型是A的子类,此时会调用形参类型为A的Show()方法。所以最后输出结果是B and A。
5:原理同4,实参c是C类型的对象。C是A的后代。
6:实参d是D类型的对象。D虽然是A的后代。但是因为A类里面有一个形参为D的Show()方法,会优先调用这个方法。即,如果两个方法都可以被调用,会优先调用精度高的。
7:b只有一个祖先,所以在创建对象时,只会创建2个,一个A型的,一个B型的。因为B类重写了形参为A类型的Show()方法,当调用Show(A)时,调用的是B类的方法。又因为b是B类型,此时他一共可以调用三个方法,如图。对于第七个打印语句,Show方法传的实参是一个B类型的对象,可调用的方法里没有形参是B类型对象的方法,此时会用到多态,父类的引用指向子类的对象,对于可以调用的三个个Show()方法,形参分别是一个D类型的变量,一个A类型的变量,一个是Object型的变量。实参b是是B类型,B类型是A的子类,也是Object的后代,此时会调用形参类型为A的Show()方法,优先调用精度高的。所以最后输出结果是B and A。
8:原理同4,实参c是C类型的对象。C是A的后代。
9:实参d是D类型的对象。D虽然是A的后代。但是因为可以调用的方法里面有一个形参为D的Show()方法,会优先调用这个方法。