Scala泛型混乱

Mic*_*tte 4 java generics scala parameterized-types

我有一些通用/ OOP Scala代码(没用,只是课堂练习)

我有一个Container接口,"IterableContainer",它接收并返回其类型是AnyRef的子类的对象.它有一个具体的子类,它也接受并返回其类型是AnyRef的子类的对象.

  trait IterableContainer[Type <: AnyRef] {

    def length: Int

    def getAt(index: Int): Type

    def append(element: Type)
  }

  class IterableArrayList[T <: AnyRef]() extends IterableContainer[T] {

    val underlyingContainer = new ArrayBuffer[T](16)

    override def length: Int = {
      return underlyingContainer.length
    }

    override def getAt(index: Int): T =
    {
      if (index < underlyingContainer.length) {
        return underlyingContainer(index)
      } else {
        return null // Expression of type T does not conform to expected type. 
      }
    }

    override def append(element: T) = {
      underlyingContainer :+ element // :+ means append
    }
  }
Run Code Online (Sandbox Code Playgroud)

任何人都可以解释为什么当显式地将T声明为扩展AnyRef的类型的对象时,我不能返回null?

另外,如果你比我更了解Scala的泛型,请进一步解释 - 它们对我来说毫无意义(相对于C++泛型).

Dao*_*Wen 5

问题是T <: AnyRef并不意味着这一点T >: Null.

我相信唯一的反例就是Nothing.

在你IterableArrayList的类型参数上添加一个额外的(较低)类型绑定会使编译器满意:

class IterableArrayList[T >: Null <: AnyRef]() extends IterableContainer[T] {
Run Code Online (Sandbox Code Playgroud)

现在编译器知道这T是一个子类型AnyRef 超类型Null.


现在,你可能会想到自己,"这不会导致在Scala中编写库代码的各种问题吗?人们必须在T >: Null任何地方添加!"

然而,答案是否定的.

为什么?因为Scala程序员几乎从未使用过null.

我们来看List一个例子.获取列表的第一个元素有两种方法:

def head: A (scaladoc)

def headOption: Option[A] (scaladoc)

如果您调用head空列表,则会出现异常.如果你打电话headOption给一个空列表,那么你得到None.

这是Scala库的常用策略:要么抛出异常,要么返回Option类型 - 你永远不会返回null.

示例使用Option[T]:

override def getAt(index: Int): Option[T] =
  if (index < underlyingContainer.length) {
    Some(underlyingContainer(index))
  } else {
    None
  }
Run Code Online (Sandbox Code Playgroud)

使用例外的示例:

override def getAt(index: Int): T = underlyingContainer(index)
Run Code Online (Sandbox Code Playgroud)

请注意,如果索引无效,则底层ArrayBuffer已经抛出IndexOutOfBoundsException.如果用户传入负索引,这还具有正常工作的附加优势...(0 <= index如果您继续使用,可能需要添加一个条件Option.)