Null->null
Nothing
Nil->List[Nothing]
None->Option
Unit
很多人抱怨Scala相比于Java过于复杂了:大部分使用过Scala的程序员都没有能深刻理解它的类型系统和Scala的函数式编程。Scala的类型系统跟Java和C++很不一样,Scala想把面向对象纯粹化(学院派的作风?),不能有破坏面向对象的一切因素出现。null
、NULL
、int
、…这些都是不和谐的东西,应该割掉。Scala又给了太多了空间给程序员,你可以使用传统的命令式编程风格,也可以使用函数式风格,一个语言写出了不同的代码。这个跟汉语有很多方言,极为相似。太多的自由会浪费Scala的一片好心。一个思路是在封装完Java的功能后,尽量使用Scala推荐的函数式风格来写面向对象的程序。
进入Scala,你就进入了虚无缥缈的太虚境地。在何为有?何为无?的问题上?Scala的设计走得很远。Scala的有即Any
,Scala的无是Null
,null
,Nil
,Nothing
,None
,Unit
。Scala的无太让人手足无措,今天就讨论Scala的无。
要想在正确的地方使用正确的无,就要先理解它们分别表示的含义。
Null&null
很多人一辈子都没有走出这个无。Null
是一个Trait
,你不能创建她它的实例。但是Scala在语言层面上存在一个Null
的实例,那就是null
。Java中的null
意味着引用并没有指向任何对象。但存在一个悖论,一切都是对象,那没有对象是不是也是对象呢?Scala定义了一个类似于对象语义的Null
,和一个值语义的null
。这样面向对象在空引用的情况下完备了。如果你写了一个带有Null
作为参数的对象,那么你传入的参数只能是null
,或者指向Null
的引用。
scala> def tryit(value: Null) {}
tryit: (value: Null)Unit
scala> tryit("hey")
<console>:12: error: type mismatch;
found : String("hey")
required: Null
tryit("hey")
^
scala> val someRef: String = null
someRef: String = null
scala> tryit(someRef)
<console>:13: error: type mismatch;
found : String
required: Null
tryit(someRef)
^
scala> tryit(null)
scala> val nullRef: Null = null
nullRef: Null = null
scala> tryit(nullRef)
第四行我们试图传入一个String
,自然不能工作。第14行我们传入一个null
引用,但是仍然不能工作,为什么呢?因为null
引用指向的是String
类型。它可能在运行时是null
,但是在编译时类型检查却不认同,编译器认为他是个String
。
Nil
Nil
是一个继承List[Nothing]
的对象。它就是一个空的列表,下面是一些使用实例:
scala> Nil
res16: scala.collection.immutable.Nil.type = List()
scala> Nil.length
res17: Int = 0
scala> "ABC":: Nil
res18: List[String] = List(ABC)
scala> Nil :: Nil
res19: List[scala.collection.immutable.Nil.type] = List(List())
可以看出,Nil
长度为0。它并不是一无所有,它是一个容器,一个列表,只是没有存放内容而已。
Nothing
Nothing
可能比较难理解。Nothing
也是一个Trait
,它继承自Any
,而Any
是整个Scala类型系统的根。Nothing
是没有实例的,但它是任何对象的子类,他是List
的子类,是String
的子类,是Int
的子类,是任何用户自定义类型的子类。
前面提到Nil
是一个空的List[Nothing]
。由于Nothing
是任何类型的子类,那么Nil
就可以当做是一个空的String List
,空的Int List
,甚至使Any List
。Nothing
比较适合用来定义基类容器。
scala> val emptyStringList: List[String] = List[Nothing]()
emptyStringList: List[String] = List()
scala> val emptyIntList: List[Int] = List[Nothing]()
emptyIntList: List[Int] = List()
scala> val emptyStringList: List[String] = List[Nothing]("abc")
<console>:10: error: type mismatch;
found : String("abc")
required: Nothing
val emptyStringList: List[String] = List[Nothing]("abc")
第一行,我们将一个List[Nothing]
赋值给一个List[String]
。一个Nothing
是一个String
,因此是正确的。第四行,我们将一个List[Nothing]
赋值给一个List[Int]
。一个Nothing
是一个Int
,因此是正确的。Nothing
是任何类型的子类,但是上面的List[Nothing]
都不包含任何成员。当我们创建一个包含一个String
的List[Nothing]
的List
,并把它赋值给List[String]
,会出现什么情况?它会失败,因为Nothing
并不是任何类型的父类,并且也不存在Nothing
的实例。任何Nothing
的容器必然是空的,是Nil
。
另一种Nothing
的用法是作为不返回函数的返回值。因为Nothing
没有任何实例,而函数的返回值必定是一个值,是一个对象,这样定义为Nothing
为返回值的函数实际上不可能返回。
scala> def test():Nothing = { throw new IllegalArgumentException }
test: ()Nothing
scala> test
java.lang.IllegalArgumentException
at .test(<console>:10)
... 33 elided
None
写Java程序的时候,经常会碰到没有有意义的东西可以返回,我们返回null
。但返回null
有一些问题,调用方必须检查返回值,不然会有NullPointerException
的异常。这逼迫我们去check函数的返回值。还有一种解决办法是使用异常,但增加try/catch块,并不是明智的选择。
Scala内置一种解决办法。如果你想返回一个String
,但可能有的时候得不到有意义的返回值,我们可以让函数返回Option[String]
。
scala> def getAsStringMaybe(num: Int): Option[String] = {
| if (num >= 0) Some("A positive number!")
| else None //A number less than 0? Impossible!
| }
getAsStringMaybe: (num: Int)Option[String]
scala> def printResult(num: Int) = {
| getAsStringMaybe(num) match {
| case Some(str) => println(str)
| case None => println("No string!")
| }
| }
printResult: (num: Int)Unit
scala> printResult(100)
A positive number!
scala> printResult(-50)
No string!
函数getAStringMaybe
返回Option[String]
。Option
是一个抽象类,它有两个子类:Some
和None
。因此,初始化一个Option
有两种方法。getAStringMaybe
返回的只可能是Some[String]
或None
。Some
和None
又是Case Class
,可以直接用来做模式匹配,很容易用来处理返回值。
类似地,Option[T]
作为返回值,意味着调用者可能会收到Some[T]
或None
。
其实Option
作为返回值,并没有比返回null
好多少。代码里面充满了Option
和模式匹配的代码,也不优雅,所以对待Option
还是慎重。
Unit
Unit
跟java的void
一样,表示函数没有返回值。
scala> def doThreeTimes(fn: (Int) => Unit) = {
| fn(1); fn(2);fn(3);
| }
doThreeTimes: (fn: Int => Unit)Unit
scala> doThreeTimes(println)
1
2
3
scala> def specialPrint(num: Int) = {
| println(">>>" + num + "<<<")
| }
specialPrint: (num: Int)Unit
scala> doThreeTimes(specialPrint)
>>>1<<<
>>>2<<<
>>>3<<<
总的来看,Scala定义如此多的无,主要目的是为了保证它的类型系统的完整性,同时细化概念。到底好不好呢?下一个十年再下定论。