В Scala есть несколько разных сущностей для обозначения несуществующих, пустых или неопределенных объектов.
Начнем с самого привычного для Java-программистов случая.

Null

Null – это trait. Объект null (с маленькой буквы) — это как раз и есть объект типа Null. Он находится внизу иерархии типов в Scala, в противовес AnyRef.
Благодаря этому вы всегда можете как-бы "занулить" любую ссылку, т.е. присвоить ссылки значение null:

scala> var x = "String"
x: java.lang.String = String
scala> var i = List(3)
i: List[Int] = List(3)
scala> i = null
i: List[Int] = null
scala> x = null
x: java.lang.String = null

Null работает только для ссылочных типов, для более общего случая, есть "ничто (Nothing)" .

Nothing

Nothing – это тоже trait. Nothing находится на самом дне иерархии типов, в противовес Any. Соответственно, это более общий тип, чем Null и подходит даже для AnyVal объектов (числа, буквы, правда/ложь и т.д.).

В отличие от Null, Nothing не может иметь экземпляров (на то оно и ничто).
Другими словами нет аналога null для Nothing.

Возникает вопрос, где такое самое "нижнее ничто" может использоваться?
В документации по API можно найти несколько примеров:

// пакет scala.sys
def error(message: String): Nothing = throw new RuntimeException(message)

Этот метод никогда ничего не возвращает, поэтому возвращаемый тип Nothing.

Unit

Unitкласс, чем-то похоже на void, который используется в Java. В Scala тип Unit применяется тогда, когда нужно показать, что функция возвращает пустое значение (но все-таки что-то возвращает, хоть и пустое).

Если открыть документацию, то можно увидеть что:

class Unit extends Any with AnyVal

Внимание! AnyVal, а не AnyRef. Это значит, что Unit это не ссылочный тип ( в отличие от Null). Можно сказать, что Unit-у как бы "ближе по родству" будут числа, буквы и другие примитивные типы (которые тоже AnyVal).

В отличие от Nothing, Unit повезло больше. Он может иметь свой объект, правда в единственном экземпляре. Он обозначается двумя круглыми скобками: (). Например:

scala> val u = ()
u: Unit = ()

Другими словами Nothing уместен тогда, когда функция в принципе ничего не возвращает, а Unit – это когда возвращает, но оно пустое.
Это отличие существенно. Например результат вызова функции с Unit может быть присвоено (в Java с void такой фокус не выйдет).

scala> def foo():Unit = {}
foo: ()Unit
scala> val u = foo()
u: Unit = ()

Nil

Nilобъект, пустой список (extends List[Nothing]).
Поскольку Nil – это список, хоть и пустой, у него как у любого списка есть метод :: (два двоеточия), с помощью которого удобно создавать списки:

scala> var x = 1 :: 2 :: Nil
x: List[Int] = List(1, 2)

Это возможно благодаря тому, что название метода заканчивается на : (двоеточие), в таком случае метод применяется к правому операнду (работает для операторной нотации вызова метода).

Другими словами, это аналогично вызову: (Nil.::(2)).::(1)

None

None – это такой хитрый объект (extends Option[Nothing]) , который используется в случае, если мы хотим получить что-то, например, из Map, а его там нет.

Например:

scala> var m = Map(1->2)
m: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2)
scala> val n = m.get(100)// ну нет такого элемента
n: Option[Int] = None

None примечателен тем, что:

  • это объект (т.е. синглтон)
  • это кейс-объект
  • наследуется от Option[Nothing]

В случае попытки получить значение (вызвав метод n.get()), мы получим исключение:java.util.NoSuchElementException.

Если у None вызывать метод isEmpty() – мы получим true.

Поскольку None – объект кейс-класса, то мы можем его "матчить" (сопоставлять по шаблону).

scala> n match { 
 case Some(x) => println("x:" + x) 
 case None => println("none") 
}

Если у None вызвать метод toList – мы получим пустой список (т.е. Nil).

scala> n.toList() == Nil
res21: Boolean = true

Поскольку None объявлен как extends Option[Nothing], а Nothing – "самое нижнее ничто", то None может работать с любыми типами (как с ссылочными так и с примитивными).