为什么var会导致类型差异错误,其中val在Scala中编译?

时间:2016-01-03 13:07:08

标签: scala types variance

scala> case class Data[+T](val value:T=null)
defined class Data

scala> val foo=Data[ArrayBuffer[Data[Any]]]()
foo: Data[scala.collection.mutable.ArrayBuffer[Data[Any]]] = Data(null)

scala> foo.value+=Data[String]()
java.lang.NullPointerException
  ... 33 elided

我想要一个Data类,它实例化为Data [String],Data [ArrayBuffer [Data [Any]]]或Data [Map [String,Data [Any]]]。在上面的例子中,我尝试将其实例化为Data [ArrayBuffer [Data [Any]]]并将Data [String]添加到其arraybuffer中。当然我得到一个空指针异常,因为value为null。但这个例子的重点是它至少可以编译并运行。

现在,在Data构造函数中,我想将value实例化为Data [String],ArrayBuffer [Data [Any]]或Map [String,Data [Any]],具体取决于最初为null的值的类型由getClass方法返回。但是为此,我需要将值变为var,以便在检查其null值的类型后对其进行修改。

但是我收到了这个错误:

scala> case class Data[+T](var value:T=null)
<console>:11: error: covariant type T occurs in contravariant position in type T of value value_=
       case class Data[+T](var value:T=null)

3 个答案:

答案 0 :(得分:4)

Data中设置T不变量。只需删除+Data[T] - 这应该编译。 更好的是,重新考虑你的设计以摆脱空值和可变变量 - 它们都闻到了。

修改:阅读完评论后,我会更清楚您的目标。考虑这样的事情,例如作为选项之一。

sealed trait Node 
case class ListNode(list: Seq[Node]) extends Node
case class MapNode(map: Map[String, Node]) extends Node
case class LeafNode(data: String) extends Node

现在你可以用类似的东西解析你的文档(这是“伪代码”,将它调整为你正在使用的任何xml解析库):

def parseNode(tag: XMLTag): Node = tag.tagType match {
   case LIST => 
      val subNodes = tag.getNestedTags.map(parseNode)
      ListNode(subNodes)
   case MAP => 
      val subNodes = tag.getNestedTags.map { tag => 
        tag.name -> parseNode(tag) 
      }
      MapNode(subNodes.toMap)
   case _ =>  
      LeafNode(tag.text)
}

答案 1 :(得分:3)

http://like-a-boss.net/2012/09/17/variance-in-scala.html#variance_and_type_safety

  

方差和类型安全

     

使用var字段定义泛型类时,我们可能会遇到编译时错误:

scala> class Invariant[T](var t: T)
defined class Invariant

scala> class Covariant[+T](var t: T)
<console>:7: error: covariant type T occurs in contravariant position in type T of value t_=
      class Covariant[+T](var t: T)
            ^

scala> class Contravariant[-T](var t: T)
<console>:7: error: contravariant type T occurs in covariant position in type => T of method t
      class Contravariant[-T](var t: T)
  

让我们分解一下。为什么编译器不允许在Covariant类中使用getter?

scala> abstract trait Covariant[+T] {
    |   def take(t: T): Unit
    | }
<console>:8: error: covariant type T occurs in contravariant position in type T of value t
        def take(t: T): Unit
                  ^

scala> abstract trait Contravariant[-T] {
    |   def take(t: T): Unit
    | }
defined trait Contravariant
  

为什么呢?让我们考虑一下协方差的用法,假设我们有一个类:

class Printer[+T] {
    |    def print(t: T): Unit = ???
    | }
<console>:8: error: covariant type T occurs in contravariant position in type T of value t
          def print(t: T): Unit = ???
  

如果打印方法可以打印狗是否有意义(一般情况下)它还应该打印动物?也许有时候但是在一般意义上,如果我们想要概括Printer类,我们应该使用逆变。编译器非常聪明,可以为我们检查这种用法。

     

让我们考虑第二个用例:返回一个通用参数:

scala> class Create[-T] {
    |   def create: T = ???
    | }
<console>:8: error: contravariant type T occurs in covariant position in type => T of method create
        def create: T = ???
  

再一次 - Create是否应该通过逆变来概括?如果Create返回Animal类的实例,我们是否应该能够在每个需要Create [Dog]的地方使用它? scala编译器足够智能,如果我们尝试它就会在我们面前爆炸。

答案 2 :(得分:0)

我可以这样做:

package data

case class Data[+T](val value:T)
{
    println(value.getClass)
}

这样我必须从构造函数中显式初始化值。没错,只是我发现它有点过于冗长。

import data.Data

import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.Map

object Run extends App
{

    val a=Data[ArrayBuffer[Data[Any]]](ArrayBuffer[Data[Any]]())

    a.value+=Data[String]("bar")

    println(a.value)

    val m=Data[Map[String,Data[Any]]](Map[String,Data[Any]]())

    m.value+=("foo"->Data[String]("bar"))

    println(m.value)

}

打印:

class scala.collection.mutable.ArrayBuffer
class java.lang.String
ArrayBuffer(Data(bar))
class scala.collection.mutable.HashMap
class java.lang.String
Map(foo -> Data(bar))

程序只编译带有+ T类型参数的数据,否则我得到错误:

type mismatch;
[error]  found   : data.Data[String]
[error]  required: data.Data[Any]
[error] Note: String <: Any, but class Data is invariant in type T.
[error] You may wish to define T as +T instead. (SLS 4.5)
[error]         a.value+=Data[String]("bar")