1、多态
多态就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同 的状 态。(同一件事,发生在不同对象上就会产生不同的结果)
1.1 多态实现条件
在java中要实现多态,必须要满足如下几个条件:
1. 必须在继承体系下(在继承关系上,一般是向上转型)
常见的可以发生向上转型的3个时机:
1、直接赋值
2、方法的参数,传递的时候向上转型
3、返回值:向上转型。
2. 子类必须要对父类中的方法进行重写
如果在继承关系上,满足以下三点:
1、方法的返回值一样。
2、方法名一样
3、方法的参数列表一样。
那么就说这两个方法之间构成重写的关系。
父类在进行方法调用时,调用到了子类中被重写的方法。
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);
}
@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 {
// 编译器在编译代码时,并不知道要调用Dog 还是 Cat 中eat的方法
// 等程序运行起来后,形参a引用的具体对象确定后,才知道调用那个方法
// 注意:此处的形参类型必须时父类类型才可以
public static void eat(Animal a){
a.eat();
}
public static void main(String[] args) {
Cat cat = new Cat("元宝",2);
Dog dog = new Dog("小七", 1);
eat(cat);
eat(dog);
}
}
//元宝吃鱼~~~
//元宝正在睡觉
//小七吃骨头~~~
//小七正在睡觉
上图代码,下图讲解:
1、运行测试类,在测试类中编写一个eat方法,该方法中传递的参数是animal类的a引用指向的对象。,在该方法里,通过a引用(a可以理解为父类对象的引用)调用其对应的方法。(此时由于不知道a引用指向的是那个对象,所以不能确定我们要调用哪个子类的方法)
2、开始运行main方法,启动main线程,实例化cat类的cat对象和dog类的dog对象,通过这两个子类的cat和dog对象,传递到eat方法中的a引用中,通过不同的子类对象来调用各自继承类中重写的eat()方法,从而实现不同对象在继承体系中调用由父类的同一个方法而重写的多个子类方法,而导致实现效果大大不同,展现出多态。
如上图所示,我们把这个过程叫做动态绑定。
1.2 重写
重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。其好处在于子类可以根据需要,定义特定于自己的行为。 (即子类能够根据需要实现父类的方法)
Q: 一般重写的方法要注解@override,如果不写这个注解,方法能否重写成功?既然能,为啥还要写这个注解?
A:其主要目的是为了检查语法(在语法中有很多机制,是方便让编译器对我们的代码进行检查的)
【方法重写的规则】:
- 最基本的方法的返回值,参数列表,方法名必须一样。
- 被重写的方法的访问权限修饰符子类要大于等于父类。
- 被private修饰的方法不能够重写。
- 被static修饰的方法不能够重写。
- 被final修饰的方法不能够重写。
- 构造方法是不能被重写的。
方法的重写快捷键:alt + insert,结果显示如下图:
【重写和重载的区别】:
方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现
1.3动态绑定和静态绑定
静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。下图代码便于理解。
动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体 调用那个类的方法。
(经典类型)
1.4 向上转移和向下转型
1.4.1 向上转型
实际就是创建一个子类对象,将其当成父类对象来使用。
语法格式:
//animal是父类类型,但可以引用一个子类对象,因为是从小范围向大范围的转换。
向上转型的优点:让代码实现更简单灵活。
向上转型的缺陷:不能调用到子类特有的方法。
1.4.2 向下转型
将父类引用再还原为子类对象即可,即向下转换(将动物类(其实是狗类)引用还原为猫类型对象)
向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入 了 instanceof (主要是为了检查向下转型后的类是否对应转型之前的类,一定程度上提高了代码运行的正确性),如果该表达式为true,则可以安全转换。
public class TestAnimal {
public static void main(String[] args) {
Cat cat = new Cat("元宝",2);
Dog dog = new Dog("小七", 1);
// 向上转型
Animal animal = cat;
animal.eat();
animal = dog;
animal.eat();
if(animal instanceof Cat){
cat = (Cat)animal;
cat.mew();
}
if(animal instanceof Dog){
dog = (Dog)animal;
dog.bark();
}
}
}
Animal instanceof cat;判断父类能不能强行转换为子类,是否满足向下转换的条件
1.5 多态的优缺点
【使用多态的好处】:
1. 能够降低代码的 "圈复杂度", 避免使用大量的 if - else
2. 可扩展能力更强
多态缺陷:代码的运行效率降低。
1. 属性没有多态性 当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性
2. 构造方法没有多态性
1.6 避免在构造方法中调用重写的方法
上图简单讲解:在测试类的main方法里面创建d对象,在创建子类d之前完成父类b的构造,但是在d类构造里面由于动态绑定,直接回到了子类里面重写的方法,这样既没有没完成父类构造,也没有完成子类中number的初始化。
结论: "用尽量简单的方式使对象进入可工作状态", 尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题.
ps:今天的内容就到这里了,还请一键三连哦。