多态是同一个行为具有多个不同表现形式或形态的能力。
一、基本概念
多态就是同一个接口,使用不同的实例而执行不同操作,如图所示(来自菜鸟教程):
多态存在的三个必要条件:
- 继承
- 子类要对父类方法进行重写
- 通过父类引用指向子类对象,调用重写方法
Parent P = new Child();
优点:
- 消除类型之间的耦合关系
- 可替换性
- 可扩充性
- 接口性
- 灵活性
- 简化性
二、多态的示例
通过示例来理解多态:
//Animal.java
public class Animal {
String breed;
String name;
public Animal(String breed, String name) {
this.breed = breed;
this.name = name;
}
//父类的吃的方法
public void eat(){
System.out.println(name+"在吃饭ing");
}
}
//Dog.java
public class Dog extends Animal {
public Dog(String breed, String name) {
super(breed, name);
}
//对吃的方法进行了重写
public void eat(){
System.out.println(name+"在吃骨头ing");
}
}
//Cat.java
public class Cat extends Animal{
public Cat(String breed, String name) {
super(breed, name);
}
//对吃的方法进行了重写
public void eat(){
System.out.println(name+"在吃鱼ing");
}
}
//Test.java
public class Test {
public static void eat(Animal animal){
//编译器编译的时候并不知道要调用哪个类中的方法,该处的这种行为叫做多态
//只有当程序运行起来,编译器才能通过形参引用的具体对象,确定调用哪个类中的方法
//此处的形参必须是父类类型才行
animal.eat();
}
public static void main(String[] args) {
Cat cat = new Cat("英短","小灰");
Dog dog = new Dog("拉布拉多","旺财");
Animal animal = new Animal("动物","某动物");
eat(cat);
eat(dog);
eat(animal);
}
}
三、重写
重写(override): 也称为覆盖。重写是子类对父类非静态、非private
修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变,只改写其中的方法实现。重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
方法重写的规则:
- 子类在重写父类方法时,一般情况下必须与父类方法原型一致,即:修饰符 返回值类型 方法名(参数列表)。
- JDK7之后,重写的方法返回值可以不同,但必须具有父子关系。
- 子类重写的方法访问权限不能比父类中被重写的方法访问权限更低。例如:如果父类方法被
public
修饰,则子类中重写该方法就不能声明为protected
。 - 父类中被
static
、private
修饰的方法或者构造方法都不能被重写。 - 子类和父类在同一个包中,那么子类可以重写父类的所有方法,除了被
private
和final
修饰的方法。 - 子类和父类不在同一个包中,那么子类只能够重写父类的声明为
public
和protected
的非final
方法。 - 可以使用
@Override
注解来显式指定。有了这个注解能帮我们进行一些合法性校验。
示例:
//父类中的方法
public void eat(){
System.out.println(name+"在吃饭");
}
//子类中重写的方法
@Override
private void eat(){
System.out.println(name+"在吃骨头ing");
}
1、重载和重写的区别
区别 | 重载(Overload) | 重写(Override) |
---|---|---|
参数列表 | 必须修改 | 不能修改 |
返回类型 | 可以修改 | 不能修改 |
访问限定符 | 可以修改 | 不能做更严格的限定,但可以降低限定 |
异常 | 可以修改 | 可以减少或删除,一定不能抛出新的或者更广的异常 |
注: 方法重载是一个类的多态性表现;方法重写是子类与父类的一种多态性表现。
四、向上/下转型
1、向上转型
其实质就是创建一个子类对象,将其作为父类对象来使用。
语法格式:
父类类型 对象名 = new 子类类型()
示例:
Animal animal = new Dog("中华田园犬","阿黄"); //因为狗都是动物
animal
是父类类型,引用了一个子类对象,把子类对象当作父类对象来用。因为是从小范围到大范围的转换,所以向上转型是安全的。
使用的场景:
- 直接赋值
- 方法传参
- 方法返回
public class Test {
//方法传参:接受任意子类对象
public static void eat(Animal animal){
animal.eat();
}
//返回值:返回任意子类对象
public static Animal buyAnimal(String need){
if ("狗" == need)
return new Dog("英短","小灰");
else if ("猫" == need)
return new Cat("中华田园犬","旺财");
else
return null;
}
public static void main(String[] args) {
Animal cat = new Cat("狸花猫","小花"); //子类对象赋值给父类对象
Dog dog = new Dog("雪纳瑞","大白");
eat(cat);
eat(dog);
System.out.println("=======================");
Animal animal = buyAnimal("猫");
animal.eat();
System.out.println("=======================");
animal = buyAnimal("狗");
animal.eat();
}
}
向上转型缺点:不能调用到子类特有的方法
//
public class Cat extends Animal{
public Cat(String breed, String name) {
super(breed, name);
}
//Cat类特有的方法
public void printCat() {
System.out.println("Cat.toString");
}
}
Animal cat = new Cat("狸花猫","小花"); //子类对象赋值给父类对象
cat.printCat(); //该部分会报错
2、向下转型
将一个子类对象先经过向上转型当作父类方法使用,再无法调用子类方法,但有些时候可能又需要调用子类中的特有方法,此时就需要向下转换,将父类引用还原为子类对象。(向下转型应用较少,因为不安全)
用动物类来举例可能出现两种情况:
public static void main(String[] args) {
Cat cat = new Cat("英短","小灰");
Dog dog = new Dog("中华田园犬","旺财");
//向上转型
Animal animal = cat;
animal.eat();
animal = dog;
animal.eat();
cat = (Cat)animal; //可以通过编译,但是运行时会抛出异常
//animal原本也是dog所以安全可以转换
dog = (Dog)animal;
dog.printDog();
}
3、instanceof
关键字提高向下转型的安全性
为了提高向下转型的安全性,引入instanceof
关键字,如果表达式为true
,则可安全转换。
public static void main(String[] args) {
Cat cat = new Cat("英短","小灰");
Dog dog = new Dog("中华田园犬","旺财");
//向上转型
Animal animal = cat;
animal.eat();
animal = dog;
animal.eat();
if (animal instanceof Cat){
cat = (Cat)animal;
}
if (animal instanceof Dog) {
dog = (Dog) animal;
dog.printDog();
}
}