Привет, я недавно изучаю Scala, и меня смущает нуль/ничего в Scala, в следующем коде я реализую связанный с клиентом список с общим типом и практикуюсь с дисперсией.
Определил мой абстрактный класс
/**
* head = first element of the list
* tail = remainder of the list
* isEmpty = is this list empty
* add(int) => new list with this element added
* toString => a string representation of the list
*/
abstract class MyList[+T] {
val head: T
val tail: MyList[T]
val isEmpty: Boolean
def add[A >: T](n: A): MyList[A]
def printElements: String
override def toString: String = s"[$printElements]"
}
Два типа класса узла — Пустой и Минусы
class Empty[+T] extends MyList[T] {
override val head: T = ??? // what should I implement at here???
override val tail: MyList[T] = null
override val isEmpty: Boolean = true
override def add[A >: T](elem: A): MyList[A] = new Cons(elem, this)
override def printElements: String = ""
}
class Cons[+T](h: T, t: MyList[T]) extends MyList[T] {
override val head: T = h
override val tail: MyList[T] = t
override val isEmpty: Boolean = false
override def add[A >: T](elem: A): MyList[A] = new Cons(elem, this)
override def printElements: String = {
if (tail.isEmpty) s"$head"
else s"$head, ${tail.printElements}"
}
}
Вот мой тест
object ListTest2 extends App {
val listOfIntegers: MyList[Int] = new Cons(1, new Cons(2, new Empty[Int]))
val listOfString: MyList[String] = new Cons("1", new Cons("2", new Empty[String]))
println(listOfIntegers)
println(listOfString)
}
Я попытался определить object Empty
для последнего узла в этом связанном списке.
//object Empty extends MyList[Nothing] {
// override val head: Nothing = throw new NoSuchElementException
// override val tail: MyList[Nothing] = null
// override val isEmpty: Boolean = true
//
// override def add[B >: Nothing](elem: B): MyList[B] = new Cons(elem, Empty)
// override def printElements: String = ""
//}
Но он запускается с исключением во время инициации.
Еще лучше моделировать List
с помощью ADT, а не пытаться использовать методы ООП: scastie.scala-lang.org/BalmungSan/FQ13Ncd0T26B1VujWTyP3A
Возвращаться null
из tail
не очень хорошая идея. Это может привести к NPE. Лучше пораньше провалиться, поэтому просто бросайте (если это ваша семантика метода). Лучшая семантика - вернуть пустой список.
head
также может бросить. Это единственный способ создать значение произвольного типа T
. Если вы можете изменить подпись, то лучше вернуть Option[T]
т.е. None
вместо null
или исключения.
Обычно в трейте или абстрактном классе абстрактные члены (head
, tail
, isEmpty
) должны быть def
. Являются ли они на самом деле def
, val
или lazy val
, это детали реализации (и должны быть указаны в наследниках).
Я попытался определить объект Empty для последнего узла в этом связанном списке... Но он запускается с исключением во время инициации.
Вы можете избежать исключения, если сделаете head
lazy val
(или def
)
override lazy val head: Nothing = throw new NoSuchElementException
def
можно переопределить с помощью def
, val
, lazy val
.
override def head: Nothing = throw new NoSuchElementException
должен работать так, как вы ожидали.