Давайте рассмотрим, что произойдет, если мы определим как показано ниже:
case class ListNode[+T](h: T, t: ListNode[T]) {
def head: T = h
def tail: ListNode[T] = t
def prepend(elem: T): ListNode[T] =
ListNode(elem, this)
}
Если вы посмотрите на метод prepend
, он принимает T
(который является параметром типа ListNode) в качестве параметра и возвращает ListNode[T]
, все как есть. Теперь давайте уточним использование:
val list1: ListNode[String] = ListNode("abc", null)
В вышеприведенном случае String
является супертипом null
, а поскольку ListNode
определено covarient
, это правильно.
val list2: ListNode[Any] = list1
В приведенном выше втором случае ListNode[String]
присваивается ListNode[Any]
, поскольку Any
является супертипом String
, что также является правильным.
val list3: ListNode[Int] = list2.prepend(1)
Наконец, в вышеприведенном третьем случае мы добавляем 1
, то есть Int
. Если вы посмотрите на метод prepend(elem: T): ListNode[T]
, мы передаем Int
как тип значения elem
и его возвращаемое ListNode
типа T
; в этом случае ListNode
типа Int
. Следовательно, значение, возвращаемое при вызове list2.prepend(1)
, имеет тип ListNode[Int]
. Таким образом, приведенное выше выполнение для list3
также возможно (и теоретически исправлено).
Однако в scala вы не можете определить def prepend(elem: T): ListNode[T]
в случае типа covariant
( вы получите ошибку компиляции ) и, следовательно, вы никогда не сможете выполнить val list3: ListNode[Int] = list2.prepend(1)
. Но вместо этого вы можете использовать нижние границы, как показано ниже:
case class ListNode[+T](h: T, t: ListNode[T]) {
def head: T = h
def tail: ListNode[T] = t
def prepend[U<:T](elem: U): ListNode[U] =
ListNode[U](elem, this)
}