所谓多态,就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
比如你是一个酒神,对酒情有独钟。某日回家发现桌上有几个杯子里面都装了白酒,从外面看我们是不可能知道这是些什么酒,只有喝了之后才能够猜出来是何种酒。你一喝,这是剑南春、再喝这是五粮液、再喝这是酒鬼酒…在这里我们可以描述成如下:
酒a=剑南春
酒b=五粮液
酒c=酒鬼酒
这里所表现的的就是多态。剑南春、五粮液、酒鬼酒都是酒的子类,我们只是通过酒这一个父类就能够引用不同的子类,这就是多态——我们只有在运行的时候才会知道引用变量所指向的具体实例对象。
文章目录
多态
定义:面向对象三大特征最核心的特性,一个引用可以表现出多种行为或者特性。就是多态性
向上转型:
最大的意义在于参数统一化,降低使用者的使用难度!!
Dog dog = new dog();
//类名称 类引用 = new 该类对象();
Animal animal = new Dog();
//类名称 类引用 = new 子类对象();
//子类is a 子类 -> Dog is a dog
//子类 天然 is a 父类
//天然语义
//Dog is an Animal
举个现实生活中的例子:
现在很多人喜欢在家里养宠物,比如你给你家修勾起名叫六六。
叫你家狗来吃饭,你可以说六六来吃饭了,也可以说修过来吃饭了。
但是在这个场景下,其实六六和修勾指向的是同一个对象,只是这个对象的名字不同而已。
向上转型
向上转型的发生条件:向上转型发生在有继承关系的类之间。
父类名 父类引用 = new 子类实例();
这里的子类不一定是直接子类,也可以是孙子类,重孙子类,子子孙孙无穷尽……
之前对象的产生方式:
Animal animal = new Animal);
Bird bird = new Bird();
Duck duck = new Duck();
使用向上转型产生对象的方式:
比如现在有三个类,分别是Animal,Bird,Duck,后两个类一次继承自己的上一个类。
此时的产生对象的方式就可以用到向上转型,用代码表示为:
//Bird is an Animal
Animal animal1 = new Bird);
//Duck is a Bird
Bird bird = new Duck();
//Duck is a Bird ,is an Animal
Animal animal = new Duck();
可能到这里还有很多人有疑问,这个向上转型有什么作用。接下来举个栗子 :
代码举例:
public class Test {
public static void main(String[] args) {
fun(new Animal());
fun(new Bird());
fun(new Duck());
}
public static void fun(Animal animal) {}
public static void fun(Bird bird) {}
public static void fun( Duck duck) {}
}
作为类的说实现者,我们现在用fun方法接收Animal以及子其子类的对象作为参数。假设现在没有向上转型,A你买了有多少个子类,就要重载多少次fun方法,但是大自然的Animal的种类有上百万种,就有几百万个子类,就要写上百万次fun方法。
作为类的是使用者,程序的使用者,没有向上转型,我要使用fun方法,就得了解Animal以及其子类的所有对象,我才能知道我调用的是谁。
但是这样就会很麻烦,是特别麻烦~~
那么问题来了,既然子类是天然的父类,为什么不用父类去只带所有的子类?
这样向上转型就产生了。
向上转型代码示例
public class Test {
public static void main(String[] args) {
fun(new Animal());
fun(new Bird());
fun(new Duck());
fun(new Dog());
}
public static void fun(Animal animal) {
animal.eat();
}
}
//输出结果
//Animal的eat方法
//Bird的eat方法
//Duck的eat方法
//Dog的eat方法
只要是Animal的子类,都是天然的Animal对象,都满足 is a关系,通过Animal最顶层的父类引用,指代所有的子类对象。
有向上转型之后,最顶端的父类引用就可以只带所有子类对象,当天有一个Animal的新子类时,是非常容易扩展的~~
fun中animal局部变量的引用调用eat方法时,当传入不同的对象时,表现出来了不同的eat方法。
这个方法行为=>表现出多态性。
同—个引用(变量名称),同—个方法名称根据对象的不同表现出来了不同的行为。
这就是多态!!!
方法重载和方法重写
**方法重载(**overload):发生在同一个类类中,定义了若干个方法名称相同,参数列表不同的一组方法。
方法重写(override):发生在有继承关系的类中之间,子类定义了和父类除了权限不同,其他全都相同的方法,这样的一组方法叫做方法重写。
可能和多人到这里会懵了,到底调用的是谁的方法呢?
其实不用去看前半部分,看当前是通过哪个类new的对象,若该类重写了相关方法,则调用的一定是重写后的方法。
千万不要被类名称搞晕,就看new的是谁,只要new的这个对象的类中覆写了同名方法,则调用的一定是覆写后的方法。
若子类没有重写这个方法,则调用的是?
就近匹配原则!!!直接向上寻找,从父类开始向上寻找,若碰到最近的重写方法,直接调用!!
重写发生条件
当发生重写时,子类权限必须大于等于父类权限才可以重写。
问题1:
父类方法使用private权限,子类方法使用public权限,是否可以?
答:不行!!!
虽然public权限大于private权限,但是方法重写的发生条件是不包含private的。
@Override注解
Java中有一个注解叫@Override,使用这个注解写在重写方法之前,帮你检验你的方法重写是否符合规则。
问题2:
能否重写static方法?
答:不能!!!
多态的本质就是调用了不同子类的对象,这些子类对象所属的类进行了方法的重写。才能表现出不同的行为。
然而static个对象无关,这样你怎么重写一个static方法?
方法重写只发生在普通方法中。
方法重载和方法重写钟总结
No. | 区别 | 重载(overload) | 重写(override) |
---|---|---|---|
1 | 概念 | 方法名称相同,参数的类型及其个数的不同 | 方法名称,返回值类型次,参数的类型以及个数完全相同,只有权限不同 |
2 | 范围 | 同一个类中 | 存在继承关系的类之间 |
3 | 限制 | 没有权限要求 | 被重写的方法不能拥有不父类更严格(小的)访问控制权限,即大于等于 |
4 | static | 没要求 | 不能重写static方法 |
向上转型发生的时机
向上转型发生的时机一共有三种:
-
方法传参(使用最多的)
-
引用赋值
-
返回方法值
问题:
看下面代码,选择答案。
public class B {
public B() {
fun(); //②
}
public void fun() {
System.out.println("B.fun");
}
}
public class D extends B {
//super();
//实际上在这默认存在一个super();去调用父类的无参构造
private int num = 10;
public void fun() { //③
System.out.println("D.fun,num = " + num);
}
public static void main(String[] args) {
D d = new D(); //①
}
}
//选择
//A.没有输出
//B.输出B.fun();
//C.输出D.fun(),num = 10;
//D.输出D.fun(),num = 0;
//答案:D
思考:
第①步,这段代码的执行流程为,首先要产生D的对象,就要调用D的无参构造,但是此时存在继承关系,就要先调用B的构造方优先产生父类对象,
第②步,调用B的构造法之后,由于B类中的sun方法在D中存在fun的复写方法,所以去执行复写方法。
第③步,就到了D中的复写方法fun中,这时候还没执行D的构造方法呢,所有值都是默认值,以为要为成员变量赋初值需要在构造方法中执行。
小结:
向上转型
父类名称 父类引用 = new 子类对象();
天然发生的向上转型
使用父类引用调用普通方法时,若子类重写了该方法,则调用该对象所在子类重写后的方法。
Animal animal = new Dog();
animal.eat(); //调用Dog类覆写后的方法
问题:
假设此时 Dog类中拓展一个play();方法,但是这个方法在Animal中并不存在,还能通过animal.play();调用吗??
答案:
不能!!!
原因:
当用
类名称 引用名称 = new 类实例();
时,想要通过引用名称.方法名称();
有个前提——能通过"."访问的方法类名称说了算,能访问的这些方法必须都在类中定义过,编译器会先在类中查找是否包含指定方法。至于这个方法到底表现出来是哪个类的样子,实例所在的方法说了算。
也就是说,到底能".“哪些方法,前面说了算,到底”."之后方法长什么样子,后面new的说了算~~
那么如果我现在就想调用其中的play方法呢?
酒席要用到一个新的概念——向下转型
向下转型
Animal animal = new Dog();
animal.play();
animal这个引用实际上是披着Dog皮的Animal,本质上是个dog,批了个animal的外衣,此时只能调用Animal中定义的方法~~
想要调用子类的拓展方法play,就需要脱掉这层外衣,花园为子类引用,也就是向下转型。
向下转型定义方法
子类名称 子类医用 = (子类名称)父类引用
Dog dog = (Dog) animal
,其实和强制类型转换类似。
将父类类型强制转换成子类引用。
子类 is a 父类 -> 天然的 Dog is an animal
父类不一定 is a 子类 -> Animal is a Dog?
错误代码示例:
public class Dog extends Animal {
public void eat() {
System.out.println("Dog的eat方法");
}
public void play() {
System.out.println("这是个拓展方法");
}
public static void main(String[] args) {
Animal animal = new Dog();
animal.play();
}
}
正确方法代码示例:
public class Dog extends Animal {
public void eat() {
System.out.println("Dog的eat方法");
}
public void play() {
System.out.println("这是个拓展方法");
}
public static void main(String[] args) {
Animal animal = new Dog();
Dog dog = (Dog) animal;
dog.play();
}
}
//输出结果:这是个拓展方法
向下转型发生条件
要发生向下转型,首先要发生向上转型。
例如:
Animal animal = new Animal();
就无法使用 Dog dog = (Dog) animal;
这是两个毫不相关的类型,无法进行强转,就想int和Boolean之间无法强转~~
instanceof关键字
当发生向下转型时,会有风险!容易产生类型转换异常,使用instanceof关键字表示该引用指向的是不是该类的对象(返回布尔值)。
使用方法:引用名称 instanceof 类
代码示例:
public static void main(String[] args) {
Animal animal1 = new Animal();
Animal animal2 = new Dog();
if (animal1 instanceof Dog) {
Dog dog = (Dog) animal1;
System.out.println(animal1 + "转型成功!");
} else {
System.out.println(animal1 + "不是指向Dog类型的引用");
}
if (animal2 instanceof Dog) {
Dog dog = (Dog) animal2;
System.out.println(animal2 + "转型成功!");
} else {
System.out.println(animal2 + "不是指向Dog类型的引用");
}
}
}
//输出结果
//polymorphism.Animal@1b6d3586不是指向Dog类型的引用
//polymorphism.Dog@4554617c转型成功!
小结
啥时候发生向上转型,方法接收一个类和当前类的子类,参数指定为相应的父类引用,发生的就是向上转型
只有某个特殊的情况下,需要使用子类拓展的方法,才需要将原本向上转型的引用向下转型还原为子类引用。
面相对性的三大特性——封装,多态,继承,到这里也就介绍完了,感谢支持。三连关注,不迷路~~