多态
多态的概念
通俗的讲,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
多态实现条件
在JAVA中实现多态,必须满足如下几点条件
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+"吃饭");
}
class Cat extends Animl{
public Cat(String name,int age){
super(name,age):
}
public void eat(){
System.out.println(name+"吃鱼肉");
}
class Dogextends Animl{
public Dog(String name,int age){
super(name,age):
}
public void eat(){
System.out.println(name+"吃骨头");
}
}
public class Test{
//编译器在编译代码时并不知道调用Dog还是Cat中的eat方法
public static void eat(animal a){
a.eat();
}
public static void main(String[] args){
Dog dog=new Dog("wangwang",1);
dog.eat();
}
}
重写所满足的条件:
1.方法名相同
2.参数列表相同【个数,顺序,类型】
3.返回值类型相同
重写
重写是子类对父类非静态,非private修饰,非final修饰,非构造方法等的实现过程进行重新编写。
【方法重写的规则】
子类在重写父类方法时,一般必须与父类方法原型一致:返回值类型 方法名{参数列表}要完全一致。
被重写的方法返回值类型可以不同,但必须是具有父子关系
访问权限不能比父类中被重写的方法的访问权限更低。
父类被static,private修饰的方法,构造方法都不能被重写
那些情况下不能进行重写
1.被private修饰的方法
2.被static修饰的方法
3.子类的访问修饰限定符一定要大于等于父类的访问限定修饰符
4.被final修饰的方法不能被重写
5.构造方法不能被重写
【重载和重写的区别】
区别点 | 重写 | 重载 |
---|---|---|
参数列表 | 一定不能修改 | 必须修改 |
返回类型 | 一定不能修改 | 可以修改 |
访问限定符 | 一定不能做更严格的限制 | 可以修改 |
即:方法重载是一个的多态性表现,而方法重写是子类与父类的一种多态机制
向上转型与向下转型
向上转型
【使用场景】
发生的时机
1.直接赋值: Animal animal=new Dog()
2.传参:
public static void testFunc(Animal animal){
}
public static void main(String[] args){
Dog dog=new Dog(name:"wangwang",age:1);
testFunc(dog);
}
3.返回值
public static Animal testFunc2(){
return new Dog();
}
向上转型:实际就是创建一个子类对象,将其当成父类对象来使用
语法格式:父类名称 对象名=new 子类名称
Animalanimal=new Cat(“元宝”,2):
animal是父类类型,但可以引用一个子类对象,因为是从小范围向大范围的转换。
Dog dog=new Dog("wangwang",1);
Animal animal=dog;
等价于
Aniaml animal=new Dog(name:"wangwang",age:1);
向上转型的优点:让代码实现更简单灵活
向上转型的缺陷:不能调用到子类特有的方法
向下转型
将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类方法,但有时候可能需要调用子类特有的方法,此时:将父类引用还原为子类对象即可。
public class TestAnimal{
public static void main(String[] args){
Cat cat=new Cat("元宝",2);
Dog dog=new Dog("小七");
//向上转型
Animal animal=cat;
animal.eat();
animal=dog;
animal.eat();
//编译失败,编译时将编译器将animal当成Animal对象处理
//向上转型
//程序可以通过编程,无法正常还原
cat=(Cat)animal;
cat.mew();
//animal本来指向的就是狗,因此将animal还原为狗也安全
dog=(Dog)animal;
dog.bark();
}
}
向下转型比较少,而且不安全,万一转型失败,运行就会抛出异常,Java中为了提高向下转型的安全性,引入了instanceof,如果该表达式为true,则可以安全转移。
public class TestAnimal{
public static void main(String[] args){
cat cat=new Cat("元宝",2);
Dog dog=new Dog("小气",3);
//向上转型
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();
}
}
}
运行时绑定/动态绑定
1.就是发生了重写
2.是通过父类引用来调用这个重写的方法
静态绑定:在程序编译的时候通过参数类型的个数,编译器可以自动推导出来你调用的是哪个参数,此时叫做静态绑定。典型代表函数重载。
动态绑定:也称为后期绑定,即在编译时,不能确定方法的行为,需要等到程序运行时,才能够具体调用哪个类的方法。
多态实现条件
1.必须在继承体系下
2.子类必须要对父类中方法进行重写
3.通过父类的引用调用重写的方法
多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法
多态的优缺点
【使用多态的好处】
1.能够降低代码的”圈复杂度“,避免使用大量的if-else
什么叫”圈复杂度“?
圈复杂度是一种描述一段代码复杂程度的方式,一段代码如果平铺直叙,那么就比较简单,而如果有很多的条件分支或者循环语句,就认为理解起来复杂。
例如我们现在需要打印的不是一个形状,而是多个形状,如果不基于多态,实现代码如下:
public static void drawShapes(){
Rect rect=new Rect();
Cycle cycle=new Cycle();
Flower flower=new Flower();
String[] shapes ={"cycle","rect","cycle","rect","flower");
for(String shape :shapes){
if(shape.equals("cycle")){
cycle.draw();}
else if(shape.equals("rect")){
rect.draw();}
else if(shape.equals("flower"){
flower.draw();
}
}
}
如果使用使用多态,则不必写这么多得if else分支语句
public static void drawShapes(){
/我们创建一个Shape对象的数组
Shape[] shapes ={new Cycle(),new Rect(),new Cycle(),new Rect(),new Flower()}
for (Shape shape:shapes){
shape.draw();
}
}
2.拓展能力增强
如果要新增一种新的状态,使用多态的方式代码改动成本也比较低。
class Triangle extends Shape{
public void draw(){
System.out.println(" ");
}
}
对于类的调用者来说,只要创建一个新类的实例就可以了,改动成本比较低,而对于不用多态的情况来说。
多态的缺陷:代码的运行效率降低
1.属性没有多态性
当父类和子类都有同名属性的时候,通过父类的引用,只能引用父类自己的成员属性。
避免在构造方法中调用重写方法
class B{
public B(){
//do nothing
func();
}
public void func(){
System.out.println("B.func()");
}
}
class D extends B{
public int num=1;
public void func(){
System.out.println("D.func()"+num);
}
}
public class Test{
public static void main(String[] args){
D d=new D();
}
}
- 构造D对象的同时,会调用B的构造方法。
- B的构造方法中调用func方法,此时会触发动态绑定,会调用到D中的func
- 此时D对象自身还没有构造,此时num处在未初始化的状态,值为0,如果具备多态性,num的值应为1