scala(一)Nothing、Null、Unit、None 、null 、Nil理解


相對於java的類型系統,scala無疑要復雜的多!也正是這復雜多變的類型系統才讓OOP和FP完美的融合在了一起!

Nothing:

  如果直接在scala-library中搜索Nothing的話是找不到了,只能發現一個Nothing$的類(后面再說Nothing$和Nothing的關系)。要想看到Nothing.scala的源碼需要去github上的scala源碼中查找Nothing源碼  可以看到在Nothing.scala中只是定義了一個sealed trait:

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的應用場景,那么,Nothing是如何工作的呢?Nothing和Nothing$之間又有什么關系呢?

分別對Nothing.scala和Nothing$.scala進行編譯和反編譯:

    Nothing.scala

  

  Nothing$.scala

編譯之后的文件名

根據上面的結果來看,Nothing.scala編譯之后是一個接口類型:

public abstract interface Nothing {} 

 而Nothing$.scala編譯之后是一個抽象類:

public abstract class Nothing$  extends Throwable{}

 從這里看兩者沒有任何關系。但是在Nothign$.scala的源碼中有一段注釋:

package scala
package runtime
/**
 * Dummy class which exist only to satisfy the JVM. 虛擬類,只存在於JVM中
  * It corresponds to `scala.Nothing`. 它對應scala.Nothing
  * If such type appears in method signatures, it is erased to this one. 如果該Nothing出現在方法簽名中則將會被抹掉,然后替換為Nothing$
 */
sealed abstract class Nothing$ extends Throwable

  這里闡明了Nothing$的作用,也即是代碼中如果出現Nothing類型的時候,在load到JVM運行的時候將會把Nothing替換為Nothing$類型。也即在JVM之外以Nothing的身份進行顯示,在JVM中以Nothing$的身份進行顯示,兩者表示同一個含義。

 這樣解釋也滿足了Nothing可以當做  throw new XXXXXException("head of empty list")的類型使用的原因,即: Nothing$ extends Throwable.

 Null:

  Null.scala的源碼和Nothing.scala的源碼在同一個包中存在着Null.scala源碼 。對比一下兩者:

      Null有唯一的實例null  Nothing沒有任何實例

      Null是所有引用類型(AnyRef)的子類型  Nothing是所有類型的子類型  因此=> Nothing是Null 的子類型

 除了上面的兩點區別之外,Null和Nothing幾乎一致

 Null.scala 源碼:

package scala

/** `Null` is - together with [[scala.Nothing]] - at the bottom of the Scala type hierarchy.
 *
 *  `Null` is a subtype of all reference types; its only instance is the `null` reference.
 *  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。trait Null 說明其實一個類型,那么就可以用該類型定義字段:val myNull:Null=null

 

那么注釋中說Null有唯一的一個實例,那么我們new一個Null如何呢?

這里提示Null是一個abstract抽象類,可源碼中定義的Null是一個trait,那么這里在運行的時候為何會提示Null is abstract呢?其次在和Nothing$.scala 旁邊同樣存在一個Null$.scala,這個類和Null.scala又有什么關系呢?

正如你想象的那樣,Null$.scala正是Null在JVM中的另一種身份。我們看一下Null$.scala 的源碼:

package scala
package runtime
/**
 * Dummy class which exist only to satisfy the JVM. 該類為虛擬類,只存在JVM
 * It corresponds to `scala.Null`.  它對應着scala.Null
 * If such type appears in method signatures, it is erased to this one. 如果Null出現在方法簽名中則用Null$去替換
 * A private constructor ensures that Java code can't create  subclasses. private構造方法確保不會被創建其它實例
 * The only value of type Null$ should be null  唯一個實例及時null
 */
sealed abstract class Null$ private ()

 這里明確指出Null將會被Null$替換,那么在運行的時候Null便為Null$類型!原來上面提示Null is abstract是這個原因!!我們再進一步確認一下,查看一下Null$.scala 進行編譯看編譯之后的代碼:

對Null$.scala進行編譯然后反編譯結果:

注意:在javap查看代碼的時候如果是private 構造參數則不會顯示處理,如果是public則會直接顯示處理,上面沒有顯示private Null$()正說明Null$的構造參數正是private類型的)

到這里就完全就明白了,Null在運行期間被替換了Null$.

 Unit:

   在解釋Unit之前需要分析一下函數的返回值有幾種可能性!

     1、正常返回了數據,例如 def demo01():Int=10   def demo02():String="hello"

     2、方法發生異常,沒有執行結束,未進行返回操作。例如 def demo02():Nothing=throw new NoSuchElementException("head of empty list")

     3、方法返回了一個類型X的實例,該類型X表示 “沒有返回值”的類型。這里有些繞,要仔細理解“沒有返回值”和“沒有返回值類型”。而這里的X即是Unit,而返回的那個實例即(),在java中表示未void。例如: def demo03():Unit={println("hello")}

對於1很好理解,返回什么就接受什么類型。對於2便是上面說的Nothing類型,沒有進行返回操作;對於3是有返回的,只是返回的內容表示“沒有返回值”的類型。

 在源碼中很好的解釋了Unit的作用:

