文章目录
- 多态
- 向上转型
- 练习题
多态
父类的引用指向子类的对象
public class A {
public String name;
public int age;
public void run(){ //该run()方法被覆盖
System.out.println("a跑的快");
}
public void eat(String name){
System.out.println(name+"吃的很多");
}
}
public class B extends A{
public char sex;
public double height;
public void fly(){
System.out.println("b飞的很高");
}
@Override
public void run(){
System.out.println("b跑的快");
}
}
public class Test {
public static void main(String[] args) {
A ab = new B(); // 数据类型:决定在内存中的存储形式 ab是A类数据类型
// 创建子类对象前提一定会创建父类对象,此时在内存中A和B的对象都存在
ab.name = "李四";
ab.age = 18;
ab.eat("张三");
ab.run();
}
}
以上代码中对象ab就是多态,其中:
-
因为ab为A类的数据类型,以A类为模板,所以他可以调用到A类中的属性
-
ab为B类的对象,而B为A的子类,那么创建子类对象之前一定会先创建父类对象,所以ab可以访问A类中的方法
-
但是ab实际上指向的是子类对象的内存空间,因为受限于父类数据类型,所以只能对父类中的数据进行访问,子类中单独的数据并不能够进行访问
- ab满足了定义父类引用指向子类对象
我们看一下内存图:
创建对象ab的过程如下:
- ab属于B类对象,但是B类拥有A父类,那么创建子类对象之前先创建父类对象,为A类对象开辟空间,在其中存储name、age两个变量和run()、eat()两个方法
- 再为B类对象,其中包括sex、height两个变量和run()、fly()
- 因为B类为A类的子类,所以run()方法被重写,ab对象访问的话只能访问到B类中的run()方法和A中的eat()方法
- 因为ab为A类的数据类型,以A类为模板,所以他可以调用到A类中的属性
向上转型
public class C {
public static void hanlder(A a){
System.out.println("C的输出");
}
}
public class Test {
public static void main(String[] args) {
B b = new B();
C.hanlder(b); // 没有报错,为什么?
}
}
在程序中,C类中的静态方法hanlder(A a)需要的参数为一个A类对象,但是在Test类中的main()方法里,我们却向方法传了一个B类的对象,编译器并没有报错,这是因为多态可以向上转型:子类的对象可以被父类所接受,也就是我们一开始说的定义:父类引用指向子类对象
练习题
A类
public class A {
public String Show(D obj){
return "A and D";
}
public String Show(A obj){
return "A and A";
}
}
B类
public class B extends A{
public String Show(Object obj){
return "B and B";
}
public String Show(A obj){
return "B and A";
}
}
C类
public class C extends B{
}
D类
public class D extends B{
}
Test类
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(a1.Show(b)); // A and A
System.out.println(a1.Show(c)); // A and A
System.out.println(a1.Show(d)); // A and D
System.out.println(a2.Show(b)); // B and A
System.out.println(a2.Show(c)); // B and A
System.out.println(a2.Show(d)); // A and D
System.out.println(b.Show(b)); // B and A
System.out.println(b.Show(c)); // B and A
System.out.println(b.Show(d)); // A and D
}
}
我们来逐个分析一下:
首先我们可以看到A类的优先级最高,是其他BCD类的父类,B类直接继承A类,CD类直接继承B类。
-
那么对于对象a1
它属于A类对象,创建时只开辟A类对象的内存空间,可以调用A类中的两种Show()方法,虽然B类中有对Show方法重写,但是此时并没有开辟B类对象的内存空间,所以并不会出现重写覆盖的现象
System.out.println(a1.Show(b)); // A and A
a1为A类对象,只能访问A类中的数据,b属于B类对象,B类为A的子类,所以向上转型,可以被A中的show(A obj)方法接受
System.out.println(a1.Show(c)); // A and A
同理,c为C类对象,拥有B父类,B父类拥有A父类,所以和第一个同理
System.out.println(a1.Show(d)); // A and D
虽然也是通过a1访问,但是传入的参数为d,show()方法的重载,选择第一个show(D obj)方法
-
对于对象a2
它属于A类数据类型的B类对象,也就是多态,创建时B类为A类的子类,创建子类对象之前要创建父类对象,所以会为A类、B类对象都开辟内存空间。因为a2的数据类型为A类数据类型,所以只能访问A类中的数据,但是同时开辟了B类的内存空间,所以B类中如果有对A类方法的重写,则调用B中的重写方法
System.out.println(a2.Show(b)); // B and A
a2为数据类型为A的B类对象,创建子类对象时先创建父类对象,创建时分别为A类、B类开辟空间,然后因为是A类数据类型,所以应该访问A类中的show(A obj)方法,但是因为中对方法有重写,所以访问B类中的show(A obj)
System.out.println(a2.Show(c)); // B and A
同理,c为C类对象,但是向上转型,可以被show(A obj)接受
System.out.println(a2.Show(d)); // A and D
同理,但是传入的参数为d,由于方法重载,所以访问show(D obj)
-
对于对象b
它属于B类对象,B类是A类的子类,创建子类对象之前先创建父类对象,所以在创建对象b时,会先为A类对象开辟内存空间,再开辟B类对象空间。对象b可以访问A类和B类中的方法,如果B类中存在方法重写,那么访问B类中的重写方法
System.out.println(b.Show(b)); // B and A
b为B类对象,访问B类中的Show(A obj),这里存在向上转型的情况,从下往上匹配,Object类属于所有类的父类,所以先匹配A类参数,所以访问Show(A obj)
System.out.println(b.Show(c)); // B and A
b为B类对象,传入c对象,由于方法重写以及向上转型访问B类中的show(A obj)
System.out.println(b.Show(d)); // A and D
b为B类对象,创建子类对象时先创建父类对象,访问A类中的show(D obj)