通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生不同的状态。
一、实现多态的条件:
1、必须在继承体系下
2、子类必须对基类的方法进行重写
3、必须通过基类的引用去调用重写的方法
才可以实现多态的效果。
多态的效果:在程序运行时,根据基类引用实际引用对象的不同选择对应类中的方法进行调用。
二、代码演示
多态的体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。
public class Animal {
protected String name;
protected String gender;
protected int age;
public Animal(String name,String gender,int age){
this.name = name;
this.gender = gender;
this.age = age;
}
public void sleep(){
System.out.println("animal在睡觉");
}
public void eat(){
System.out.println("animal在吃饭");
}
public void bark(){
System.out.println("animal在叫");
}
}
public class Dog extends Animal{
protected String color;
public Dog(String name,String gender,int age,String color) {
super(name,gender,age);
this.color = color;
}
//重写了Animal中的方法
public void sleep(){
System.out.println(name + "睡觉呼呼呼");
}
public void eat(){
System.out.println(name + "吃骨头");
}
public void bark(){
System.out.println(name + "汪汪汪");
}
public void hobby(){
System.out.println(name + "玩球");
}
}
public class Cat extends Animal{
protected String temper;
public Cat(String name,String gender,int age,String temper) {
super(name,gender,age);
this.temper = temper;
}
//重写了Animal中的方法
public void sleep(){
System.out.println(name + "睡觉zzz");
}
public void eat(){
System.out.println(name + "吃小鱼");
}
public void bark(){
System.out.println(name + "喵喵喵");
}
public void pleaseMaster(){
System.out.println(name + "亲人");
}
}
public class TestAnimal {
//在编译器编译代码的时候,根本不知道animal具体指向哪个类的对象,因此也无法知道具体要调用哪个类的方法
//只有在方法调用的时候,需要传递实参,当方法执行起来的时候,animal指向的对象才具体,这时eat调用哪个类的方法才知道
public static void method(Animal animal){
animal.eat();
animal.sleep();
animal.bark();
}
public static void method1(Animal animal){
animal.eat();
animal.sleep();
animal.bark();
if(animal instanceof Dog){
Dog dog = (Dog)animal;
dog.hobby();
}
if(animal instanceof Cat){
Cat cat = (Cat)animal;
cat.pleaseMaster();
}
}
public static void main(String[] args) {
Dog dog = new Dog("小七","公",1,"黑白");
method(dog);
method1(dog);
System.out.println("========================================");
Cat cat = new Cat("元宝","母",1,"温顺");
method(cat);
method1(cat);
}
}
执行结果为:
注:如果没有报错不等于重写成功,如果要检测是否重写成功,可以在方法原型上加上
@Override。
三、重写(override)
1、重写的概念
重写:也成为覆盖,重写是子类对父类非静态,非private修饰,非final修饰,非构造方法等的实现过程进行重新编写,返回值和形参都不能改变,即外壳不变,核心重写。重写的好处是子类可以根据需要,定义特定于自己的行为,也就是说子类能够根据需要实现父类的方法。
2、重写的规则
1)子类在重写父类的方法时,一般必须与父类方法原型一致:返回值类型 方法名 (参数列表)要完全一致
例外:协变:被重写的方法返回值类型可以不同,返回值有要求:基类方法返回基类的引用,子类方法返回子类的引用。
2)被重写的方法返回值类型可以不同,但是必须是具有父子关系的
3)访问权限不能比父类中被重写的方法访问权限更低
4)父类被static、private修饰的方法,构造方法都不能被重写
① 构造方法不能被重写:构造方法特殊,构造方法作用在调用对象时,由编译器调用,将对象中的成员初始化完整,只有对象真正创建好之后,才可以用其调用其他的方法实现多态,而对象真正创建好之后,构造方法已经调用完了 ,不可能再在构造方法上实现多态。
②父类被private修饰的方法不能被重写:private修饰的方法无法在类外被调用,所以实现多态意义不大
③父类被static修饰的方法不能被重写:静态方法没有this引用,实例方法有this引用,最终通过this引用来找到要调用的方法,因此只有实例方法才可以重写,类方法不能被重写
注:方法重载是一个类的多态性的表现,方法重写是子类与父类的一种多态性的表现
四、静态多态和动态多态
静态多态:静态绑定:也被称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用哪个方法。在编译时,编译器会根据用户传递的实参选择合适的方法进行调用,如果没有参数完全匹配的,则发生隐式类型转换,如果转换成功则调用,否则报错。典型代表函数重载
动态多态:动态绑定:也被称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用哪个类的方法。在代码编译时,编译器无法知道基类的引用到底引用哪个类的对象,因此编译时无法确定要调用哪个类的方法,只有等到程序运行起来之后,才知道应该调用哪个类的方法。重写方法实现多态
五、向上转型和向下转型
1、向上转型
向上转型实际上就是创建一个子类对象,将其当成父类对象来使用。基类的引用去接收子类的对象。
①语法格式:父类类型 对象名 = new 子类类型()
②优点:让代码实现更简单灵活
缺点:不能调用到子类特有的方法
2、向下转型
向下转型是要让子类的引用去应用基类的引用。
注:向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常,Java中为了提高向下转型的安全性,引入了instanceof,如果该表达式为true,则可以安全转换。
public static void method1(Animal animal){
animal.eat();
animal.sleep();
animal.bark();
if(animal instanceof Dog){
Dog dog = (Dog)animal;
dog.hobby();
}
if(animal instanceof Cat){
Cat cat = (Cat)animal;
cat.pleaseMaster();
}
}
六、多态的优缺点
1、使用多态的好处
1)能够降低代码的“圈复杂度”,避免使用大量的if-else
圈复杂度:我们可以简单粗暴的理解为计算一段代码中条件语句和循环语句出现的个数,这个个数就称为圈复杂度。如果一个方法的圈复杂度太高,就需要考虑重构。
2)可扩展能力更强
如果要新增一种新的形状,使用多态的方法代码改动成本也比较低。
2、使用多态的缺点
代码运行效率降低
①属性没有多态性
当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员的属性。
②构造方法没有多态性