一、多态的定义:
面向对象程序设计的三大特性是封装、继承和多态
。
多态是同一个行为具有多个不同表现形式或形态的能力。
多态就是同一个接口,使用不同的实例而执行不同的操作。
用更通俗的话来概况多态,那就是具体问题具体分析
。
二、实现多态的必要条件
1、 首先我们来看一段基于继承实现多态
的代码:
public class Test {
public static void main(String[] args) {
util a = new util();
a.use();
keyboard b = new keyboard();
b.use();
util c = new mouse();
c.use();
util d = new computer(); //向上转型
d.use();
d.notUse();
mouse f = (mouse) c; //向下转型
f.use();
f.notUse();
f.click();
computer k = (computer) c; //错误的写法
k.use(); //因为c此时指向的是子类mouse
}
}
/** 父类 */
class util {
public void use() {
System.out.println("使用工具");
}
public void notUse() {
System.out.println("不使用工具");
}
}
/** 三个子类 */
class keyboard extends util{
public void use() {
System.out.println("使用键盘");
}
}
class mouse extends util{
public void use() {
System.out.println("使用鼠标");
}
public void click() {
System.out.println("点击");
}
}
class computer extends util{
public void use() {
System.out.println("使用电脑");
}
}
具体输出结果如下:
在这段代码中,我们有一个父类
,叫做util。
以及还有三个子类
,分别是keyboard,mouse,computer,他们都隶属于util这个大类。
而且,我们会发现他们都有一个相同的方法名
叫做use。
但现在如果要你在不修改父类的程序代码前提下,同时我们又需要在不同的情况下使用同一个方法名,那该怎么做呢?
如果我们直接删除某个子类的use函数会怎么样呢?结果就是直接调用了父类的use函数
并且输出了“使用工具”,这就是继承
,但也就达不到输出具体实例需要的效果了。
这个时候就需要多态性
,即不修改程序代码就可以改变程序运行时所绑定的具体代码,也可以让程序选择多个运行状态来达到不同的效果。
所以,我们可以得出实现多态的三个必要条件
1、继承
子类首先要继承
父类。
2、重写
为了实现具体实例的不同效果,子类需要对继承于父类的同一个方法进行重写
,比如代码中的每个子类对父类use方法的重写。
3、转型
转型又可以分为向上转型和向下转型
。
------向上转型 (具体请看代码中变量d的使用)
就是将子类对象转化为父类类型,也可以说父类引用指向子类对象
。即 父类类型 对象名称 = new 子类类型()
。
变量d重写了父类的use方法,但是仍旧能使用父类的另一个方法notUse,这大大提高了拓展性。
但同时也有缺点,我们不能再调用子类中的方法和属性了。
所以向上转型主要适用于不需要面向子类对象时,用父类的功能即可完成相应操作的情况。
------向下转型 (具体请看代码中变量f和k的使用)
就是将父类类型强制转化为子类类型。即我们先定义了一个父类对象指向了子类类型,然后将这个父类类型强制转化为子类类型。
于是向下转型就能够调用子类中的方法,比如变量f的click方法。同时也继承了父类的特点,如图中变量f继承了父类c的use和notUse方法。
所以向下转型主要适用于需要使用子类的功能的情况。
但前提这个子类必须是你这个父类引用时指向的那个子类对象
也就是说你原本是mouse,那你肯定不能变成computer啊。
否则就会出现无法强制转化类型的错误,即出现如下图所示的报错。
2、 我们再来看一段基于接口实现多态的代码:
/** 接口方法创建 */
interface Operation {
public void add();
public void delete();
}
/** 接口实现类 */
class InterfaceOfUtil implements Operation {
public void add() {
System.out.println("添加工具");
}
public void delete() {
System.out.println("删除工具");
}
}
class InterfaceOfMouse implements Operation {
public void add() {
System.out.println("添加鼠标");
}
public void delete() {
System.out.println("删除鼠标");
}
}
/** 测试类 */
public class Test {
public static void main(String[] args) {
Operation op1 = new InterfaceOfUtil();
Operation op2 = new InterfaceOfMouse();
op1.add();
op1.delete();
op2.add();
op2.delete();
}
}
具体输出结果如下:
1.
我们首先定义了一个接口
,叫做Operation,里面有两个抽象方法,分别是add、delete,用于添加操作和删除操作。
(注意接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法)
2.
我们通过两个接口实现类
: InterfaceOfUtil、InterfaceOfMouse,具体实现了接口中的抽象方法。
3.
然后我们通过测试类
: Test将两者的操作都使用了一遍。
在这个过程中,我们会发现util和mouse其实都属于可操作的operation类型,在一种类型下表现出了多种状态,比如util和mouse的名字状态就不一样,当然还可以是价格等其他属性等等,这也体现了多态性
。
三、接下来是根据经典实例略加修改的一段代码:
public class Test {
public static void main(String[] args) {
util father = new util();
util fatherFromSon = new mouse();
mouse son = new mouse();
temp1 t1 = new temp1();
temp2 t2 = new temp2();
father.use(son);
father.use(t1);
father.use(t2);
System.out.println("---------");
fatherFromSon.use(son);
fatherFromSon.use(t1);
fatherFromSon.use(t2);
System.out.println("---------");
son.use(son);
son.use(t1);
son.use(t2);
}
}
/** 父类 */
class util {
public void use(temp2 obj) {
System.out.println("使用工具和temp2");
}
public void use(util obj) {
System.out.println("使用工具和工具");
}
}
/** 子类 */
class mouse extends util{
public void use(mouse obj) {
System.out.println("使用鼠标和鼠标");
}
public void use(util obj) {
System.out.println("使用鼠标和工具");
}
}
class temp1 extends mouse {
}
class temp2 extends mouse {
}
具体输出结果如下:
这里我们有四个类,分别是util、mouse和temp1、temp2。
对于第三个操作,因为temp2有明确定义的方法,所以调用的是use(temp2 obj)这个方法。
最难理解的是其他几个操作。例如第四个操作(fatherFromSon.use(son)),fatherFromSon是向上转型的util类型,其use方法重写为son的use方法,按上面的知识点来说,当我们调用use(mouse obj)方法时,应该是输出“使用鼠标和鼠标”的,但结果却是“使用鼠标和工具”,这是为什么呢?
这里就会涉及到继承链中的优先级原则:1.this.use(obj)、2.super.use(obj)、3.this.use((super)obj)、4.super.use((super)obj)。
那么根据下面这张关系图,
1.
首先对于fatherFromSon,它是向上转型的util类型,而util类型中并没有use(mouse obj)这个方法。
2.
我们会接着去找util类型的super类,util类已经没有super了,那么就会继续判断下一个优先级。
3.
this.use((super)mouse),就等价于this.use(util),由于当前的use方法被重写了,所以就会调用mouse类中的use(util obj),也就输出“使用鼠标和工具了”。
我们会发现其他的几个操作也都是符合这个规律的。
如果我们在父类util里面把每个类型的方法都加上,例如下图:
那么输出结果就会变成:
个人理解:如果父类和子类中有相同定义的方法,那么方法就会被成功重写。否则就会按照继承链的优先级来确认方法。
四、多态的优点和缺点:
优点:
1.
可替换性(substitutability): 多态对已存在代码具有可替换性。例如,我们可以通过mouse子类重写方法来实现替换util父类的use方法。
2.
可扩充性(extensibility): 多态对代码具有可扩充性。比如我们可以继承父类util的方法,然后继续扩充子类mouse的方法。
3.
接口性(interface-ability): 多态中的父类可以通过方法,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。
4.
灵活性(flexibility): 它在应用中体现了灵活多样的操作,提高了使用效率。
5.
简化性(simplicity): 多态简化了类之间的关系,减少了代码量。
------缺点:
- 如果不进行向下转型,就无法使用子类的特定功能。
五、总结:
多态是Java中的重要特性。在项目中运用也会有出色的效果,比如在特定的情况下,当某处代码的修改会牵连到很多地方的时候,通过多态的特点来降低耦合度,可以保证程序的拓展性以及降低成本。