一、多态的概念以及分类
概念
简单来说就是多种形态,也就是说同一件事情,发生在不同对象的身上,就会产生不同的结果。比如,同样是吃东西,狗吃骨头,猫吃鱼,蓝胖子爱吃铜锣烧。
分类
静态绑定
静态绑定:也称为前期绑定(早绑定),在编译时,根据用户所传递实参类型就确定了具体调用哪个方法。eg.方法重载
动态绑定
动态绑定:也称为后期绑定(晚绑定),在编译时,不能确定方法的行为,需要等到程序运行时,才能确定具体调用哪个类的方法。
二、多态实现条件
以下条件缺一不可:
1、必须在继承体系下
2、子类必须对父类想要实现多态的方法进行重写
3、通过父类的引用调用重写的方法
多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法
public class Animal {
String name;
int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(name + "吃饭");
}
}
public class Cat extends Animal{
public Cat(String name, int age) {
super(name, age);
}
// 快捷键ctrl+o能够重写方法
@Override
public void eat() {
System.out.println(name + "吃鱼");
}
}
public class Dog extends Animal {
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(name + "吃骨头");
}
}
public class TestAnimal {
// 编译时没有任何语法问题,且编译时,形参a不知道将来会指向哪个类的对象;
// a.eat()不知道要调用哪个类的对象;
// 程序运行之后形参a引用的具体对象确定后,才知道调用哪个方法
// 形参类型必须是父类类型
// 不同对象执行产生的结果是不同的
public static void eat(Animal a){
a.eat();
}
public static void main(String[] args) {
Cat cat = new Cat("蓝胖子",1);
Dog dog = new Dog("来福",3);
eat(cat);
eat(dog);
}
}
运行结果
蓝胖子吃鱼
来福吃骨头
三、重写
重写:也称为覆盖。是子类对父类非静态、非 private 修饰、非 final 修饰、非构造方法等的实现过程进行重新编写。
返回值和形参都不能改变(外壳不变,核心重写)
子类可以根据需要,定义自己特定的行为
重写的规则
1、一般必须与父类方法原型一致:修饰符 返回值类型 方法名(参数列表)要完全一致【参数列表不同是重载】
2、JDK7 以后,被重写的方法返回值类型可以不同,但是必须是具有父子关系的
3、访问权限不能比父类中被重写的方法的访问权限更低
4、父类被 static 、private 修饰的方法、构造方法都不能被重写
5、子类和父类在一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法
6、子类和父类不在一个包中,那么子类只能够重写父类的声明为 public 和protected 的非 final 方法
7、重写的方法,可以使用@Override
注解来显示指定
重写与重载的区别
重载(overload) | 重写(override) |
---|---|
参数列表必须修改 | 参数列表一定不能修改 |
返回值类型可以修改 | 返回值类型一定不能修改(父类返回父类的引用,子类返回子类的引用) |
访问限定符可以修改 | 访问限定符可以降低限制 |
异常可以修改 | 异常可以减少或删除,一定不能抛出新的或者更广的异常 |
方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现
四、向上转型和向下转型
向上转型
向上转型实际就是创建一个子类对象,将其当成父类对象来使用
格式:父类类型 对象名 = new 子类类型();
例:Animal animal = new Cat("蓝胖子",1);
猫和狗都是动物,因此将子类对象转换为父类引用是合理的,大范围可以包含小范围,所以向上转型是安全的;反之,动物不一定是狗,也可以包含猫,企鹅,狐狸等。
优点:让代码更简单灵活
缺点:不能调用到子类特有的方法
【使用场景】
1、方法传参:形参作为父类型的引用,可以接受任意子类的对象
public static void eat(Animal a){
a.eat();
}
2、直接赋值:子类对象赋值给父类对象
Animal animal = new Cat("蓝胖子",1);
3、作为返回值:返回任意子类对象
public static Animal buyAnimal(String var) {
if ("狗" == var) {
return new Dog("狗狗", 2);
} else if ("猫" == var) {
return new Cat("猫猫", 1);
}else{
return null;
}
}
向下转型
向下转型:将父类引用再还原为子类对象
不安全,一旦转换失败,运行时会抛出异常,所以用的不多。
public class TestAnimal {
public static void main(String[] args) {
// 创建一个对象cat,cat是animal,调用Cat中eat方法
Cat cat = new Cat("蓝胖子",1);
Animal animal = cat;
animal.eat();
// 反之,如果animal不属于Cat,但二者有对应关系,则可以强转为cat,继而调用mew()
if(cat instanceof Cat){
cat = (Cat)animal;
cat.mew();
}
// 同上
Dog dog = new Dog("来福",3);
animal = dog;
animal.eat();
if(dog instanceof Dog){
dog = (Dog) animal;
dog.bark();
}
}
}
运行结果
蓝胖子吃鱼
蓝胖子喵喵~~
来福吃骨头
来福汪汪~~
五、多态的优缺点
优点:
1、能够降低代码的“圈复杂度”,避免使用大量的 if-else
[圈复杂度:循环出现的次数]
2、可扩展能力更强
缺点:代码运行效率低
六、避免在构造方法中调用重写方法
会导致子类对象没有构造完成。
public class Base {
protected int b = 10;
public Base() {
this.func();
}
public void func(){
System.out.println(b);
}
}
public class Derived extends Base{
public Derived(){
super();
b = 100;
}
@Override
public void func() {
System.out.println(b);
}
public static void main(String[] args) {
Base b = new Derived();
b.func();
}
}
运行结果
10
100