请查看以下代码,其中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
,并执行此节目代码中未显示的其他一些内容。抽象类Value
和MyExtractor
用于“客户端代码”中类型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
但我不明白,为什么它不能像我上面尝试的那样工作。 我会感谢任何帮助或提示。
答案 0 :(得分:7)
尽我所知,你正在寻找的概念是“协方差”。仅仅因为IntValue
是Value
的子类型并不意味着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
}
这意味着,如果Foo
是Bar
的子类型,则Extractor[Bar, Baz]
是Extractor[Foo, Baz]
的子类型,如果您认为它是有意义的。如果某些东西可以在传递超类型的实例时提取所需的数据,那么根据定义它可以在传递子类型的实例时提取它。相反,如果Foo
是Bar
的子类型,则Extractor[Baz, Foo]
是Extractor[Baz, Bar]
的子类型。这也是有道理的。如果你有一个返回Foo
的提取器,你当然可以在任何需要返回Bar
的提取器的地方使用它。
可以宣布违反和协方差的时间有限制。例如,逆变类型参数只能用作方法参数,协变参数只能用作方法返回值或val。两者都不能用作变量。嵌套类型参数会变得更复杂,但规则基本上归结为“合理的地方”,并且您的示例符合所有规则。
另外注意,您的示例中的所有抽象类都应该被声明为traits。只要您的抽象类不需要构造函数参数,将它们声明为特征就可以为您提供更多的重用机会。