JavaSE语法之九:多态
一、多态的概念
多态:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同 的状态。
例如:
① 彩色打印机和黑白打印机都有打印的行为,而打印效果却有彩色和黑白两种不同的效果;
② 猫和狗都是吃饭的行为,猫是吃猫粮,狗是吃狗粮。
总的来说:**同一件事情,发生在不同对象身上,就会产生不同的结果。**这种思想,就叫做“多态”。
对于代码来说,当 父类引用 引用的对象不一样的时候,表现出的行为是不一样的,这就是多态!
二、多态的实现条件
在Java中要实现多态,就必须要满足如下几个条件,缺一不可(即向上转型、继承、重写):
- 必须在继承体系下;
- 子类必须要对父类中方法进行重写;
- 通过父类的引用调用重写的方法。
多态的前提其实就是动态绑定的前提。
多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。
三、向上转型和向下转型
1. 向上转型
【向上转型的几种方式】
第一种:
class Animal{}
Class Dog extends Animal{}
Class test{
public static void main(String[] args) {
//向上转型
Animal animal1 = new Dog();
}
}
第二种:
class Animal{}
Class Dog extends Animal{}
Class test{
//定义方法
public static void func(Animal animal) {}
public static void main(String[] args) {
//向上转型
Dog dog = new Dog();
func(dog);
}
}
第三种:
class Animal{}
Class Dog extends Animal{}
Class test{
//定义方法:返回值设置成向上转型
//返回值是annimal用dog接收
public static Animal func() {
return new Dog();
}
public static void main(String[] args) {
}
}
动态绑定:
① 向上转型
② 子类重写父类方法
③ 通过父类引用,调用这个父类和子类重写的方法!
编译的时候,调用的是父类的成员方法,但当运行的时候,调用的是子类重写的方法。
静态绑定:
在编译的时候,就已经确定去用哪个方法了。
动态绑定是发生多态的基础。
【总结】
- 当发生向上转向之后,此时通过父类的引用,只能访问问父类自己的成员,不能访问到子类特有的成员。
- 向上转型的优点: 让代码实现更简单,更灵活;
- 向下转型的缺陷: 不能调用到子类的特有方法,只能调用父类的方法。
2. 向下转型
向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛出异常。
Java中为了提高向下转型的安全型,引入了instanceof,如果该表达式为true,则可以安全转换。
//假设前面只有Dog和Cat类
public static void main(String[] args) {
Animal animal1 = new Dog();
//向下转型
Dog dog = (Dog)animal1;
dog.name = "狗蛋";
dog.wangwang();
//可以理解为:animal1这个引用 是不是引用的Bird对象,结果是假
if (animal1 instanceof Bird) {
Bird bird = (Bird) animal1;
bird.fly();
}
}
四、重写
重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程 进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
重写的好处在于: 子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
【重写注意的点】
- private修饰的方法不能重写;
- static修饰的方法不能重写;
- 子类的访问修饰权限要>=父类的权限;
private < 默认 < protected < public - 被final修饰的方法是不能被重写的,此时这个方法被称为密封。
【格式】
①方法名相同;
②参数列表相同;
③返回值相同。
package ClassesAndObkects;
/**
* @Author : tipper
* @Description:
*/
class Animal{
public String name;
public int age;
public void eat() {
System.out.println(name + "正在吃饭!");
}
}
class Dog extends Animal{
public void wangwang (){
System.out.println(name + "汪汪叫!");
}
@Override
public void eat() {
System.out.println(name + "吃狗粮");
}
}
public class Test4 {
public static void main(String[] args) {
//向上转型
Animal animal1 = new Dog();
animal1.name = "坦克";
animal1.eat(); //调用了子类的方法
}
}
【重写和重载的区别】(面试题)
五、多态的优缺点
假设有如下代码:
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("画圆!");
}
}
//再加一个flower
class Flower extends Shape {
@Override
public void draw() {
System.out.println("?");
}
}
public class Test3 {
//多态
public static void drawMap(Shape shape) {
shape.draw();
}
public static void main(String[] args) {
Rect rect = new Rect();
Cycle cycle = new Cycle();
drawMap(rect);
drawMap(cycle);
drawMap(new Flower());
}
}
//输出结果
画矩形!
画圆!
?
【使用多态的好处】
-
能够降低代码的“圈复杂度”,避免使用大量的 if - else;
什么是“圈复杂度”?
圈复杂度是一种描述一段代码复杂程度的方式,一段代码如果平铺直叙, 那么就比较简单容易理解。 而如果有很多的条件分支或者循环语句,就认为理解起来更复杂。
因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数,这个个数就称为 “圈复杂度”。
如果一个方法的圈复杂度太高, 就需要考虑重构。
不同公司对于代码的圈复杂度的规范不一样, 一般不会超过 10。例如:我们现在打印多个形状,且不基于多态,实现代码如下:
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 drawShap() {
//创建Shape对象的数组
Shape[] shapes = {new Cycle(), new Rext(), new Cycle()};
for(Shape shape : shapes) {
shape.draw();
}
}
- 可扩展能力更强。
如果要新增一种新的形状,使用多态的方式代码改动成本也比较低,如上述代码的flower。
【多态缺陷】
六、避免在构造方法中调用重写的方法
理解,自己不要这样写。
class B {
public B() {
// do nothing
func();
}
public void func() {
System.out.println("B.func()");
}
}
class D extends B {
private int num = 1;
public D() {
super();
}
@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
此时为0是因为没走完,父类的都没走完。
走的时候,先父类的实例,父类的构造,再子类的实例,子类的构造,此时父类都没有运行,所以结果为默认的0.
此时执行的是子类的。
注意:
当在父类的构造方法当中去调用父类和子类重写的方法的时候,此时会调用子类的。
- 构造D对象的同时,会调用B的构造方法;
- B的构造方法中调用了func方法,此时会触发动态绑定,会调用到D中的func;
- 此时D对象自身还没有构造,此时num处在为初始化的状态,值为0。如果具备多态性,num的值应该是1;
- 所以在构造函数内,尽量避免使用实例方法,除了final和private方法。
结论: 用尽量简单的方式使对象进入可工作状态,尽量不要在构造器中调用方法(如果这个方法被子类重写,就会触发动态绑定,但是此时子类对象还没构造完成),可能会出现一些隐藏但是又及难发现的问题。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!