目录
问题:父类方法使用 private ,子类方法使用 public 是否可以重写?
多态性
多态:同一个引用可以表现出多种行为或特性。
最主要的多态的表现形式:继承+方法重写。
多态性是面向对象编程的又一个重要特征,它是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。
对面向对象来说,多态分为编译时多态和运行时多态。其中编译时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的方法。通过编译之后会变成两个不同的方法,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是大家通常所说的多态性。
Java 实现多态有 3 个必要条件:继承、重写和向上转型。只有满足这 3 个条件,开发人员才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而执行不同的行为。
- 继承:在多态中必须存在有继承关系的子类和父类。
- 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
- 向上转型:在多态中用父类所产生的引用来接受所有子类对象,只有这样该引用才既能可以调用父类的方法,又能调用子类的方法。
例如:都是 Animal 类的子类,都有 eat 方法,不同子类对象调用 eat 方法表现出来的行为就不同(调用的是相应子类覆写后的 eat 方法)。
向上转型
向上转型:发生在有继承关系的类之间。最大的意义在于参数统一化(使用一个共同父类所产生的引用来接受所有子类对象),降低使用者的使用难度。
常见的创建对象:
类名称 类引用= new 该类对象();
子类 is a 子类——>语义:Dog is a dog
Dog dog = new Dog();//最常见的
向上转型的创建对象:
父类名称 父类引用= new 子类对象(); //此处 new 的不一定是直接子类,也可以是孙子类。
子类天然 is a 父类——>天然语义:Dog is an Animal
Animal animal = new Dog();//向上转型
![]()
假设没有向上转型
package polymorphism; public class Test { public static void main(String[] args) { // 2.作为类的使用者,程序的使用者 // 没有向上转型,要使用fun方法,就得了解Animal以及其子类的所有对象 // 我才能知道调用的是谁 fun(new Animal()); fun(new Bird()); fun(new Duck()); } //1.作为类的实现者 // fun方法接收AnimaL以及其子类的对象作为参数 // 假设现在没有向上转型,AnimaL有多少子类,我就得重载多少次fun方法 // 大自然 Animal 动物子类有几百万种,就有几百万种子类,fun 就得写上百万次! public static void fun(Animal animal) {} public static void fun(Bird bird) {} public static void fun(Duck duck) {} }
既然子类是天然的父类(Dog is an Animal),为何不能用父类去指代所有的子类,向上转型因此就产生了。
有向上转型后的代码
只要是 Animal 及其子类,都是天然的 Animal 对象,都满足is a关系。
最顶层的父类 Animal 引用,指代所有的子类对象。此时 Animal 是非常容易扩展一个新的子类的。//Animal.java Animal类单独的java文件 public class Animal { public void eat() { System.out.println("Animal类的eat方法"); } } //Bird.java Bird类单独的java文件 public class Bird extends Animal{ public void eat() { System.out.println("Bird类的eat方法"); } } //Duck.java Duck类单独的java文件 public class Duck extends Bird{ public void eat() { System.out.println("Duck类的eat方法"); } } //Dog.java Dog类单独的java文件 public class Dog extends Animal{ public void eat() { System.out.println("Dog类的eat方法"); } } //Test.java Test类单独的java文件 package polymorphism; public class Test { public static void main(String[] args) { Animal animal1 = new Animal(); Animal animal2 = new Bird(); Animal animal3 = new Duck(); Animal animal4 = new Dog(); //新增 Dog 类 fun(animal1); fun(animal2); fun(animal3); fun(animal4); } // 只要是Animal及其子类,都是天然的Animal对象,都满足is a关系 // 通过AnimaL最顶层的父类引用,指代所有的子类对象。 public static void fun(Animal animal) { animal.eat(); } }
fun 中 animal 局部变量的引用调用 eat 方法时,当传入不同的对象时,表现出来了不同的 eat 方法行为叫做多态性。
多态性:同一个引用(变量名称),同一个方法名称根据对象的不同表现出来了不同的行为。
方法重写
方法重载(overload):发生在同一个类中,定义了若干个方法名称相同,参数列表不同的一组方法。
方法重写(override):发生在有继承关系的类之间。子类定义了和父类除了权限不同,方法名称,返回值,参数列表完全相同(返回值完全相同或者至少是向上转型类的返回值),这样的一组方法称之为方法重写。
不用去看前半部分,看当前是通过哪个类 new 的对象,若该类重写了相关方法,则调用的一定是重写后的方法。
只要new的这个对象的类中覆写了同名方法,则调用的一定是覆写后的方法。
若子类没有重写方法,调用的是什么?
//Animal.java Animal类单独的java文件 public class Animal { public void eat() { System.out.println("Animal类的eat方法"); } } //Bird.java Bird类单独的java文件 public class Bird extends Animal{ public void eat() { System.out.println("Bird类的eat方法"); } } //Duck.java Duck类单独的java文件 public class Duck extends Bird{ //没有重写eat方法 } //Test.java Test类单独的java文件 package polymorphism; public class Test { public static void main(String[] args) { Animal animal1 = new Animal(); Animal animal2 = new Bird(); Animal animal3 = new Duck(); fun(animal1); fun(animal2); fun(animal3); } public static void fun(Animal animal) { animal.eat(); } }
就近匹配规则,碰到最接近的调用。
当发生重写时,子类权限必须 >= 父类权限才可以重写。
private < default < protected < public。
当子类权限<父类权限时
//Animal.java Animal类单独的java文件 public class Animal { public void eat() { System.out.println("Animal类的eat方法"); } } //Bird.java Bird类单独的java文件 public class Bird extends Animal{ protected void eat() { System.out.println("Bird类的eat方法"); } } //运行时报错
问题:父类方法使用 private ,子类方法使用 public 是否可以重写?
public > private
答: private 权限不包含在内,父类私有方法没有被覆写。
//Duck.java Duck类单独的java文件 package polymorphism; public class Duck extends Bird{ public void eat() { System.out.println("Duck类的eat方法"); } } //Bird.java Bird类单独的java文件 package polymorphism; public class Bird extends Animal{ public void eat() { System.out.println("Bird类的eat方法"); } } //Animal.java Animal类单独的java文件 package polymorphism; public class Animal { private void eat() { System.out.println("Animal类的eat方法"); } public static void main(String[] args) { Animal animal1 = new Animal(); Animal animal2 = new Bird(); Animal animal3 = new Duck(); fun(animal1); fun(animal2); fun(animal3); } public static void fun(Animal animal) { animal.eat(); } } //输出结果 Animal类的eat方法 Animal类的eat方法 Animal类的eat方法
Java中的注解@Override
使用这个注解写在重写方法之前,可校验方法重写是否符合规则。此处显示方法不可重写。
问题:能否重写 static 方法?
答:多态的本质就是因为调用了不同的子类“对象”,这些子类对象所属的类,覆写相应的方法才能表现出不同的行为。但是static和对象无关,所以不能重写 static 方法。
//Animal.java Animal类单独的java文件 public class Animal { public static void eat() { //使用static修饰 System.out.println("Animal类的eat方法"); } } //Bird.java Bird类单独的java文件 public class Bird extends Animal{ protected void eat() { //此处出现错误 System.out.println("Bird类的eat方法"); } } //报错
判断父类是否被覆写、子类是否重写父类方法
方法重写的返回值必须严格相同,向上转型类除外
1. 返回值不相同的情况
2. 返回值为向上转型类
运行成功
3. 返回值为向下转型
运行出错,不算方法重写
小结:
NO 区别 重载(overload) 覆写(override) 1 概念 方法名称相同,
参数的类型及个数不同
返回值无关
方法名称、返回值类型、
参数的类型及个数
完全相同(或返回值为向上转型类)
2 范围 一个类中 继承关系的类 3 限制 没有权限要求 被覆写的方法不能拥有
比父类更严格的访问控
制权限(>=)(不含private)
4 用于static
没有要求 不能重写static方法
向上转型发生的时机
引用赋值、方法传参、方法返回值
题目练习:求输出结果?
//B.java B类单独的java文件 package polymorphism; public class B { public B() { } public void fun() { System.out.println("B的fun方法"); } } //D.java D类单独的java文件 package polymorphism; public class D extends B { private int num = 10; public void fun() { System.out.println("D的fun方法,num = " + num); } public static void main(String[] args) { D d = new D(); } }
答:
使用父类引用调用普通方法时,若子类重写了该方法,则调用该对象所在子类覆写后的方法。
题目:假设此时Dog类中有一个扩展方法play(),这个方法Animal类中不具备,此时还能通过 animal.play() 吗?
//Animal.java Animal类单独的java文件 public class Animal { public void eat() { System.out.println("Animal类的eat方法"); } } //Dog.java Dog类单独的java文件 public class Dog extends Animal{ public void eat() { System.out.println("Dog类的eat方法"); } public void play() { System.out.println("Dog类独有的play方法"); } public static void main(String[] args) { Animal animal = new Dog(); animal.eat(); animal.play(); //报错 Animal 类中没有 play 方法 } }
答:不能
类名称 引用名称 = new 类实例();
引用名称.方法名称();能通过" . "访问的方法,类名称说了算。能访问的这些方法必须都在类中定义过,编译器会充在类中查找是否包含指定方法。
至于这个方法到底表现出来是哪个类的样子,实例所在的方法说了算。
Animal animal = new Dog();
animal.play();
animal 这个引用在本质上是个 Dog,批了个 Animal 的外衣,此时只能调用 Animal 中定义的方法。
若此时就想调用子类扩展的方法
脱掉这层 Animal 外衣,还原为子类引用——>向下转型
子类名称 子类引用 = (子类名称) 父类引用;
Dog dog =(Dog) animal; //脱掉 animal 对应的对象的外衣还原为具体的子类引用
向下转型
将父类引用强制类型转换为子类引用。
子类is a父类——>天然的 Dog is an animal父类is a子类(不—定), Animal is a Dog?
通过向下转型使用子类独有方法
要发生向下转型,首先要发生向上转型
毫无关系的两个类之间没法强转(如 int boolean类型)
Animal animal = new Dog(); //本质是个 Dog 类对象(向上转型) Dog dog = (Dog) animal; //向下转型(强制类型转换)正确 Animal animal = new Animal(); //本质是个 Aniaml 类的对象,与 Dog 类无关 Dog dog = (Dog) animal; //向下转型(强制类型转换)错误
报错,类型转换异常。强制把两个毫无关系的类的引用进行强转。
instanceof 关键字
当发生向下转型时会有风险,类型转换异常,使用 instanceof 关键字。
引用名称 + instanceof + 类——>返回布尔值,表示该引用名称指向的是不是该类的对象 。
使用 instanceof 关键字的返回值搭配分支语句进行类型转换
instanceof 关键字的作用是判断左边对象是否是右边类的实例(子类对象或右边类本身的对象)返回的boolean类型,true 或 false。
小结
什么时候发生向上转型:
public class Test { public static void main(String[] args) { Animal animal = new Dog();//向上转型 fun(animal);//父类引用animal } public static void fun(Animal animal) { animal.eat(); } }
方法接收一个类和当前类的子类,参数指定为相应的父类引用,发生的就是向上转型。
什么时候发生向下转型:
只有某些特殊的情况下,需要使用子类拓展的方法,才需要将原本向上转型的引用向下转型,还原为子类引用。