简体   繁体   中英

What's the rule to implement an method in trait?

I defined a trait:

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

Then define a class X to implement it:

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

It compiled. Then I change the return type in the subclass:

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

It also compiled. Then change the parameter type:

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

It can't compiled this time, the error is:

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.)

It seems the parameter should match exactly, but the return type can be a subtype in subclass?


Update: @Mik378, thanks for your answer, but why the following example can't work? I think it doesn't break Liskov:

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

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

It's exactly like in Java, to keep Liskov Substitution principle , you can't override a method with a more finegrained parameter.

Indeed, what if your code deals with the A type, referencing an X type under the hood. According to A , you can pass Any type you want, but B would allow only String . Therefore => BOOM

Logically, with the same reasonning, a more finegrained return type is allowed since it would be cover whatever the case is by any code dealing with the A class.

You may want to check those parts: http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)#Covariant_method_return_type

and

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

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

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

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

It would act as a perfect overloading, not an overriding.

In Scala, it's possible to have methods with the same name but different parameters:

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

This is called method overloading, and mirrors the semantics of Java (although my example is unusual - normally overloaded methods would do roughly the same thing, but with different combinations of parameters).

Your code doesn't compile, because parameter types need to match exactly for overriding to work. Otherwise, it interprets your method as a new method with different parameter types.

However, there is no facility within Scala or Java to allow overloading of return types - overloading only depends on the name and parameter types. With return type overloading it would be impossible to determine which overloaded variant to use in all but the simplest of cases:

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

This is why your first example compiles with no problem - there is no ambiguity about which method you are implementing.

Note for JVM pedants - technically, the JVM does distinguish between methods with different return types. However, Java and Scala are careful to only use this facility as an optimisation, and it is not reflected in the semantics of Java or Scala.

This is off the top of my head, but basically for X.hello to fit the requirements of A.hello , you need for the input of X.hello to be a superclass of A.hello 's input (covariance) and for the output of X.hello to be a subclass of A.hello 's output(contravariance).

Think of this is a specific case of the following

class A 
class A' extends A
class B 
class B' extends B

f :: A' -> B
g :: A  -> B'

the question is "Can I replace f with g in an expression y=f(x) and still typecheck in the same situations?

In that expression, y is of type B and x is of type A'

In y=f(x) we know that y is of type B and x is of type A'

g(x) is still fine because x is of type A' (thus of type A )

y=g(x) is still fine because g(x) is of type B' (thus of type B )

Actually the easiest way to see this is that y is a subtype of B (ie implements at least B ), meaning that you can use y as a value of type B . You have to do some mental gymnastics in the other thing.

(I just remember that it's one direction on the input, another on the output, and try it out... it becomes obvious if you think about it).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM