Scala 泛型混淆
Scala generics Confusion
我有一些 generic/OOP Scala 代码(没有用,只是 class 室内练习)
我有一个 Container 接口 "IterableContainer",它接受 returns 对象,其类型是 AnyRef 的子class。它有一个具体的 sub-class,它也接收和 returns 对象,其类型是 AnyRef.class 的 subclass。
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
}
}
当 T 被明确声明为扩展 AnyRef 类型的对象时,谁能解释为什么我不能 return null?
此外,如果您比我更了解 Scala 的泛型,请进一步解释 - 它们对我来说真的毫无意义(相对于 C++ 泛型)。
问题是 T <: AnyRef
并不意味着 T >: Null
。
我认为唯一的反例是Nothing
。
在 IterableArrayList
的类型参数上添加一个额外的(较低的)类型绑定会让编译器满意:
class IterableArrayList[T >: Null <: AnyRef]() extends IterableContainer[T] {
现在编译器知道 T
是 AnyRef
的子类型 和 是 Null
的超类型。
现在,你可能在想,"Doesn't this cause all sorts of problems for writing library code in Scala? People must have to add T >: Null
everywhere!"
然而,答案是否定的
为什么?因为 Scala 程序员几乎从不使用 null
.
我们以List
为例。有两种获取列表第一个元素的方法:
def head: A
(scaladoc)
def headOption: Option[A]
(scaladoc)
如果您在空列表上调用 head
,则会出现异常。如果您在空列表上调用 headOption
,则会得到 None
.
这是 Scala 库的常用策略:要么抛出异常,要么 return 一个 Option
类型——你永远不会 return null
.
使用 Option[T]
的示例:
override def getAt(index: Int): Option[T] =
if (index < underlyingContainer.length) {
Some(underlyingContainer(index))
} else {
None
}
使用异常的示例:
override def getAt(index: Int): T = underlyingContainer(index)
请注意,如果索引无效,基础 ArrayBuffer
已经抛出 IndexOutOfBoundsException
。如果用户传入负索引,这还有一个额外的好处,即可以正常工作...(如果您使用 Option
,您可能需要在条件中添加 0 <= index
。)
我有一些 generic/OOP Scala 代码(没有用,只是 class 室内练习)
我有一个 Container 接口 "IterableContainer",它接受 returns 对象,其类型是 AnyRef 的子class。它有一个具体的 sub-class,它也接收和 returns 对象,其类型是 AnyRef.class 的 subclass。
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
}
}
当 T 被明确声明为扩展 AnyRef 类型的对象时,谁能解释为什么我不能 return null?
此外,如果您比我更了解 Scala 的泛型,请进一步解释 - 它们对我来说真的毫无意义(相对于 C++ 泛型)。
问题是 T <: AnyRef
并不意味着 T >: Null
。
我认为唯一的反例是Nothing
。
在 IterableArrayList
的类型参数上添加一个额外的(较低的)类型绑定会让编译器满意:
class IterableArrayList[T >: Null <: AnyRef]() extends IterableContainer[T] {
现在编译器知道 T
是 AnyRef
的子类型 和 是 Null
的超类型。
现在,你可能在想,"Doesn't this cause all sorts of problems for writing library code in Scala? People must have to add T >: Null
everywhere!"
然而,答案是否定的
为什么?因为 Scala 程序员几乎从不使用 null
.
我们以List
为例。有两种获取列表第一个元素的方法:
def head: A
(scaladoc)
def headOption: Option[A]
(scaladoc)
如果您在空列表上调用 head
,则会出现异常。如果您在空列表上调用 headOption
,则会得到 None
.
这是 Scala 库的常用策略:要么抛出异常,要么 return 一个 Option
类型——你永远不会 return null
.
使用 Option[T]
的示例:
override def getAt(index: Int): Option[T] =
if (index < underlyingContainer.length) {
Some(underlyingContainer(index))
} else {
None
}
使用异常的示例:
override def getAt(index: Int): T = underlyingContainer(index)
请注意,如果索引无效,基础 ArrayBuffer
已经抛出 IndexOutOfBoundsException
。如果用户传入负索引,这还有一个额外的好处,即可以正常工作...(如果您使用 Option
,您可能需要在条件中添加 0 <= index
。)