JavaSE自学笔记012_Real(多态polymorphism)
(一)概述
多态形成的三个条件:有继承 有重写 有父类引用指向子类对象
多态形成:animal类中有Dog类和Cat类,我们在方法的参数列表中有animal类型,则在调用方法时,传入的参数可以时Dog或者Cat类对象
代码案例实现(养宠物):
//宠物类(Pet类)
public class Pet {
public void coquetry(){
System.out.println("宠物在撒娇");
}
}
//狗类(Dog类)
public class Dog extends Pet{
@Override
public void coquetry() {
System.out.println("狗在撒娇!");
}
}
//猫类(Cat类)
public class Cat extends Pet{
@Override
public void coquetry() {
System.out.println("猫在撒娇!");
}
}
//Girl类
public class Girl {
public void feed(Pet pet){
pet.coquetry();
}
public static void main(String[] args) {
Girl girl = new Girl();
Dog dog = new Dog();
Pet cat = new Cat();
girl.feed(dog);
girl.feed(cat);
girl.feed(new Pet());
}
}
输出结果:
狗在撒娇!
猫在撒娇!
宠物在撒娇
(三)多态的底层逻辑
1、字节码分析:
一段程序从写代码到代码运行会经历编译和运行两个阶段,编译是将.java文件转换为jvm识别的字节码文件,jvm会将字节码文件加载到内存,并执行。
下面一行代码:Animal animal = Math.random() > 5? new Dog() : new Cat();
当代码还没运行的额时候,我们不知道运行结果animal是Dog类型还是Cat类型,只有运行之后才会知道。
我们将Animal叫做animal的静态类型、编译类型、申明类型、外观类型,将右侧的Dog和Cat叫做动态类型、运行时类型、实际类型。
【常量池】是我们的资源仓库,里面存放了大量的【符号引用】(就是我们给类、方法、变量起的名字),这些符号引用有一部分是在类加载阶段或者第一次使用的时候就转化为【直接引用】,这种转化叫做【静态解析】,另一部分会在与逆行期间转化为【直接引用】,这一部分叫做动态解析。
2、字节码反编译的结果:
jvm生成的.class文件
PS D:\NewSoftwares\Javasooftware\javaprojects\JavaSEBasicProjects\out\p
roduction\inheritance\com\ydlclass> javap -v .\computer.class
Classfile /D:/NewSoftwares/Javasooftware/javaprojects/JavaSEBasicProjec
ts/out/production/inheritance/com/ydlclass/computer.class
Last modified 2021年11月23日; size 678 bytes
MD5 checksum c17cb3f2a3937047a0dc64b896a5befa
Compiled from "Computer.java"
public class com.ydlclass.Computer
minor version: 0
major version: 55
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #2 // com/ydlclass/Computer
super_class: #7 // java/lang/Object
interfaces: 0, fields: 0, methods: 3, attributes: 1
Constant pool:
#1 = Methodref #7.#27 // java/lang/Object."<init>":
()V
#2 = Class #28 // com/ydlclass/Computer
#3 = Methodref #2.#27 // com/ydlclass/Computer."<in
it>":()V
#4 = Fieldref #29.#30 // java/lang/System.out:Ljava
/io/PrintStream;
#5 = Methodref #2.#31 // com/ydlclass/Computer.plus
:(II)I
#6 = Methodref #32.#33 // java/io/PrintStream.printl
n:(I)V
#7 = Class #34 // java/lang/Object
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 LocalVariableTable
#13 = Utf8 this
#14 = Utf8 Lcom/ydlclass/Computer;
#15 = Utf8 plus
#16 = Utf8 (II)I
#17 = Utf8 i
#18 = Utf8 I
#19 = Utf8 j
#20 = Utf8 main
#21 = Utf8 ([Ljava/lang/String;)V
#22 = Utf8 args
#23 = Utf8 [Ljava/lang/String;
#24 = Utf8 computer
#25 = Utf8 SourceFile
#26 = Utf8 Computer.java
#27 = NameAndType #8:#9 // "<init>":()V
#28 = Utf8 com/ydlclass/Computer
#29 = Class #35 // java/lang/System
#30 = NameAndType #36:#37 // out:Ljava/io/PrintStream;
#31 = NameAndType #15:#16 // plus:(II)I
#32 = Class #38 // java/io/PrintStream
#33 = NameAndType #39:#40 // println:(I)V
#34 = Utf8 java/lang/Object
#35 = Utf8 java/lang/System
#36 = Utf8 out
#37 = Utf8 Ljava/io/PrintStream;
#38 = Utf8 java/io/PrintStream
#39 = Utf8 println
#40 = Utf8 (I)V
{
public com.ydlclass.Computer();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Objec
t."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/ydlclass/Computer;
public int plus(int, int);
descriptor: (II)I
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: iload_1
1: iload_2
2: iadd
3: ireturn
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 this Lcom/ydlclass/Computer;
0 4 1 i I
0 4 2 j I
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=2, args_size=1
0: new #2 // class com/ydlclass/Com
puter
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1
8: getstatic #4 // Field java/lang/System
.out:Ljava/io/PrintStream;
11: aload_1
12: iconst_2
13: iconst_3
14: invokevirtual #5 // Method plus:(II)I
17: invokevirtual #6 // Method java/io/PrintSt
ream.println:(I)V
20: return
LineNumberTable:
line 9: 0
line 10: 8
line 11: 20
LocalVariableTable:
Start Length Slot Name Signature
0 21 0 args [Ljava/lang/String;
8 13 1 computer Lcom/ydlclass/Computer;
}
SourceFile: "Computer.java"
3、重载方法的静态分派过程
//Human类
public class Human {
}
//Man类
public class Man extends Human{
}
//Woman类
public class Woman extends Human{
}
//Party类
public class Party {
public void play(Human human){
System.out.println("人类的狂欢");
}
public void play(Man man){
System.out.println("男人的狂欢");
}
public void play(Woman woman){
System.out.println("女人的狂欢");
}
public static void main(String[] args) {
Party party = new Party();
Human human = new Human();
party.play(human);
Human man = new Man();
party.play(man);
Human woman = new Woman();
party.play(woman);
}
}
运行结果:
人类的狂欢
人类的狂欢
人类的狂欢
虚拟机在选择重载方法的时候,使用过【静态类型】决定的而不是【动态类型】,由于静态类型编译的时候就可以知道,事实上虚拟机在编译期就已经知道选择哪一个重载方法,并且把这个方法的符号i引用到invokevirtual的指令中。
所有依赖【静态类型】决定党法执行的分派动作称之为静态分派,JVM帮助我们选择一个合适的方法的时候,也是尽最大努力,选择它认为最适合的版本,因为确实存在诸如自动拆装箱,对象转型等问题。
3、重载方法和重写方法的综合使用
代码实现:
//Animal类
public class Animal {
public void eat(){
System.out.println("animal id eating");
}
public void eat(String food){
System.out.println("animal is eating "+food);
}
}
//Dog类
public class Dog extends Animal{
@Override
public void eat(){
System.out.println("Dog is eating");
}
@Override
public void eat(String food){
System.out.println("Dog is eating "+food);
}
public static void main(String[] args) {
Animal animal = new Dog();
animal.eat("meat");
}
}
运行结果:
Dog is eatingmeat
在JVM运行程序的时候,先进行静态解析,确定animal的类型为Animal,然后进行动态分派,确定动态类型为Dog()类型,然后调用Dog()中的eat(String food)方法.
注意:多态只和方法有关,和属性没有关系
(四)对象转型
向上转型:子类对象转为父类,向上转型不需要显式的转化 Father father = son;
向上转型会丢失子类独有的特性
向下转型:父类对象转化为子类,向下转型需要强制转化 Son son = (Son)father;