多态
多态的概念:通俗来说,就是多种形态,具体点就是去完成某个⾏为,当不同的对象去完成时会产⽣出不同的形态。说直白一些就是,同⼀件事情,发⽣在不同对象⾝上,就会产⽣不同的结果;先别想代码,想象一个遥控器上的"播放"按钮:
按下去,如果是DVD机,他就开始读光盘
如果是MP3播放器,他就开始放音乐
如果是手机上的视频APP,他就开始播放视频
同一个按钮"播放",作用在不同对象时,产生不同的行为,这就叫多态
多态的实现条件
1.必须在继承体系下
2.⼦类必须要对⽗类中⽅法进⾏重写
3.通过⽗类的引⽤调⽤重写的⽅法
多态体现:在代码运⾏时,当传递不同类对象时,会调⽤对应类中的⽅法
...
java
class Animal {
void makeSound () {
System.out.println("动物发出声音");
}
}
S
class Cat extends Animal{
@OverrideS
void makeSound() {
System.out.println("喵喵喵");
}
}
class Dog extends Animal{
@Override
void makeSound() {
System.out.println("汪汪汪");
}
}
class Test {
public static void main(String[] args) {
Animal a1 = new Dog(); //父类引用指向Dog对象
Animal a2 = new Cat(); //父类引用指向Cat对象
a1.makeSound();S
a2.makeSound();
}
}
//输出结果
汪汪汪
喵喵喵
...
a1的类型是Animal,但他实际指向的对象是Dog
a1.makeSound()编译的时候,编译器只知道Anima里面有makeSound(),所以语法通过
但运行时,Java虚拟机发现a1实际是Dog的makeSound(),输出"汪汪汪"
这就是一个方法调用表现出多种形态------多态
重写
重写也成为覆盖,重写是⼦类对⽗类⾮静态、⾮private修饰,⾮final修饰,⾮构造⽅法等的实现过程进⾏重新编写, 返回值和形参都不能改变。即外壳不变,核⼼重写!重写的好处在于⼦类可以根据需要,定义特定于⾃⼰的⾏为。 也就是说⼦类能够根据需要实现⽗类的⽅法
重写的规则
1.⼦类在重写⽗类的⽅法时,⼀般必须与⽗类⽅法原型⼀致: 返回值类型 ⽅法名 (参数列表) 要完全一致。(特殊情况:主要在返回值类型上,也就是协变类型,也就是子类重写方法时,可以把返回值类型改成父类返回值的子类,比如父类方法返回Animal,子类可以返回Dog,这时候方法原型的返回值类型不完全一致,但满足父子类的继承关系;除此之外方法名、参数列表还是得完全一致,这才是重写的核心)
2.被重写的⽅法返回值类型可以不同,但是必须是具有⽗⼦关系的(也就是协变类型)
3.访问权限不能⽐⽗类中被重写的⽅法的访问权限更低。例如:如果⽗类⽅法被public修饰,则⼦类中重写该⽅法就不能声明为 protected(public > protected > 包访问权限 > private)
4.⽗类被static、private修饰的⽅法、构造⽅法都不能被重写
5.重写的⽅法, 可以使⽤ @Override 注解来显式指定. 有了这个注解能帮我们进⾏⼀些合法性校验。(加了这个后,如果不小心把方法名、参数名之类的写错了编译器就会报错,此时我们就可以检查。说白了就是多了容错)
| 区别点 | 访问限定符 | 返回类型 | 参数列表 |
|---|---|---|---|
| 重写 | 可以降低限制 | 一定不能修改(除非可以构成父子关系) | 一定不能修改 |
| 重载 | 可以修改 | 可以修改 | 必须修改 |
即:⽅法重载是⼀个类的多态性表现,⽽⽅法重写是⼦类与⽗类的⼀种多态性表现
向上转型和向下转型
向上转型
向上转型:实际就是创建⼀个⼦类对象,将其当成⽗类对象来使⽤
语法格式:父类类型 对象名 = new 子类类型()
...
java
Animal a1 = new Cat();
//animal是⽗类类型,但可以引⽤⼀个⼦类对象,因为是从⼩范围向⼤范围的转换
...
使用场景
1.直接赋值
2.方法传参
3.方法返回
...
java
//直接赋值
Animal a1 = new Dog();
//参数传递
public static void func(Animal animal) {
}
//返回值
public static Animal func2() {
return new Dog();
}
...
向上转型的优点:让代码更简单灵活
向上转型的缺点:不能调用到子类特有的方法
向下转型
将⼀个⼦类对象经过向上转型之后当成⽗类⽅法使⽤,再⽆法调⽤⼦类的⽅法,但有时候可能需要调⽤⼦类特有的⽅法,此时:将⽗类引⽤再还原为⼦类对象即可,即向下转换
...
java
public class Animal {
void makeSound() {
System.out.println("动物发出叫声");
}
public class Dog extends Animal {
void bark() {
System.out.println("狗正在汪汪汪叫");
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Animal();
Dog dog = new Dog();
dog = (Dog) animal;
dog.bark();
//程序可以通过编程,但运⾏时抛出异常---因为:dog实际指向的是animal,animal中并没有bark()这个方法
}
}
...
向下转型⽤的⽐较少,⽽且不安全,万⼀转换失败,运⾏时就会抛异常。Java中为了提⾼向下转型的安全性,引⼊了 instanceof ,如果该表达式为true,则可以安全转换
使用多态的好处
1.能够降低代码的 "圈复杂度", 避免使⽤⼤量的 if - else
2.可扩展能⼒更强
避免在构造⽅法中调⽤重写的⽅法
不要在父类的构造方法中调用父类和子类同名的方法,如下:
...
java
public class B {
public B() {
func();
}
public void func() {
System.out.println("B.func()");
}
}
public class D extends B {
private int num = 1;
@Override
public void func() {
System.out.println("D.func()"+num);
}
}
public class Test {
public static void main(String[] args) {
D d = new D();
}
}
//执行结果
D.func()0
...
构造 D 对象的同时, 会调⽤ B 的构造⽅法;B 的构造⽅法中调⽤了 func ⽅法, 此时会触发动态绑定, 会调⽤到 D 中的 func;此时 D 对象⾃⾝还没有构造, 此时 num 处在未初始化的状态, 值为 0;所以在构造函数内,尽量避免使⽤实例⽅法,除了final和private⽅法
结论:"⽤尽量简单的⽅式使对象进⼊可⼯作状态", 尽量不要在构造器中调⽤⽅法(如果这个⽅法被⼦类重写, 就会触发动态绑定, 但是此时⼦类对象还没构造完成), 可能会出现⼀些隐藏的但是⼜极难发现的问题