目录
[3.3 多态](#3.3 多态)
3.3 多态
- 多态,多种形态,指向那个对象就去指向那个对象的方法。
- 实现多态的条件:必须有继承关系、必须要有重写,父类的引用指向子类的对象。
- instanceof 判断xx是否为Child2类型的对象,或者Child2子孙后代对象。
- 强制类型转换
-
基本数据类型强制类型转换不会报错,但是结果可能与预期不符。
-
引用类型数组强制类型转换,前提是有继承关系。
java
public class Parent {
public void eat() {
System.out.println("我是Parent");
}
}
java
public class Child1 extends Parent{
public void eat() {
System.out.println("我是Child1");
}
}
java
public class Child2 extends Parent {
public void eat() {
System.out.println("我是Child2");
}
}
java
public class Child3 extends Parent{
public void eat() {
System.out.println("我是Child3");
}
}
java
public class GrandSon1 extends Child1 {
public void eat() {
System.out.println("我是GrandSon1");
}
}
java
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方法。

- 例子:
java
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()方法,会优先调用这个方法。