Hi I'm learning Scala recently and I am confused about the null/nothing in Scala, in the following code, I'm implementing a customer linked list with generic type, and practice with variance
Defined my abstract class
/**
* 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]"
}
Two types of node class - Empty and Cons
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}"
}
}
Here is my test
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)
}
I tried to define an object Empty
for the last node in this linked list.
//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 = ""
//}
But it run with exception during the initiation.
CodePudding user response:
Returning null
from tail
is not so good idea. This can lead to NPE. It's better to fail early, so just throw (if this is your semantics of the method). Better semantics is to return an empty list.
head
also can throw. This is the only way to create a value of arbitrary type T
. If you can change the signature then it's better to return Option[T]
i.e. None
instead of null
or exception.
Normally, in a trait or abstract class, abstract members (head
, tail
, isEmpty
) should be def
. Whether they are actually def
, val
or lazy val
is implementation details (and should be specified in inheritors).
I tried to define an object Empty for the last node in this linked list... But it run with exception during the initiation.
You can avoid exception if you make head
a lazy val
(or def
)
override lazy val head: Nothing = throw new NoSuchElementException
def
can be overridden with def
, val
, lazy val
.