目录
1、多态的概念
2、重写
3、向上转型
4、向下转型
5、多态的优缺点
理解
不同的对象,面对同一种的行为的状态不一样
内容
条件:
- 必须在继承体系下
- 子类必须要对父类中方法进行重写
- 通过父类的引用调用重写的方法多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。
主要是运用到
方法的传参过程中,也可以发生向上转型
代码
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); } // 重写的Cat类的eat @Override public void eat(){ System.out.println(name+"吃鱼~~~"); } } public class Dog extends Animal { public Dog(String name, int age){ super(name, age); } // 重写的Dog类的eat @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); } } 运行结果: 元宝吃鱼~~~ 元宝正在睡觉 小七吃骨头~~~ 小七正在睡觉
理解
对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容, 并且添加或者改动新的内容。
例如:若干年前的手机,只能打电话,发短信,来电显示只能显示号码,而今天的手机在来电显示的时候,不仅仅 可以显示号码,还可以显示头像,地区等。在这个过程当中,我们不应该在原来老的类上进行修改,因为原来的类,可能还在有用户使用,正确做法是:新建一个新手机的类,对来电显示这个方法重写就好了,这样就达到了我们当今的需求了。
内容
注意:
- 方法名称相同
- 返回值相同
- 参数列表相同(数据类型、个数、顺序)
子类的访问权限要 大于等于 父类的权限
private(修饰后不可重写) < default < protected < public
- 1、被 private修饰的方法,不可以被重写
- 2、被final修饰的方法 叫做密封方法。不能被重写
- 3、 被static修饰,也不可以被重写
【重写和重载的区别】
**静态绑定(早绑定)**:在编译时,根据用户所传递的参数,能够确定具体调用哪个方法。典型代表函数 重载
区别点 重写(override) 重载(override) 参数列表 一定不能修改 必须修改 返回类型 一定不能修改【除非可以构成父子类关系】 可以修改 访问限定符 一定不能做更严格的限制(可以降低限制) 可以修改
代码
class Animal { public String name; public int age; public void eat() { System.out.println(name + "吃饭!"); } } class Dog extends Animal{ public boolean silly; @Override public void eat() { System.out.println(name+"正在吃狗粮!");//一定发生在继承上 } }
理解
可以用父类承接任何一种子类
Dog dog = new Dog("hello",10,false); //现在dog是Dog类 Animal animal = dog;// dog被转换成Animal了
内容
使用场景
- 直接赋值
- 方法传参
- 方法返回
代码
public class Test { //2. 方法的传参过程当中 也可以发生向上转型 public static void function2(Animal animal) { animal.eat(); } //3. 应该返回Animal类,但转型成返回Cat类 public static Animal function3() { return new Cat(); } public static void main2(String[] args) { Dog dog = new Dog("hello",10,false); //animal这个引用 指向了Dog对象 Animal animal = dog; //animal.silly = false; 不能访问 原因是 没有这个属性。 此时的animal 只能调用自己特有的属性。 animal.eat();// 此时调用 的是Dog类的eat,动态绑定 //1. 直接赋值 Animal animal2 = new Dog("hello",10,false); Animal animal3 = new Cat(); } }
理解
内容
设置一个动物,他拥有猫的习性。
但是我们通过(Cat)向下转型他,强制这个动物为猫
如果正好有她的习性方法(抓老鼠)可以正确运行
如果没有,则不行
代码
public static void main(String[] args) { Animal animal2 = new Dog("hello",10,false); //animal2.catchMouse(); // animal2 是不是引用了 Cat这个对象 if(animal2 instanceof Cat) { Cat cat = (Cat) animal2; cat.catchMouse(); } } public static void main4(String[] args) { Animal animal2 = new Cat("hello",10); //animal2.catchMouse(); Cat cat = (Cat)animal2; cat.catchMouse(); }
理解
优点:
- 能够降低代码的 "圈复杂度", 避免使用大量的 if - else
- 可扩展能力更强
缺点:
- 代码的运行效率降低。
- 属性没有多态性 (当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性 )子类的属性在多态发生的时候来不及被构造
- 构造方法没有多态性(避免在构造方法中调用重写的方法)
内容
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(); } } }
会增加“圈复杂度”
什么叫 "圈复杂度" ?
圈复杂度是一种描述一段代码复杂程度的方式. 一段代码如果平铺直叙, 那么就比较简单容易理解. 而如果有很多的条件分支或者循环语句, 就认为理解起来更复杂.
因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 "圈复杂度". 如果一个方法的圈复杂度太高, 就需要考虑重构
2、但使用多态,就很简便
public static void drawShapes() { // 我们创建了一个 Shape 对象的数组. Shape[] shapes = {new Cycle(), new Rect(), new Cycle(), new Rect(), new Flower()}; for (Shape shape : shapes) { shape.draw(); } }
3、可扩展能力更强突出在,想要输出其他的图形,直接继承多态就好
class Triangle extends Shape { @Override public void draw() { System.out.println("△"); } }
4、避免在构造方法中调用重写的方法
class B { public B() { // do nothing func(); } public void func() { System.out.println("B.func()"); } } class D extends B { private int num = 1; @Override public void func() { System.out.println("D.func() " + num); } } public class Test { public static void main(String[] args) { D d = new D(); } } // 执行结果 D.func() 0
我们创建两个类, B 是父类, D 是子类. D 中重写 func 方法. 并且在 B 的构造方法中调用 func过程:
- 构造 D 对象的同时, 会调用 B 的构造方法.
- B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func
- 此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0. 如果具备多态性,num的值应该是1.
- 所以在构造函数内,尽量避免使用实例方法,除了final和private方法。
代码
class Shape { //属性.... public void draw() { System.out.println("画图形!"); } } class Rect extends Shape{ @Override public void draw() { System.out.println("♦"); } } class Cycle extends Shape{ @Override public void draw() { System.out.println("●"); } } class Flower extends Shape{ @Override public void draw() { System.out.println("❀"); } }