/** `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 {
  // Provide a more specific return type for Scaladoc
  override def getClass(): Class[Unit] = ???
}

  根據注釋可以看出,Unit是所有AnyVal 的子類(注意區別Nothing),只有一個唯一的Value(注意這里是Value依舊是實例/對象)。如果方法的返回值類型為Unit,則類似於java中void。

在Unit的伴生對象中則揭開了Unit和void的關系:

object Unit extends AnyValCompanion {

  /** 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 /** Transform a boxed type into a value type.  Note that this
   *  method is not typesafe: it accepts any Object, but will throw
   *  an exception if the argument is not a scala.runtime.BoxedUnit.
   *
   *  @param  x   the scala.runtime.BoxedUnit to be unboxed.
   *  @throws     ClassCastException  if the argument is not a scala.runtime.BoxedUnit
   *  @return     the Unit value ()
   */
  def unbox(x: java.lang.Object): Unit = x.asInstanceOf[scala.runtime.BoxedUnit]

  /** The String representation of the scala.Unit companion object. */
  override def toString = "object scala.Unit"
}

  請注意box()和unbox()方法,該方法是對數值類型進行裝箱和拆箱操作,scala中所有的數值型類型類中都有這兩種方法,主要用來數值型向java 數值的封裝型轉化,例如:int->Integer float->Float

那么Unit中的box()和unbox()也是進行拆裝箱操作了,我們看到在Unit的unbox中把java.lang.Object類型轉換為一個BoxeUnit類型的數據,那么這個BoxedUnit是什么類型呢?我們打開源碼看一下:

package scala.runtime;
public final class BoxedUnit implements java.io.Serializable {
    private static final long serialVersionUID = 8405543498931817370L;
    public final static BoxedUnit UNIT = new BoxedUnit();
    public final static Class<Void> TYPE = java.lang.Void.TYPE;
    private Object readResolve() { return UNIT; }
    private BoxedUnit() { }
    public boolean equals(java.lang.Object other) {return this == other;}
    public int hashCode() { return 0;}
    public String toString() {return "()";}
}

  可以看到其TYPE值直接指向了java的void: public final static Class<Void> TYPE = java.lang.Void.TYPE;

 看來scala只是使用Unit對java中的void進行了包裝,用Unit來表示void的類型,用BoxedUnit的單例對象來表示那個唯一的void,也即是Unit中的“()”。到這里才明白Unit是沒有任何實例的,它只是起一個類型的作用,而那個“()”也只是BoxedUnit的單例對象toSting的輸出內容而已。

 None:

   在說None之前需要了解Option類型。Option類型是對值進行封裝,根據值是否為null來返回Some(value)或者None: 

def apply[A](x: A): Option[A] = if (x == null) None else Some(x)

  Option的apply()方法可以返回None/Some可知None或Some必定是Option的子類了。看三者的定義:

sealed abstract class Option[+A] extends Product with Serializable {}//注意sealed 關鍵字 //class final case class Some[+A](value: A) extends Option[A] {
  def isEmpty = false
  def get = value
}
//Object case object None extends Option[Nothing] {
  def isEmpty = true
  def get = throw new NoSuchElementException("None.get")
}

  Option[+A]前面有sealed 關鍵字,則Option[+A]的所有子類必須在同一個文件中定義。因此Option只有Some和None兩個子類。注意上面對Some和None的定義,Some是Class,而None是Object。class容易理解,是可以new實例的,而Object類型在編譯之后構造方法是private,無法在外部生成實例對象,而其中的方法編譯之后變為static的靜態方法,可以通過類名直接調用。另外,在對Object進行編譯的時候會同時生成一個XXXX$.class的文件,該類是一個單例類,內部會構造一個   public static XXXX$ MODULE$;  單例對象。None也同樣如此:

因此我們平時所使用的None其實就是這個public static scala.None$ MODULE$; 單例對象。在應用層面上我們只需要知道None就是一個Option[Nothing]類型的對象,調用get()方法將會拋出NoSuchElementException("None.get")異常,其存在的目的是為了表面java中的NullPointerException()的發生。

 null:

   null 就很容易理解了和java中的null是同一個null。一般在scala中不直接使用null!

 

Nil:

   看一下源碼:

case object Nil extends List[Nothing] {....}

  根據object便可知Nil是一個單例對象,必定存在一個Nil$.class,在解壓的scala-library中找到Nil$.class進行反編譯可以找到Nil$的單例對象:

可以看到  class scala.collection.immutable.Nil$ extends scala.collection.immutable.List<scala.runtime.Nothing$> ,說明Nil是List[Nothing]的子類。而在源碼中的方法則直接表明了Nil的性質:一個沒有元素的List集合

  override def isEmpty = true//集合為空
  override def head: Nothing = throw new NoSuchElementException("head of empty list")//拋出NoSuchElementException異常
  override def tail: List[Nothing] = throw new UnsupportedOperationException("tail of empty list")//拋出UnsupportedOperationException異常

 

 

=========================================

原文鏈接:scala(一)Nothing、Null、Unit、None 、null 、Nil理解 轉載請注明出處!

=========================================

-----end


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM