在特质中实施方法的规则是什么?

时间:2014-04-24 09:16:28

标签: scala types extends traits

我定义了一个特征:

trait A {
   def hello(name:Any):Any
}

然后定义一个类X来实现它:

class X extends A {
  def hello(name:Any): Any = {}
}

它汇编了。然后我更改子类中的返回类型:

class X extends A {
  def hello(name:Any): String = "hello"
}

它也编译了。然后更改参数类型:

class X extends A {
  def hello(name:String): Any = {}
}

这次无法编译,错误是:

error: class X needs to be abstract, since method hello in trait A of type (name: Any)
Any is not defined
(Note that Any does not match String: class String in package lang is a subclass 
of class Any in package scala, but method parameter types must match exactly.)

参数似乎应该完全匹配,但返回类型可以是子类中的子类型吗?


更新:@ Mik378,感谢您的回答,但为什么以下示例无效?我认为它并没有打破Liskov:

trait A {
   def hello(name:String):Any
}

class X extends A {
   def hello(name:Any): Any = {}
}

3 个答案:

答案 0 :(得分:6)

与Java完全一样,要保持Liskov Substitution principle,您无法覆盖具有更精细参数的方法。

确实,如果您的代码处理A类型,并引用了X类型,该怎么办? 根据{{​​1}},您可以传递所需的A类型,但Any仅允许B。 因此=> BOOM

逻辑上,使用相同的推理,允许使用更精细的返回类型,因为任何处理String类的代码都会覆盖任何情况。

您可能需要检查这些部分: http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)#Covariant_method_return_type

http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)#Contravariant_method_argument_type

UPDATE ----------------

A

这将是一个完美的超载,而不是压倒一切。

答案 1 :(得分:2)

在Scala中,可以使用相同名称但参数不同的方法:

class X extends A {
  def hello(name:String) = "String"
  def hello(name:Any) = "Any"
}

这称为方法重载,并镜像Java的语义(虽然我的例子很不寻常 - 通常重载的方法会做大致相同的事情,但使用不同的参数组合)。

您的代码无法编译,因为参数类型需要完全匹配才能覆盖才能工作。否则,它会将您的方法解释为具有不同参数类型的新方法。

但是,Scala或Java中没有允许重载返回类型的工具 - 重载仅取决于名称和参数类型。对于返回类型重载,除了最简单的情况之外,不可能确定要使用哪个重载变体:

class X extends A {
  def hello: Any = "World"
  def hello: String = "Hello"
  def doSomething = {
    println(hello.toString) // which hello do we call???
  }
}

这就是为什么你的第一个例子没有问题编译的原因 - 你实现的方法没有歧义。

注意JVM学习者 - 从技术上讲,JVM确实区分具有不同返回类型的方法。但是,Java和Scala小心地仅使用此工具作为优化,并且它没有反映在Java或Scala的语义中。

答案 2 :(得分:1)

这不是我的首要问题,但基本上X.hello符合A.hello的要求,您需要输入X.hello作为{{1}的超类输入(协方差)和A.hello的输出是X.hello输出的子类(逆变)。

想想这是以下

的具体情况
A.hello

问题是"我可以将class A class A' extends A class B class B' extends B f :: A' -> B g :: A -> B' 替换为表达式f中的g并在相同情况下仍然进行类型检查吗?

在该表达式中,y=f(x)的类型为y,x的类型为B

A'我们知道y=f(x)的类型为y,而x的类型为B

A'仍然没问题,因为g(x)的类型为x(因此类型为A'

A仍然没问题,因为y=g(x)的类型为g(x)(因此类型为B'

实际上,最简单的方法是By子类型(即至少实现B),这意味着您可以使用B作为y类型的值。你必须在另一件事上做一些心理体操。

(我只记得它是输入的一个方向,输出上的另一个方向,然后尝试一下......如果你想一想就会变得很明显。)