多态性(polymorphism)是OOP最强大、最有用的特性。截至目前,多态性用到了所讲的所有其他OOP概念和特性。在通向精通Java语言编程的征程上,多态性是最高级别概念站点。
一个对象具有跟另一不同类的对象一样的行为,或者具有跟另一不同接口的实现一样的行为。具有这样行为的能力被称为多态性(polymorphism)。如果在因特网上搜索“多态性”一词,就会发现它是“以多种不同形式出现的情况”。变态(metamorphosis)是“通过自然或超自然的手段,改变某物或某人的形状或性质,使其变成完全不同的形状或性质。”因此,Java多态性是对象在不同的条件下表现或展示出完全不同行为的能力,如同经过了一个变态的过程。
下面开始动手实战,直观理解这一概念。这里采用的是对象工厂(object factory)——一种工厂式的特定编程实现手段。对象工厂是“一种方法,返回的是发生了改变了的原型(或类)的对象”。
01、对象工厂
对象工厂背后的理念是创建一个方法,该方法在某些条件下返回某个类型的新对象。举例来说吧。拿CalcUsingAlg1和CalcUsingAlg2这两个类做示范:
interface CalcSomething{
double calculate();
}
class CalcUsingAlg1 implements CalcSomething{
public double calculate(){ return 42.1; }
}
class CalcUsingAlg2 implements CalcSomething{
private int prop1;
private double prop2;
public CalcUsingAlg2(int prop1, double prop2) {
this.prop1 = prop1;
this.prop2 = prop2;
}
public double calculate(){ return prop1 * prop2; }
}
可以看到,这两个类实现相同的接口CalcSomething,但使用的算法不同。
现在,假设这样决定:选择所用的算法是在一个属性文件中,那么,就可创建以下对象工厂:
class CalcFactory{
public static CalcSomething getCalculator(){
String alg = getAlgValueFromPropertyFile();
switch(alg){
case "1":
return new CalcUsingAlg1();
case "2":
int p1 = getAlg2Prop1FromPropertyFile();
double p2 = getAlg2Prop2FromPropertyFile();
return new CalcUsingAlg2(p1, p2);
default:
System.out.println("Unknown value " + alg);
return new CalcUsingAlg1();
}
}
}
这个工厂根据getAlgValueFromPropertyFile()方法返回的值,选择要使用哪种算法。
对第二个算法而言,还用到getAlg2Prop1FromPropertyFile()和getAlg2Prop2FromPropertyFile()方法来获取算法的输入参数。
但这种复杂性对客户是隐藏的。示范如下:
CalcSomething calc = CalcFactory.getCalculator();
double result = calc.calculate();
可以添加新的算法变量,更改算法参数的源代码或算法选择的过程,但客户端不需要更改代码。多态性的威力体现于此。此外,可以使用继承来实现多态行为。思考下面的类:
class CalcSomething{
public double calculate(){ return 42.1; }
}
class CalcUsingAlg2 extends CalcSomething{
private int prop1;
private double prop2;
public CalcUsingAlg2(int prop1, double prop2) {
this.prop1 = prop1;
this.prop2 = prop2;
}
public double calculate(){ return prop1 * prop2; }
}
那么,这里的工厂会呈现下面的模样:
class CalcFactory{
public static CalcSomething getCalculator(){
String alg = getAlgValueFromPropertyFile();
switch(alg){
case "1":
return new CalcSomething();
case "2":
int p1 = getAlg2Prop1FromPropertyFile();
double p2 = getAlg2Prop2FromPropertyFile();
return new CalcUsingAlg2(p1, p2);
default:
System.out.println("Unknown value " + alg);
return new CalcSomething();
}
}
}
但是,客户端代码仍然不变:
CalcSomething calc = CalcFactory.getCalculator();
double result = calc.calculate();
如果可以选择,有经验的程序员将使用公共接口来实现。
公共接口允许更灵活的设计,因为Java的一个类可以实现多个接口,但仅可以扩展(继承)一个类。
02、instanceof运算符
不幸的是,事情并不总是那么简单。有时,程序员不得不处理由不相关的类组装而成的代码,而这些不相关的类甚至来自不同的框架。这种情况下,使用多态性可能不是一个可选的办法。不过,仍然可以隐藏算法选择的复杂性,甚至使用instanceof运算符来模拟多态行为。对象是某个类的实例时,instanceof运算符返回true。
假设有两个不相关的类,具体如下:
class CalcUsingAlg1 {
public double calculate(CalcInput1 input){
return 42. * input.getProp1();
}
}
class CalcUsingAlg2{
public double calculate(CalcInput2 input){
return input.getProp2() * input.getProp1();
}
}
每个类都期待输入某类型的对象,具体如下:
class CalcInput1{
private int prop1;
public CalcInput1(int prop1) { this.prop1 = prop1; }
public int getProp1() { return prop1; }
}
class CalcInput2{
private int prop1;
private double prop2;
public CalcInput2(int prop1, double prop2) {
this.prop1 = prop1;
this.prop2 = prop2;
}
public int getProp1() { return prop1; }
public double getProp2() { return prop2; }
}
假设一下,如果实现的方法接收到这样一个对象:
void calculate(Object input) {
double result = Calculator.calculate(input);
//other code follows
}
这里,仍然使用了多态性,因为将输入描述为Object类型。能够做到这一点,是因为Object类是所有Java类的基类。现在,看看Calculator类是如何实现的:
class Calculator{
public static double calculate(Object input){
if(input instanceof CalcInput1){
return new CalcUsingAlg1().calculate((CalcInput1)input);
} else if (input instanceof CalcInput2){
return new CalcUsingAlg2().calculate((CalcInput2)input);
} else {
throw new RuntimeException("Unknown input type " +
input.getClass().getCanonicalName());
}
}
}
由上可见,Calculator类用的是instanceof运算符来选择适当的算法。
通过使用Object类作为输入类型,Calculator类也利用了多态性,但是其大部分实现与多态性无关。
然而,从外部看,Calculator类似乎是多态的。确实如此,但只是在一定程度上呈多态性。