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] = 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编译之后是一个抽象类:

class Nothing$  extends Throwable{}

 从这里看两者没有任何关系。但是在Nothign$.scala的源码中有一段注释:

 scala
 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 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 源码:

 `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 的源码:


 * 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
 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 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  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.
   *
   *    x   the scala.runtime.BoxedUnit to be unboxed.
   *  @throws     ClassCastException  if the argument is not a scala.runtime.BoxedUnit
   *       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是什么类型呢?我们打开源码看一下:

 scala.runtime;
class BoxedUnit implements java.io.Serializable {
    static long serialVersionUID = 8405543498931817370L;
    static BoxedUnit UNIT = new BoxedUnit();
    static Class<Void> TYPE = java.lang.Void.TYPE;
    private Object readResolve() { return UNIT; }
    private BoxedUnit() { }
    boolean equals(java.lang.Object other) {return this == other;}
    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 class Option[+A]  Product with Serializable {}//注意sealed 关键字
//class
case class Some[+A](value: A) extends Option[A] {
  def isEmpty = false
  def get = value
}
//Object
object None extends Option[Nothing] {
  def isEmpty = true
  def get = 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:

   看一下源码:

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 = 集合为空
  override def head: Nothing = new NoSuchElementException("head of empty list")抛出NoSuchElementException异常
  override def tail: List[Nothing] = new UnsupportedOperationException("tail of empty list")抛出UnsupportedOperationException异常

 

 

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

原文链接:scala(一)Nothing、Null、Unit、None 、null 、Nil理解 转载请注明出处!

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

-----end

相关文章

共收录Twitter的14款开源软件,第1页Twitter的Emoji表情 Tw...
Java和Scala中关于==的区别Java:==比较两个变量本身的值,即...
本篇内容主要讲解“Scala怎么使用”,感兴趣的朋友不妨来看看...
这篇文章主要介绍“Scala是一种什么语言”,在日常操作中,相...
这篇文章主要介绍“Scala Trait怎么使用”,在日常操作中,相...
这篇文章主要介绍“Scala类型检查与模式匹配怎么使用”,在日...