面向对象编程有三大特性:封装、继承、多态。
一、多态
在JAVA当中,简单来说,就是父类引用指向子类对象,调用方法时会调用子类的实现,而不是父类的实现,这叫多态。
Parent instance = new Child();
instance.foo(); //==> Child foo()
定义
指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)
实现多态的技术
实现多态的技术称为:动态绑定(dynamic binding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。
多态的存在有三个前提
1. 继承:存在有继承关系的父类和子类
class Cat extends Animal
2. 重写:子类要重写父类的某些方法
eat() sleep()
3. 向上转型:父类引用指向子类对象
Animal am = new Cat();
举例分析
首先我们定义两个类,一个父类Animal,一个子类Cat。
// 父类 Animal
class Animal {
int num = 10;
static int age = 20;
public void eat() {
System.out.println("动物吃饭");
}
public static void sleep() {
System.out.println("动物在睡觉");
}
public void run(){
System.out.println("动物在奔跑");
}
}
// 子类 Cat
class Cat extends Animal {
int num = 80;
static int age = 90;
String name = "tomCat"; // 子类特有的属性
public void eat() {
System.out.println("猫吃饭");
}
public static void sleep() {
System.out.println("猫在睡觉");
}
public void catchMouse() { // 子类特有的方法
System.out.println("猫在抓老鼠");
}
}
然后进行测试:
// 测试类 Demo_Test1
class Demo_Test1 {
public static void main(String[] args) {
Animal am = new Cat();
am.eat();
am.sleep();
am.run();
//am.catchMouse(); // 子类特有的方法.这里先注释掉,待会说明
//System.out.println(am.name); // 子类特有的属性.这里先注释掉,待会说明
System.out.println(am.num);
System.out.println(am.age);
}
}
从这三段代码,我们能了解到什么?
首先,以上的三段代码充分体现了多态的三个前提,即:
存在继承关系
Cat类继承了Animal类子类要重写父类的方法
子类重写(override)了父类的两个成员方法eat(),sleep()。其中eat()是非静态的,sleep()是静态的(static)。父类数据类型的引用指向子类对象(向上转型)
测试类Demo_Test1中 Animal am = new Cat();语句在堆内存中开辟了子类(Cat)的对象,并把栈内存中的父类(Animal)的引用指向了这个Cat对象。到此,满足了Java多态的的必要三个前提。
然后,我们来看看运行结果
猫吃饭
动物在睡觉
动物在奔跑
10
20
也就是说
- 子类Cat重写了父类Animal的非静态成员方法am.eat();的输出结果为:猫吃饭。
- 子类重写了父类(Animal)的静态成员方法am.sleep();的输出结果为:动物在睡觉
- 未被子类(Cat)重写的父类(Animal)方法am.run()输出结果为:动物在奔跑
- 非静态成员变量num的输出结果为:动物的数量
- 静态成员变量age的输出结果为:动物的年龄
简而言之,多态后调用与父类同名的成员变量/方法时,输出结果分两种:
- 只有对于
非静态的成员方法
,虽然编译是看父类,但是运行是看子类
- 而对于其余的(即
成员变量
和静态的成员方法
),编译和运行都是看父类
多态弊端
多态的弊端在于:
1、多态后不能使用子类特有的属性和方法。 -> 解决方法:执行强转语句Cat ct = (Cat)am;
在上方的测试类(Demo_Test)中,存在两行被注释了的代码:
am.catchMouse(); // 子类特有的方法
System.out.println(am.name); // 子类特有的属性
当我们尝试调用子类特有的方法catchMouse()和打印子类特有的成员属性String name = “tomCat”时,系统就会报错。
原因就是多态的弊端,即:多态后不能使用子类特有的成员属性和子类特有的成员方法, 因为调用method方法的时候首先回去找父类中有没有这个方法,结果父类中没有这个方法,所以就会报错。
2、多态后不能直接调用父类同名方法。 -> 解决方法: super关键字
上面的例子有体现的如,am不能调用父类的eat()输出“动物吃饭”。如果子类方法确实想访问父类中被隐藏的同名字段,可以用super关键字来访问它。
解决方法详解
如果在代码执行过程中还想使用Cat类中特有的属性String name和它特有的成员方法catchMouse()了怎么办呢?那我们就可以把这个父类引用指向了子类对象的家伙am再强制变回Cat类型。这样am就是Cat类型的引用了,指向的也是Cat对象了,自然也能使用Cat类的一切属性和一切的成员方法。
class Demo_Test {
public static void main(String[] args) {
Animal am = new Cat();
am.eat();
am.sleep();
am.run();
// am.catchMouse();
// System.out.println(am.name);
System.out.println(am.num);
System.out.println(am.age);
System.out.println("------------------------------");
Cat ct = (Cat)am; //注意这里
ct.eat();
ct.sleep();
ct.run();
ct.catchMouse();
}
}
运行结果:
猫吃饭
猫在睡觉
动物在奔跑
猫在抓老鼠
很明显,执行强转语句Cat ct = (Cat)am;
之后,ct就指向最开始在堆内存中创建的那个Cat类型的对象了。这就是多态的魅力吧,虽然它有缺点,但是它确实十分灵活,减少多余对象的创建,不用说为了使用子类的某个方法又去重新再堆内存中开辟一个新的子类对象。
多态优点
可替换性(substitutability)
。多态对已存在代码具有可替换性。例如,多态对圆Circle类工作,对其他任何圆形几何体,如圆环,也同样工作。可扩充性(extensibility)
。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。接口性(interface-ability)
。多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。如图8.3所示。图中超类Shape规定了两个实现多态的接口方法,computeArea()以及computeVolume()。子类,如Circle和Sphere为了实现多态,完善或者覆盖这两个接口方法。灵活性(flexibility)
。它在应用中体现了灵活多样的操作,提高了使用效率。简化性(simplicity)
。多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。
二、Overload 重载
一个方法名,参数不同
,这叫方法重载(Overload)。
void foo(String str);
void foo(int number);
三、Override 覆盖
父类与子类有同样的方法名和参数
,这叫方法覆盖(Override)。
class Parent {
void foo() {
System.out.println("Parent foo()");
}
}
class Child extends Parent {
void foo() {
System.out.println("Child foo()");
}
}
参考:
http://blog.csdn.net/Liveor_Die/article/details/77886732
https://www.zhihu.com/question/30082151/answer/46688599