继承和(自动?)类型转换

时间:2010-09-12 18:22:11

标签: scala

请查看以下代码,其中Extractor[A,B]是通用框架的一部分,其他所有内容应被视为“客户端代码”(我把它煮了很多,并重命名为所有内容。所以不要请注意,Extractor似乎没有太大用处。)

scala> abstract class Extractor[A,B] {                                          
     |   def extract(d:A):B                                                     
     |   def stringRepr(d:A):String                                             
     | }                                                                        
defined class Extractor

scala> sealed abstract class Value                                              
defined class Value

scala> case class IntValue(i:Int) extends Value                                 
defined class IntValue

scala> case class StringValue(s:String) extends Value                           
defined class StringValue

scala> case class Data(i:Int, s:String)                                         
defined class Data

scala> sealed abstract class MyExtractor[Value] extends Extractor[Data, Value] {
     |   def stringRepr(d:Data) = extract(d) match {                            
     |     case IntValue(i) => i.toString                                       
     |     case StringValue(s) => s                                             
     |   }                                                                      
     | }                                                                        
defined class MyExtractor

scala> class IntExtractor(name:String) extends MyExtractor[IntValue] {
     |   def extract(d:Data) = IntValue(d.i)
     | }
defined class IntExtractor

scala> class StringExtractor(name:String) extends MyExtractor[StringValue] {
     |   def extract(d:Data) = StringValue(d.s)
     | }
defined class StringExtractor

所以简而言之,Extractor[A,B]用于从B中提取一些值A,并执行此节目代码中未显示的其他一些内容。抽象类ValueMyExtractor用于“客户端代码”中类型savety的原因。 当我尝试创建List MyExtractor时,会发生以下情况:

scala> val l = List.empty[MyExtractor[Value]]
l: List[MyExtractor[Value]] = List()

scala> new IntExtractor("test1") :: l
res5: List[MyExtractor[_ >: IntValue <: Value]] = List(IntExtractor@1fd96c5)

尝试将IntExractor转换为超类

scala> new IntExtractor("test"):MyExtractor[Value]   
<console>:24: error: type mismatch;
 found   : IntExtractor
 required: MyExtractor[Value]
       new IntExtractor("test"):MyExtractor[Value]
       ^

scala> new IntExtractor("test"):Extractor[Data,Value]
<console>:24: error: type mismatch;
 found   : IntExtractor
 required: Extractor[Data,Value]
       new IntExtractor("test"):Extractor[Data,Value]

当我像这样定义IntExtractor

时,我知道一切都很好
scala> class IntExtractor(name:String) extends MyExtractor[Value] {
     |   def extract(d:Data) = IntValue(d.i)                            
     | }
defined class IntExtractor

scala> new IntExtractor("test"):Extractor[Data,Value]              
res17: Extractor[Data,Value] = IntExtractor@1653d7a

但我不明白,为什么它不能像我上面尝试的那样工作。 我会感谢任何帮助或提示。

1 个答案:

答案 0 :(得分:7)

尽我所知,你正在寻找的概念是“协方差”。仅仅因为IntValueValue的子类型并不意味着MyExtractor[IntValue]MyExtractor[Value]的子类型。默认情况下,这两种类型之间根本没有子类型关系。要创建这样的关系,您需要声明MyExtractor与其参数相关。 Scala允许您通过在类型参数声明之前添加“+”来声明类型参数是协变的。这称为方差表示法。

sealed abstract class MyExtractor[+Value] extends Extractor[Data, Value] {        
}   

Scala还支持类型参数的逆变。逆变量就像协方差,但是相反,并且在类型参数上用“ - ”方差表示法表示。您的Extractor类型提供了一个很好的例子,说明了逆变符号是有意义的。

abstract class Extractor[-A,+B] {                                          
   def extract(d:A):B                                                     
   def stringRepr(d:A):String                                             
}       

这意味着,如果FooBar的子类型,则Extractor[Bar, Baz]Extractor[Foo, Baz]的子类型,如果您认为它是有意义的。如果某些东西可以在传递超类型的实例时提取所需的数据,那么根据定义它可以在传递子类型的实例时提取它。相反,如果FooBar的子类型,则Extractor[Baz, Foo]Extractor[Baz, Bar]的子类型。这也是有道理的。如果你有一个返回Foo的提取器,你当然可以在任何需要返回Bar的提取器的地方使用它。

可以宣布违反和协方差的时间有限制。例如,逆变类型参数只能用作方法参数,协变参数只能用作方法返回值或val。两者都不能用作变量。嵌套类型参数会变得更复杂,但规则基本上归结为“合理的地方”,并且您的示例符合所有规则。

另外注意,您的示例中的所有抽象类都应该被声明为traits。只要您的抽象类不需要构造函数参数,将它们声明为特征就可以为您提供更多的重用机会。