一、什么是多态
多态指多种形态,多态允许同一个方法在不同对象中表现出不同的行为。换句话说,在多态的情况下,相同的接口可以指向不同的实现。
父类的引用指向子类的对象,子类的对象也可以向上转型到父类的类型接收,即一个类的句柄可以指向自己类的对象或者是自己子孙后代类的对象,从而实现不同子类对象的特殊化操作或统一操作。
例如,小明在保定找到一家驴肉火烧,这家驴肉火烧店有意思,用长火烧做驴肉火烧,用圆火烧做夹土豆丝。长火烧和圆火烧都是火烧,一个用来夹驴肉,一种用来夹土豆丝。这就是多态,即同一事件(如夹东西),在不同的事物(长火烧和圆火烧)上就会产生不同的结果(驴肉火烧和夹土豆丝)。
二、Java变量的数据类型指定
要彻底理解Java类与继承类在内存中的表现形式,就要理解Java中不同数据类型的内存存储差异。在 Java 中,数据类型分为基本数据类型和引用数据类型,它们在内存中的存储形式是不同的。
基本数据类型是 Java 中最基础的数据类型,它直接存储数据的值。常见的基本数据类型包括:byte
、short
、int
、long
、float
、double
、char
和 boolean
。每种基本数据类型占用的内存大小是固定的,并且它们在内存中的存储方式是直接存储值。
以 int
、byte
和 float
为例,它们在内存中的存储形式如下:
int 类型:占用 32 位(4 字节),其中 1 位是符号位,31 位是数值位。例如,int a = 10;
在内存中的二进制表示为:
00000000 00000000 00000000 00001010
byte 类型:占用 8 位(1 字节),1 位符号位,7 位数值位。例如,byte a = 10;
在内存中的二进制表示为:
00001010
float 类型:占用 32 位(4 字节),包含 1 位符号位、8 位指数位和 23 位尾数位。例如,float a = 10.0f;
在内存中的二进制表示为:
0 10000001 01000000000000000000000
由上面的基本数据类型的例子可以得到:不同的数据类型在内存中的存储形式是不一样的,即Java 的数据类型决定了数据在内存当中的存储形式。
三、父类 Animal 和子类 Cat
java">class Animal{}public class Cat extends Animal{public static void main(String[] args){Animal cat = new Cat();}
}
对于上面类 Animal 和类 Cat 的继承关系,类 Animal 的句柄 cat 指向了一个 Cat 对象,懵啦,这Animal 约束的句柄怎么能指向一个非Animal类的对象呢?好,那么带着这个疑问继续往下看。
根据继承中,创建子类对象之前先创建父类对象的规则,当我们 new Cat() 后,内存中有一块既有类Animal 的方法和变量又有类 Cat 的方法和变量的空间,如下图(省略方法区,只针对堆区和栈区)。
main方法入栈,cat变量指向一个Cat对象,但是cat是Animal类型,父类类型修饰的子类对象只能调用父类中未被子类重写的方法,以及子类中重写的方法。咱们举个例子简单说。
java">class Animal{public void Eat(){System.out.println("Animal的吃");}public void Eat(String name){System.out.println(name+"在吃");}
}public class Cat extends Animal{public void Eat(){System.out.println("Cat的吃");}public static void main(String[] args){Animal cat = new Cat();cat.Eat(); // 输出:Cat的吃}
}
现在Animal类中有两个Eat方法,一个有参一个无参,Cat类中有一个无参的Eat方法,那么Animal类中无参的Eat方法就被Cat类中无参的Eat方法重写,所以cat调用Eat()方法时,会输出
Cat的吃
文字比较绕口的话,可以看看下图
如果在main方法中再添加一行代码:
java">Cat cat_all = new Cat();
那么cat_all 可以调用Animal类中的方法,以及Cat类中的方法。
四、ABCD四个类理解向上转型
1、什么是向上转型
多态可以解决Java语言设计当中的问题——数据的向上转型,即数据类型的转换。
多态使用向上转型来解决数据类型的转换,即子类的对象可以由父类的类型接收,以扩充当前对象的能力,让一个行为表现出不同的形态和形式
2、直接上类
看代码
java">class A {public String Show(D obj) {return "A and D";}public String Show(A obj) {return "A and A";}public String Show() {return "无参的A";}
}
class B extends A{public String Show(Object obj) {return "B and B";}public String Show(A obj) {return "B and A";}public String Show() {return "无参的B";}
}
class C extends B{}
class D extends B{}
public class Test {public static void main(String[] args) {A a1 = new A();A a2 = new B();B b1 = new B();C c = new C();D d = new D();System.out.println(a1.Show());System.out.println(a2.Show());System.out.println(b1.Show());System.out.println(a1.Show(b1));System.out.println(a1.Show(c));// 不用向上转型System.out.println(a1.Show(d));System.out.println(a2.Show(b1));System.out.println(a2.Show(c));System.out.println(a2.Show(d));System.out.println(b1.Show(b1));System.out.println(b1.Show(c));System.out.println(b1.Show(d));}
}
ABCD满天飞的,看懵了吧,咱用内存图一点点分析每句System.out.println
1. System.out.println(a1.Show());
A a1 = new A();
System.out.println(a1.Show());
这句非常简单,A类对象调用自己类里的无参Show方法,输出“无参的A”。无关继承和多态。
2. System.out.println(a2.Show());
A a2 = new B();
System.out.println(a2.Show());
A类接收子类B的对象,那么a2只能使用A类中方法,B类又重写了A类中无参Show方法和参数为A类型的Show方法,那么此句输出“无参的B”
3. System.out.println(a1.Show(b1));
A a1 = new A();
System.out.println(a1.Show(b1));
咱们发现了,所有方法中没有参数为B类型的方法啊。此时就涉及到向上转型。B类继承了A类,Java会默认将b1向上转型,b1所指向的内存空间中有A类的内容,可以完成转型。那么此句输出“A and A”
完整的输出内容如下
无参的A
无参的B
无参的B
A and A
A and A
A and D
B and A
B and A
A and D
B and A
B and A
A and D
五、总结多态的实现条件
1、继承:子类必须继承父类才能实现多态;
2、方法重写:子类必须重新父类的方法;
3、父类引用指向子类对象。