美文网首页大数据相关
理解Scala语言中Null/Nothing/Nil/None/

理解Scala语言中Null/Nothing/Nil/None/

作者: LittleMagic | 来源:发表于2020-11-08 22:58 被阅读0次

Prologue

Scala语言虽然是构建在JVM体系之上的,但为了适应函数式编程的需要,它的语法和Java几乎完全不同,在很多基础层面——比如类型系统——也是自成一派的。在Scala类型系统中,Null、Nothing、Nil、None、Unit这些类型看起来似乎都表达“空”的语义,但实际上很有一些区别,容易混淆。本文来简单分辨一下。

作为预热,可以先看看来自官方文档的class hierarchy diagram。

https://www.scala-lang.org/files/archive/spec/2.12/12-the-scala-standard-library.html

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特征会以抽象类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的蛛丝马迹。

public final static Class<Void> TYPE = java.lang.Void.TYPE;

The End

准备迎接双11大潮的洗礼,民那晚安晚安。

相关文章

网友评论

    本文标题:理解Scala语言中Null/Nothing/Nil/None/

    本文链接:https://www.haomeiwen.com/subject/ikekbktx.html