同一个引用 调用了 同一个方法,但是因为引用的对象不一样,所表现的行为不一样,我们把这种思想称为:多态
目录
学会多态的前提:
(1)继承(2)向上转型(3)重写
继承在之前的课程已经介绍过,下面介绍向上转型和重写,这两种也是发生在继承的基础上。
1.向上转型
1.1.向上转型定义
概念:父类引用 引用 子类对象
语法格式:
父类类型 引用名 = new 子类类型();
例如:Parent是父类类型,Son是子类类型,此时就发生了向上转型
Parent p = new Son();
1.2.向上转型的优缺点
优点:
(1)可以实现代码的灵活使用 (2)实现多态的一种条件
缺点:
(1)虽然引用了子类对象,但是也无法访问子类对象中特有的内容
这是一个父子类:
public class Animal {
public String name;
public int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println(name+"正在吃……");
}
}
class Dog extends Animal {
public String color;
public Dog(String name, int age,String color) {
super(name, age);
this.color = color;
}
public void sleep() {
System.out.println(name+"正在睡觉!");
}
}
当访问的时候: 父类引用无法访问子类单独的方法
(2)但是可以访问父类子类都存在的方法(重写),这种是发生了动态绑定,在下面的重写部分介绍
1.3.向上转型的内存指向
向上转型就相当于,把子类对象当成父类使用。如果需要调用子类独有的属性,需要通过向下转型回来才能使用。
1.4.向上转型的三种方式
父类、子类
public class Animal {
public String name;
public int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println(name+"正在吃……");
}
}
class Dog extends Animal {
public String color;
public Dog(String name, int age,String color) {
super(name, age);
this.color = color;
}
public void eat() {
System.out.println("正在吃史");
}
public void sleep() {
System.out.println(name+"正在睡觉!");
}
}
1.4.1.直接赋值
第一种:
public static void main(String[] args) {
Dog dog = new Dog("旺财",2,"黄色");
Animal animal = dog;//向上转型
animal.eat();
}
第二种:
public static void main(String[] args) {
Animal animal = new Dog("旺财",2,"黄色");
animal.eat();
}
1.4.2传参方式
public static void func(Animal animal) {
animal.eat();
animal.age = 3;
}
public static void main(String[] args) {
Dog dog = new Dog("旺旺",1,"黑色");
func(dog);
}
1.4.3.通过返回值
public static Animal func2() {
return new Dog("旺旺",1,"黑色");
}
public static void main(String[] args) {
Animal animal = func2();
animal.name ="666";
}
介绍了向上转型,在下面介绍向下转型。
1.5.向下转型
向下转型一般是使用在向上转型之后,因父类引用无法访问子类特有的方法
转型方式:
public static void main(String[] args) {
Animal animal = new Dog("1",1,"1");
animal.eat();
Dog dog = (Dog)animal;
dog.sleep();
((Dog) animal).sleep();
}
注意事项:
总结:
向上转型是安全的,而向下转型是不安全的,慎用。比如:狗一定是动物,但是动物不一定是狗。
做法:经历对实现了向上转型的对象,再使用向下转型
2.重写
重写发生的条件:发生继承
2.1.概念
2.1.1.命名
重写,又称为覆写,覆盖。
2.1.2.啥是重写
对父类的方法进行重新编写
例如:
2.2.重写的规则
重写的模板:
(1)返回值、形参都不能被修改,必须相同。(返回值如果构成父子类关系也可以)
(2)子类重写的方法的修饰访问权限的大小 >= 父类的
不能被重写的:
(1)被final修饰的方法
(2)静态方法
(3)构造方法
(4)被private修饰的方法
以上父类中的方法都是不能被子类重写的!
注解:
(1)重写的注解:@Override
(2)作用:可以标识是被重写的方法;可以帮助校验重写的格式、语法是否正确,否则会报错。
2.3.重写与重载
区别点 | 重写(Override) | 重载(Overloading) |
---|---|---|
参数列表 | 一定不能修改 | 必须修改 |
返回值类 | 一定不能修改(除非构造父子类关系) | 可以修改 |
访问限定符 | 一定不能做到更严格的限制(可以降低限制) | 可以修改 |
2.4.动态与静态绑定
2.4.1.静态绑定
也称为前期绑定,这种代表就是方法重载,在编译时候(代码写完的时候),根据用户传递的参数就确定了 会调用的方法。
2.4.2.动态绑定
也称为后期绑定,典型代表就是重写。在编译期间,是不确定会调用哪一个方法的,只有在程序运行时,才能确定调用的方法。
2.5.避免在构造方法中调用重写的方法
(1)第一种情况
class B {
public B() {
func();//在构造方法中,调用重写方法(子类父类都有)
}
public void func() {
System.out.println("B.func()");
}
}
class D extends B {
@Override
public void func() {
System.out.println("D.func() ");
}
}
public class Test2 {
public static void main(String[] args) {
D d = new D();
}
}
在父类的构造方法中,调用了重写的方法。在实例化子类对象的时候,调用的是子类的func()方法。说明在构造方法内,也会发生动态绑定。
(2)第二种情况
class B {
public B() {
func();//在构造方法中,调用重写方法(子类父类都有)
}
public void func() {
System.out.println("B.func()");
}
}
class D extends B {
private int num = 1;
@Override
public void func() {
System.out.println("D.func() "+num);
}
}
public class Test2 {
public static void main(String[] args) {
D d = new D();
}
}
实例化子类对象时,为什么不会给num赋值
代码指向顺序:实例化子类对象-->子类构造-->父类的构造方法-->调用重写方法,此时num没有被赋值。(当实例化子类的时候,会给子类对象分配一个内存空间,此时属性都会被加载出来,但是没有进行初始化,就默认为0)
说明:
3.使用多态
在上面介绍向上转型的时候,就已经接触到一点多态的影子了。
一个父类、两个子类
public class Animal {
public String name;
public int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println(name+"正在吃……");
}
}
class Dog extends Animal {
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("正在吃狗粮……");
}
}
class Cat extends Animal {
public Cat(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("正在吃鱼翅……");
}
}
3.1.简单的多态实现
3.1.1.先发生重写
3.1.2.发生向上转型
我们使用传参的方式进行向上转型
public static void func(Animal animal) {
animal.eat();
}
public static void main(String[] args) {
Dog dog = new Dog("小狗",1);
Cat cat = new Cat("小猫",2);
func(dog);
func(cat);
}
3.1.3.多态的发生
(1)多态发生
(2)多态的概念
同一个引用 调用了 同一个方法,但是因为引用的对象不一样,所表现的行为不一样,我们把这种思想称为:多态
3.2.实现自定义多态
下面这种实现的方向,才是以后多态实现的常用手段。一般是通过返回值实现多态
类:
public class Animal {
public String name;
public int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println(name+"正在吃……");
}
}
class Dog extends Animal {
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(name+"正在吃狗粮……");
}
}
class Cat extends Animal {
public Cat(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(name+"正在吃鱼翅……");
}
}
main函数:
import java.util.Scanner;
public class Test {
public static void func1(Animal animal) {
animal.eat();
}
public static Animal func(int q) {
Scanner in = new Scanner(System.in);
if(q == 1) {
System.out.print("输入名字:");
String s = in.nextLine();
System.out.print("输入年龄:");
int b = in.nextInt();
return new Dog(s,b);
}else {
System.out.print("输入名字:");
String s = in.nextLine();
System.out.print("输入年龄:");
int b = in.nextInt();
return new Cat(s,b);
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int q = 0;
do {
System.out.println("请输入你的身份:1(dog)/2(cat)");
q = in.nextInt();
Animal animal = func(q);
animal.eat();
}while (q == 1 || q == 2);
}
}
运行结果:
本节的多态知识就介绍到这里了,快去按照代码练习一下吧。