【发布时间】:2012-07-28 18:22:40
【问题描述】:
在一次采访中,有人问我是否可以在没有继承的情况下实现多态性。这可能吗?
【问题讨论】:
标签: java oop inheritance polymorphism
在一次采访中,有人问我是否可以在没有继承的情况下实现多态性。这可能吗?
【问题讨论】:
标签: java oop inheritance polymorphism
我读过的关于这个主题的最佳解释是著名类型理论家Luca Cardelli 的一篇文章。文章名为On Understanding Types, Data Abstraction, and Polymorphism。
Cardelli 在本文中定义了几种类型的多态性:
与继承相关的多态性分为包含多态性或亚型多态性。
Wikipedia 提供了一个很好的定义:
在面向对象编程中,子类型多态或包含 多态性是类型论中的一个概念,其中名称可以表示 许多不同类的实例,只要它们通过以下方式相关 一些常见的超类。包含多态性一般是 通过子类型支持,即不同类型的对象是 完全可以替代另一种类型的对象(它们的基础 type(s)),因此可以通过通用接口进行处理。 或者,包含多态性可以通过类型来实现 强制转换,也称为类型转换。
另一篇名为Polymorphism in object-oriented programming 的维基百科文章似乎也回答了您的问题。
Java 中的这种子类型化特性是通过类和接口的继承等方式实现的。尽管 Java 的子类型化特性在继承方面可能并不总是很明显。以泛型的协变和逆变的情况为例。此外,数组是可序列化和可克隆的,尽管这在类型层次结构中的任何地方都不明显。也可以说,通过原始扩展转换,Java 中的数字运算符是多态的,在某些情况下甚至可以接受完全不相关的操作数(即字符串和数字或字符串加上其他对象的连接)。还要考虑对基元进行装箱和拆箱的情况。后面这些多态性(强制和重载)与继承无关。
包容
List<Integer> myInts = new ArrayList<Integer>();
这就是您的问题所指的情况,即类型之间存在继承或实现关系时,例如 ArrayList 实现 List 的情况。
不过,正如我所提到的,当您介绍 Java 泛型时,有时子类型化规则会变得模糊:
List<? super Number> myObjs = new ArrayList<Object>();
List<? extends Number> myNumbers = new LinkedList<Integer>();
在其他情况下,API 中的关系甚至不明显
Cloneable clone = new int[10];
Serializable obj = new Object[10]
即便如此,根据 Cardelli 的说法,所有这些都是通用多态性的形式。
参数化
public <T> List<T> filter(Predicate<T> predicate, List<T> source) {
List<T> result = new ArrayList<>();
for(T item : source) {
if(predicate.evaluate(item)){
result.add(item);
}
return result;
}
}
相同的算法可用于过滤具有各种谓词的各种列表,而不必为每种可能的列表类型重复一行代码。实际列表的类型和谓词的类型是参数化的。请参阅JDK 8 Preview 中提供的 lambda 表达式示例(为了实现谓词的简洁性)。
filter(x -> x % 2 == 0, asList(1,2,3,4,5,6)); //filters even integers
filter(x -> x % 2 != 0, asList(1L,2L,3L,4L,5L,6L)); //filters odd longs
filter(x -> x >= 0.0, asList(-1.0, 1.0)); //filters positive doubles
根据 Cardelli 的说法,这是一种通用多态性。
强制
double sum = 1 + 2.0;
整数和浮点运算是完全不同的。如果没有某种形式的强制,在这里将加号运算符应用于不同类型的两个操作数是不可能的。
在此示例中,类型 integer 和 double 会自动强制(转换)为 double 类型,而无需显式强制转换。整个表达式提升为两倍。之所以如此,是因为在 Java 中我们有原始的扩展转换。
根据 Cardelli 的说法,这种形式的自动强制转换是为加号运算符提供的一种特殊多态性形式。
在某些语言中,如果没有显式强制转换,您甚至无法将整数和浮点数相加(即 AFAIK、SML,顺便说一下,参数多态性是克服此类问题的关键)。
重载
double sum = 2.0 + 3.0;
String text = "The sum is" + sum;
这里的加号运算符表示两种不同的东西,具体取决于所使用的参数。显然,操作员已经超载。这意味着它根据操作数的类型具有不同的实现。根据 Cardelli 的说法,这是为加号运算符提供的一种特殊多态性形式。
当然,这也适用于类中的方法重载形式(即 java.lang.Math 方法 min 和 max 被重载以支持不同的原始类型)。
即使继承在其中一些形式的多态性的实现中起着重要作用,当然它也不是唯一的方法。其他非面向对象的语言提供其他形式的多态性。例如,duck typing 在 Python 等动态语言甚至 Go 等静态类型语言中的案例,或在 SML、Ocaml 和 Scala 等语言中的 algebraic data types 或在 Haskell、@987654329 等语言中的 type classes 案例Clojure 中的 @,JavaScript 中的 prototypal inheritance 等
【讨论】:
即席多态 > 运算符重载 > 没有继承
即席多态 > 方法重载 > 没有继承
即席多态 > 方法覆盖 > 继承
参数多态 > 泛型 > 没有继承
子类型多态或包含多态> 多态赋值> 带继承
子类型多态或包含多态> 多态返回类型> 带继承
子类型多态或包含多态>多态参数类型>具有继承
强制多态 > 扩展 > 有或没有继承
强制多态 > 自动装箱和拆箱 > 没有继承
强制多态 > Var args > 没有继承
强制多态 > 类型转换 > 没有继承
【讨论】:
当然。在 Java 中,您可以让两个类实现相同的接口,并且它们的结果是多态的。没有继承任何功能。
public interface Foo {
public int a();
}
public class A implements Foo {
public int a() {
return 5;
}
}
public class B implements Foo {
public int a() {
return 6;
}
}
然后在别处:
Foo x = new A();
System.out.println(x.a())
Foo y = new B();
System.out.println(y.a())
x和y都是Foos,但是调用a()时结果不同。
【讨论】:
静态类型
重载 - 这意味着多个具有相同名称但签名不同的方法,可以不覆盖
class StaticPolyExample
{
void print(int s)
{
//print s
}
void print(String s)
{
//print s
}
}
动态类型
overriding - 这意味着超类中的方法将在需要继承的子类中重新定义
class Printer
{
void print(String s)
{
// prints String
}
}
class diffPrinter extends Printer
{
void print(String s)
{
// prints String differently
}
}
【讨论】:
函数重载是一种多态性(尽管它不是真正的多态性),无需继承即可实现。
例如
class Foo {
public void Arrest( Animal A){
/*code...*/
}
public void Arrest( Terrorist T ) {
/*code...*/
}
}
from main :
Foo f= new Foo();
f.Arrest( new Lion() );
f.Arrest(new Terrorist());
arrest方法被调用了2次,但是代码的执行路径不同。
*这又不是真正的多态形式。 一般而言,真正的多态性如果没有继承是无法实现的。
【讨论】:
是的,我想他们可能想听听接口的多态性。因此,如果有 2 个类从同一个接口实现,那么我们可以在我们期望具有这种接口的对象的所有地方使用。参见维基百科的代码:
// from file Animal.java
public interface Animal {
public String talk();
}
// from file Cat.java
public class Cat implements Animal {
@Override
public String talk() {
return "Cat says Meow!";
}
}
// from file Dog.java
public class Dog implements Animal {
@Override
public String talk() {
return "Dog says Woof! Woof!";
}
}
// from file PolymorphismExample.java
public class PolymorphismExample {
public static void main(String[] args) {
Collection<Animal> animals = new ArrayList<Animal>();
animals.add(new Cat());
animals.add(new Dog());
for (Animal a : animals) {
System.out.println(a.talk());
}
}
}
【讨论】: