JAVA面向对象之多态(面向对象的多态)
2022-03-01
JAVA面向对象之多态
多态的形成
多态的形成条件大概有三个
#1、有继承
#2、有重写
#3、有父类引用指向子类对象
我们可以写下面一个例子
Animals.java
public class Animals {
public void run(){
System.out.println("Animals is runing");
}
}
Dog.java
public class Animals {
public void run(){
System.out.println("Dog is runing");
}
}
Cat.java
public class Animals {
public void run(){
System.out.println("Cat is runing");
}
}
Girl.java
public class Girl {
public void feed(Animals animals){
animals.run();
}
public static void main(String[] args) {
Animals animals = new Animals();
Animals dog = new Dog();
Animals cat = new Cat();
Girl girl = new Girl();
girl.feed(animals);
girl.feed(dog);
girl.feed(cat);
}
}
我们得到的结果是
Animals is runing
Dog is runing
Cat is runing
这个结果其实在我们的意料之中,为什么呢,因为我们传入feed
的就是animals
,dog
,cat
,这其实就是一种多态,
但是我想说,我们定义的是一个Animals啊,这是不是无法解释的通了,不急我们往下看
静态类型和动态类型
我们来看一段小代码
Animal animal = Math.random() > 0.5 ? new Dog():new Cat();
我们知道animals
指向的一只猫还是一条狗嘛?
实际上我们并不知道,指向的是谁只有程序运行起来我们才知道,但是我们知道的是一定指向animals的类或者他的子类
那我们就叫 Animal为 animal 的静态类型,或者叫编译类型或者叫申明类型或者叫外观类型。而等于右侧的我们叫动态类型,也叫运行时类型或者叫实际类型。
重载方法的调用
我们在调用一个虚方法的时候,jvm会在适当的时候帮我们选择合适的方法版本,有的时候在编译期、有时是在运行时,这个方法版本的选择过程我们可以称之为方法分派
。
当设计类时。方法的重写使得子类能够重写父类的方法。
当子类对象调用重写的方法时,调用的是子类的方法,而不是父类中被重写的方法。
Java虚方法你可以理解为java里所有被overriding的方法都是virtual的,所有重写的方法都是override的。
我们看一个例子
Human.java
public class Human {
}
Man.java
public class Man extends Human{
}
Woman.java
public class Woman extends Human{
}
Party.java
public class Party {
public void play(Human human){
System.out.println("Human is playing!");
}
public void play(Man man){
System.out.println("Man is playing!!");
}
public void play(Woman woman){
System.out.println("Woman is playing!!");
}
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);
}
}
得到的结果一定出乎你的意料
Human is playing!
Human is playing!
Human is playing!
事实上这是因为虚拟机在选择重载方式时,是通过静态类型决定的而不是动态类型。由于静态类型编译时就可知,事实上虚拟在编译期就已经知道选择哪一个重载方法,并且把这个方法的符号引用写在了invokevirtual的指令中。
在这个例子上面就是编译前我就知道我要调用的是第一个play方法
重写方法的调用
先在子类中找,子类中没有到父类中找
用多态的形成中的例子就是,先在dog中找run的方法,dog中没有的时候去animals中找,这个是符合实际思维的
重写方法的调用时依据运行时的类型决定的
重载和重写
重载只是选择了调用方法的版本。
重写是具体明确了调用谁的方法。
举一个略显变态的例子
Animal.java
public class Animal {
public void eat(){
System.out.println("animal is eating!");
}
public void eat(String food){
System.out.println("animal is eating "+food);
}
}
dog.java
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 eating meat
其实不难理解java重载会选择Animal::eat(String food)这个方法,具体的调用谁的方法取决的动态类型,我们的动态类型是Dog就会调用狗的方法