zl程序教程

您现在的位置是:首页 >  后端

当前栏目

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

scala语言 理解 null unit none nil
2023-09-11 14:16:37 时间

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)