假设我们有一个称为interface
的Animal
,它有两个名为move()
和makeSound()
的方法。
这意味着我们可以在move()
类型的变量上发送makeSound()
和Animal
消息,并且我们只能将实现Animal
的类的对象分配给Animal
类型的变量。
现在我的问题是,Java是否可以不强制要使用多态性的类来实现interface
?
例如,为什么Java不能像下面这样实现多态:
我们只需要创建一个Animal
interface
,然后我们就可以将想要的任何对象分配给Animal
类型的变量,只要该对象具有方法move()
和makeSound()
即可,例如:
Animal animal1;
/* The Java compiler will check if Dog have the methods move() and makeSound(), if yes then
compile, if no then show a compilation error */
animal1 = new Dog();
animal1.move();
animal1.makeSound();
注意:我以Java为例,但我通常谈论的是所有OOP语言。另外,我知道我们可以使用从父类(super class)继承的子类实现多态(但这与使用interface
基本上是相同的想法)。 最佳答案
有多种获取多态性的方法。您最熟悉的一个是包含多态性(也称为子类型多态性),程序员通过某种扩展子句明确地说“X is-a Y”。您可以在Java和C#中看到这一点;两者都让您选择对表示形式和API(extends
)还是仅对API(implements
)使用这样的is-a。
还有参数多态性,您可能已将其视为泛型:使用单个声明定义Foo<T>
类型的族。您可以在Java/C#/Scala(泛型),C++(模板),Haskell(类型类)等中看到这一点。
某些语言具有“鸭子类型”,在这种情况下,他们更愿意从结构上确定类型,而不是查看声明(“X is-a Y”)。如果契约(Contract)说“要成为Iterator
,则必须具有hasNext()
和next()
方法”,则在这种解释下,提供这两种方法的任何类都是Iterator
,无论是否声明。这与您描述的情况相符。这是Java设计人员可以选择的选择。
具有模式匹配或运行时反射的语言可以表现出一种特殊的多态性(也称为数据驱动的多态性),您可以在其中定义不相关类型的多态行为,例如:
int length(Object o) {
return switch (o) {
case String s -> s.length();
case Object[] os -> os.length;
case Collection c -> c.size();
...
};
}
在这里,length
在一组特定类型上是多态的。也可以有一个明确的声明“X is-a Y”,而不必将其放在X的声明中。Haskell的类型类是这样做的,在这里,不是X声明“I is a Y”,而是一个单独的声明的
instance
明确表示“X是Y(如果对编译器不明显,这里是将X功能映射到Y功能的方法。”)这种实例通常称为见证人;通常是见证人。它是X的Y罩的见证者。Clojure的协议(protocol)是相似的,并且Scala的隐式参数起着相似的作用(“为我提供CanCopyFrom[A,B]
的见证者,否则在编译时失败”)。所有这些的意义在于,有多种方法可以实现多态,有些语言会选择自己喜欢的语言,而另一些语言则支持不止一种,等等。
如果您的问题是为什么Java为什么选择显式子类型而不是鸭子类型,那么答案就很明确:Java是一种旨在用组件构建大型系统(如C++)的语言,而组件则需要对其边界进行严格检查。松散匹配的情况是,因为双方碰巧具有相同名称的方法,所以与显式声明相比,建立程序员意图的方法不那么可靠。另外,Java语言的核心设计原则之一是“阅读代码比编写代码更重要”。声明“implementation Iterator”可能会做更多的工作(但要多做很多),但是它使读者更清楚地知道您的设计意图是什么。
因此,这是我们现在称为“仪式”的一种折衷,以提高可靠性并更清晰地捕获设计意图。