zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    Quartus II 12.0傻瓜版安装教程(附下载链接)
    MathType 免费版安装教程(附下载链接)
    Matlab与Visual Studio 安装和联合编译配置(附下载链接)
    操作系统的概述
    Sublime Text 3 安装,C/C++代码的编译运行以及代码格式化的配置 (附下载链接)
    DSP中的DELAY_US ( )函数存放在FLASH下执行在RAM中的方法
    vtk:如何利用鼠标交会三维模型,获取交点坐标?
    进一步完善了近景摄影测量和低空数据处理软件!
    SURF算法速度更快!
    vtk:如何使用鼠标选中多个点?
  • 原文地址:https://www.cnblogs.com/PerkinsZhu/p/7868012.html
Copyright © 2011-2022 走看看