在一次采访中,有人问我是否可以在没有继承的情况下实现多态性。这可能吗?
6 回答
我读过的关于这个主题的最佳解释是著名类型理论家Luca Cardelli的一篇文章。这篇文章名为On Understanding Types, Data Abstraction, and Polymorphism。
多态性的类型
Cardelli 在本文中定义了几种类型的多态性:
- 普遍的
- 参数
- 包容
- 特别指定
- 重载
- 强迫
与遗传相关的多态性分为包含多态性或亚型多态性。
维基百科提供了一个很好的定义:
在面向对象的编程中,子类型多态性或包含多态性是类型理论中的一个概念,其中一个名称可以表示许多不同类的实例,只要它们与某个公共超类相关。包含多态性通常通过子类型来支持,即不同类型的对象完全可以替代另一种类型的对象(它们的基类型),因此可以通过公共接口处理。或者,可以通过类型强制(也称为类型转换)来实现包含多态性。
另一篇名为“面向对象编程中的多态性”的Wikipedia 文章似乎也回答了您的问题。
在 Java 中
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 被重载以支持不同的原始类型)。
其他语言
即使继承在这些形式的多态性的实现中发挥了重要作用,当然它也不是唯一的方法。其他非面向对象的语言提供其他形式的多态性。例如,在Python 等动态语言甚至 Go 等静态类型语言中的鸭子类型,或SML、Ocaml 和 Scala 等语言中的代数数据类型,或Haskell 等语言中的类型类,Clojure中的多方法, JavaScript 中的原型继承等。
即席多态 > 运算符重载 > 没有继承
Ad-hoc 多态性 > 方法重载 > 没有继承
即席多态性 > 方法覆盖 > 具有继承
参数多态 > 泛型 > 没有继承
子类型多态或包含多态 > 多态赋值 > 具有继承
子类型多态或包含多态 > 多态返回类型 > 具有继承
子类型多态或包含多态 > 多态参数类型 > 具有继承
强制多态 > 扩展 > 有或没有继承
强制多态 > 自动装箱和拆箱 > 没有继承
强制多态性 > 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
和都是s,但是当你调用它们时它们有不同的y
结果。Foo
a()
静态类型
重载 - 这意味着具有相同名称但签名不同的多个方法,无需覆盖即可
class StaticPolyExample
{
void print(int s)
{
//print s
}
void print(String s)
{
//print s
}
}
动态类型
覆盖 - 这意味着超类中的方法将在需要继承的子类中重新定义
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());
}
}
}