scala(一)Nothing、Null、Unit、None 、null 、Nil理解
Posted on 2017-11-22 10:33 PerKins.Zhu 阅读(12818) 评论(1) 编辑 收藏 举报相对于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