1.概述
多态是Java面向对象三大特征之一。
多态(Polymorphism),顾名思义,即对象具有多种形态。具体而言,是编译时类型、运行时类型。
- 编译时类型:由声明时的类型决定。一般是父类。
- 运行时类型:由实际对应的对象类型决定。具体是哪个子类就是哪个子类。
多态性是OOP中的一个重要特性,主要是用来实现动态联编的,换句话说,就是程序的最终状态只有在执行过程中才被决定而非在编译期间就决定了。对于大型系统来说能提高系统的灵活性和扩展性。
多态存在要有三个必要条件
要有继承、方法重写、父类引用指向子类对象。
2.代码分析
如下,我们通过一段代码进行演示,并进一步进行内存分析
首先创建一个Animal类,及其子类Cat和Dog,并重写Animal的talk方法:
public class Animal {
public void talk() {
System.out.println("yell...");
}
}
class Cat extends Animal {
@Override
public void talk() {
System.out.println("喵喵。。。");
}
public void catchMouse() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
@Override
public void talk() {
System.out.println("汪汪。。。");
}
}
我们在这里建立一个测试类Test01,用于调用
public class Test01 {
public static void testTalk(Animal a) {
a.talk();
}
// 创建重载方法,传递Cat类型参数
public static void testTalk(Cat c) {
c.talk();
}
// 创建重载方法,传递Dog类型参数
public static void testTalk(Dog d) {
d.talk();
}
public static void main(String[] args) {
Animal a = new Animal();
Cat c = new Cat();
Dog d = new Dog();
testTalk(a);
testTalk(c);
testTalk(d);
}
}
这是我们之前的做法,这样就存在一个问题,如果,我们还有其他类,比如Mouse等之类的继承Animal,这样就需要每个类都写一个重载方法,及其繁琐。
这段代码我们可以通过多态的方式实现,创建使用多态的测试类Test
public class Test {
public static void testTalk(Animal a1) {
a1.talk();
if (a1 instanceof Cat) {
Cat c = (Cat) a1;
c.catchMouse();
}
}
public static void main(String[] args) {
Animal c = new Cat();
testTalk(c);
Animal d = new Dog();
testTalk(d);
}
}
接下来我们针对以上代码,进行内存分析。
3.内存分析
这里的核心代码只有三句话,我们主要分析如下三句即可。
如下,我们把Test类简化
public class Test {
public static void testTalk(Animal a1) {
a1.talk();
if (a1 instanceof Cat) {
Cat c = (Cat) a1;
c.catchMouse();
}
}
public static void main(String[] args) {
Animal c = new Cat();
Cat a = (Cat) c;
testTalk(c);
}
}
3.1加载Test类信息
对应图解步骤1.
注:事实上,在启动时,以上所有类的代码信息均被加载到了JVM中,这里便于理解,采用用到才加载的方式。
把Test的类信息加载到JVM中。
3.2 Animal c = new Cat();
3.2.1 把Animal的类信息加载到JVM中。
对应图解步骤2.
3.2.2 栈空间中生成局部变量c
对应图解步骤3.
3.2.3 调用Cat的构造器生成对象
这里需要指出的是:会首先加载该类的父类的信息,因而在这一步会:
1. 生成Object对象。
对应步骤4
2. 生成Animal类对象。
对应步骤5
3. 生成Cat类对象。
对应步骤6
此外,每个方法都会传递两个隐式参数,this和super。而这三个对象(Object、Animal、Cat对象)的super分别代表Object、Object、Animal对象。但是this指的全都是Cat对象。
关于this关键字,链接:this关键字详解
需要注意的是:图中的三个this指向的是同一个对象:最外层的对象。
关于super关键字,链接:super关键字详解
我们做个简单的实验说明这一点:
创建要给测试父类:
public class HttpServlet {
public void service() {
System.out.println("HttpServlet.service()");
doGet();
}
public void doGet() {
System.out.println("HttpServlet.doGet()");
}
public void goPost() {
System.out.println("HttpServlet.goPost()");
}
}
一个子类
public class Myservlet extends HttpServlet {
public void doGet() {
System.out.println("Myservlet.doGet()");
}
}
一个测试类:
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
HttpServlet s = new Myservlet();
s.service();
}
}
运行输出结果:
HttpServlet.service()
Myservlet.doGet()
可以看出,this始终指向最外围的对象。
3.2.4 变量c指向Cat对象
3.3 Cat a = (Cat) c
栈空间生成局部变量a。把c指向的地址赋值给a,即a也指向该Cat对象。
3.4调用testTalk()方法
在该方法中,把c的地址指向传递给a1。
如下图: