Scala语言中Null/Nothing/Nil/None/Unit的理解
Prologue
Scala语言虽然是构建在JVM体系之上的,但为了适应函数式编程的需要,它的语法和Java几乎完全不同,在很多基础层面——比如类型系统——也是自成一派的。在Scala类型系统中,Null、Nothing、Nil、None、Unit这些类型看起来似乎都表达“空”的语义,但实际上很有一些区别,容易混淆。
官方文档的class hierarchy diagram:
Null
Scala中所有类型的父类型是Any,Any有两个一级子类型:AnyVal和AnyRef,分别对应所有值类型(primitives)和引用类型(references)的根。Null是所有AnyRef的子类,处于Scala类型体系的次底层。并且它只有一个实例,就是null,表示空引用。
在Scala源代码中,Null作为一个空的特征(trait)存在。
package scala /** `Null` is - together with [[scala.Nothing]] - at the bottom of the Scala type hierarchy. * * `Null` is the type of the `null` literal. It is a subtype of every type * except those of value classes. Value classes are subclasses of [[AnyVal]], which includes * primitive types such as [[Int]], [[Boolean]], and user-defined value classes. * * Since `Null` is not a subtype of value types, `null` is not a member of any such type. * For instance, it is not possible to assign `null` to a variable of type [[scala.Int]]. */ sealed trait Null
可见,null只能赋值给引用类型,不能赋值给值类型。而在运行时,Null特征会以抽象类Null$的形式存在于JVM中。
package scala package runtime /** * Dummy class which exist only to satisfy the JVM. It corresponds to * `scala.Null`. If such type appears in method signatures, it is erased * to this one. A private constructor ensures that Java code can't create * subclasses. The only value of type Null$ should be null */ sealed abstract class Null$ private ()
这样就阻断了new Null()以及继承Null的可能性,维护了空引用的唯一性。下面举个例子说明Null和null的意义。
scala> var myStr: String = _ myStr: String = null scala> var myNull: Null = null myNull: Null = null scala> myNull = new Null <console>:12: error: class Null is abstract; cannot be instantiated myNull = new Null ^ scala> def useNull(n: Null) = { println("Hello null!") } useNull: (n: Null)Unit scala> useNull(myStr) <console>:14: error: type mismatch; found : String required: Null useNull(myStr) ^ scala> useNull(myNull) Hello null!
Nothing
Nothing在Null的基础上更进一步(说是更退一步也可以)。它是所有Any(包含Null在内)的子类,处于Scala类型体系的最底层,表示“no instance”,即真正意义上的“没有任何实例”。其定义与Null是相同的。
package scala /** `Nothing` is - together with [[scala.Null]] - at the bottom of Scala's type hierarchy. * * `Nothing` is a subtype of every other type (including [[scala.Null]]); there exist * ''no instances'' of this type. Although type `Nothing` is uninhabited, it is * nevertheless useful in several ways. For instance, the Scala library defines a value * [[scala.collection.immutable.Nil]] of type `List[Nothing]`. Because lists are covariant in Scala, * this makes [[scala.collection.immutable.Nil]] an instance of `List[T]`, for any element of type `T`. * * Another usage for Nothing is the return type for methods which never return normally. * One example is method error in [[scala.sys]], which always throws an exception. */ sealed trait Nothing
Nothing没有任何实例,其类似于java中的标示性接口(如:Serializable,用来标识该该类可以进行序列化),只是用来标识一个空类型。根据官方注释可以看出来Nothing用来标识no instances类型,该类型是其它所有类型的子类型。官方注释中给了如下两个用途:
1、用来当做Nil的类型List[Nothing]
case object Nil extends List[Nothing] {...}
Nil表示一个空的list,与list中的元素类型无关,他可以同时表示List[任意类型]的空集合。也即是Nil可以同时表示List[Int]类型的空集合和List[String]类型的空集合。那么考虑一下Nil:List[?],这里的“?”应该为什么类型呢?也即是“?”应该为Int、String....所有类型的子类型(List集合为协变的)。因此这里引入了Nothing类型作为所有类型的子类型。这样Nil:List[Nothing]就可以完美实现上述需求。
2、表示非正常类型的返回值类型
例如Nil中的两个方法:
override def head: Nothing = throw new NoSuchElementException("head of empty list") override def tail: List[Nothing] = throw new UnsupportedOperationException("tail of empty list")
Nil为空List,所以调用head和tail应该返回Nothing和List[Nothing]的实例。但是Nothing是没有实例的,这里就直接抛出Exception。所以这里就使用Nothig来表示throw .... 非正常返回的类型。非正常即发生了错误没有返回任何对象,连Unit都没有,用Nothing类表示确实也挺合适。
另外,在运行时,Nothing特征会以抽象类Nothing$的形式存在于JVM中。特别注意,Nothing可以作为异常类型被抛出,后面会见到它的这种用法。
package scala package runtime /** * Dummy class which exist only to satisfy the JVM. It corresponds * to `scala.Nothing`. If such type appears in method * signatures, it is erased to this one. */ sealed abstract class Nothing$ extends Throwable
由上文的叙述可知,Nothing类型的对象永远无法被赋值,但是它的灵活性也是最大的,可以用来作为任意类型的marker interface。来看下面的例子即可理解。
scala> val myStrList: List[String] = List[Nothing]() myStrList: List[String] = List() scala> val myIntList: List[Int] = List[Nothing]() myIntList: List[Int] = List() scala> val myStrList2: List[String] = List[Nothing]("123", "456") <console>:11: error: type mismatch; found : String("123") required: Nothing val myStrList2: List[String] = List[Nothing]("123", "456") ^ <console>:11: error: type mismatch; found : String("456") required: Nothing val myStrList2: List[String] = List[Nothing]("123", "456") ^
Nil
直接来看Nil的定义,它位于scala.collection.immutable包下。
case object Nil extends List[Nothing] { override def isEmpty = true override def head: Nothing = throw new NoSuchElementException("head of empty list") override def tail: List[Nothing] = throw new UnsupportedOperationException("tail of empty list") // Removal of equals method here might lead to an infinite recursion similar to IntMap.equals. override def equals(that: Any) = that match { case that1: scala.collection.GenSeq[_] => that1.isEmpty case _ => false } }
可见,Nil就是一个空的List[Nothing],即一个可以封装任何类型元素但又没有元素的容器。由于它确定是空的,所以其head()和tail()方法都应抛出异常,这里就利用了上述Nothing可以作为异常类型的特性。从某种意义上讲,方法抛出异常表示没有按照既定的返回值类型进行返回,所以Nothing的语义正合适。
None
在说None之前,应该先来了解Option。
Scala中的Option与Java 8引入的java.util.Optional语义相同,表示“一个实例有可能不为空,也有可能为空”,是OOP大一统思想下的产物,旨在通过让用户提前判空来避免讨厌的NullPointerException,降低直接返回null的风险。在Scala类Option[+A]的伴生对象中,apply()方法的定义如下。
/** An Option factory which creates Some(x) if the argument is not null, * and None if it is null. * * @param x the value * @return Some(value) if value != null, None if value == null */ def apply[A](x: A): Option[A] = if (x == null) None else Some(x)
当实例x不为null时,返回Some(x),否则返回的就是None了。Some和None都是Optional的子类,定义如下。
@SerialVersionUID(1234815782226070388L) // value computed by serialver for 2.11.2, annotation added in 2.11.4 final case class Some[+A](x: A) extends Option[A] { def isEmpty = false def get = x } @SerialVersionUID(5066590221178148012L) // value computed by serialver for 2.11.2, annotation added in 2.11.4 case object None extends Option[Nothing] { def isEmpty = true def get = throw new NoSuchElementException("None.get") }
可见,None的本质就是Option[Nothing]。还是举个例子吧。
scala> def getPositive(num: Int): Option[Int] = { | if (num > 0) Some(num) | else None | } getPositive: (num: Int)Option[Int] scala> def showPositive(num: Int) = { | getPositive(num) match { | case Some(x) => println("Got a positive number") | case None => println("Got a non-positive number") | } | } showPositive: (num: Int)Unit scala> showPositive(7) Got a positive number scala> showPositive(-7) Got a non-positive number
Scala可以优雅地使用模式匹配来处理Some和None的情况,所以Option在主要以Scala写成的开源框架(如Spark)中应用甚为广泛。
Unit
Unit在上面的例子中已经出现过多次了,它完全等同于Java里的void,表示函数或方法没有返回值。它的定义如下。
package scala /** `Unit` is a subtype of [[scala.AnyVal]]. There is only one value of type * `Unit`, `()`, and it is not represented by any object in the underlying * runtime system. A method with return type `Unit` is analogous to a Java * method which is declared `void`. */ final abstract class Unit private extends AnyVal { override def getClass(): Class[Unit] = null }
可见,Unit是一种特殊的值类型(AnyVal)的子类,并且它不代表任何实际的值类型,故其getClass()方法返回null。在Unit伴生对象的拆装箱逻辑中,我们可以发现:
/** Transform a value type into a boxed reference type. * * @param x the Unit to be boxed * @return a scala.runtime.BoxedUnit offering `x` as its underlying value. */ def box(x: Unit): scala.runtime.BoxedUnit = scala.runtime.BoxedUnit.UNIT
在BoxedUnit的定义里,也能够发现Unit等同于void的蛛丝马迹。
请注意box()和unbox()方法,该方法是对数值类型进行装箱和拆箱操作,scala中所有的数值型类型类中都有这两种方法,主要用来数值型向java 数值的封装型转化,例如:int->Integer float->Float
public final static Class<Void> TYPE = java.lang.Void.TYPE;
Ref:理解Scala语言中Null/Nothing/Nil/None/Unit的区别 - 简书 (jianshu.com)
scala(一)Nothing、Null、Unit、None 、null 、Nil理解 - PerKins.Zhu - 博客园 (cnblogs.com)