本文由 KnewHow 發表在 ScalaCool 團隊部落格。
去年我加入水滴團隊,面試中,面試官問:“你瞭解 Scala 嗎?”
“不瞭解(尷尬)。”
“你知道 Spark 嗎?它就是使用 Scala 編寫的,不過在我們團隊中,Scala 主要作為後端語言,我們 90% 以上的業務程式碼都是使用 Scala 編寫。Scala 在國內使用的比較少,但是在國外用的還是蠻多的,如 Twitter 就是使用 Scala 寫的後端。”
自那以後,我便開始了 Scala 的學習之旅。
Scala 是由德國的電腦科學家和程式設計方法教授 Martin Odersky 設計出來的,它的設計原理嚴格遵循數學的邏輯推理。因此它是一門優秀的程式語言,它不僅僅在工業界被廣泛使用,在學術界也佔用很高的研究地位。
由於之前的 Java 背景,我經常拿 Scala 與 Java 這兩門語言比較。 Scala 和 Java 都基於 JVM,因此 Java 的類庫,Scala 都可以直接使用。但是我對 Scala 印象最深的點,並不是「物件導向」,而是它還擁抱「函式式」,尤其是它的「高階」。
如果我們把「物件導向」比作站在地面上觀察事物的原理,並且使用這些原理解決問題,那麼「高階」就是讓你站在山上去看待事物,對問題進行更高層次的抽象。
因此不管是解決實際問題,還是提高對程式語言的認知,Scala 都是一們值得學習的語言。
我是從《快學 Scala》這本書開始學習 Scala 的,受此書啟發,我想能不能書寫一個「從 Java 到 Scala 系列」,尋找一棵從 Java 通往 Scala 的連續的知識樹,通過對知識樹的講解,來學習 Scala。
好了,這就是本系列的第一篇,那麼我們如何談起呢?
既然 Java 和 Scala 都是「物件導向」的,那我們就來探索一下什麼是「物件導向」吧。
模板和物件
「模板」是在程式碼層面描述一類物件的「行為」或者「狀態」的程式碼,它是抽象的。如 Java 中的類,C 語言中的結構體,它們都是「模板」。
「物件」是在執行期間通過模板在記憶體中生成的一個個實體,它是具體的。如 Java 在執行期間通過 new 在記憶體中產生的實體就叫做「物件」。
如果你說共享單車,那麼它就是一個「模板」;如果你說這輛共享單車和那輛共享單車,那它們就是「物件」。
在程式碼層面,「物件」的行為可以定義為「方法」,「物件」的狀態可以定義為「屬性」,那我們如何去描述一類「物件」的方法或者屬性呢?-封裝。
例如共享單車,它有車輪,二維碼等屬性,有開鎖和關鎖等行為。那麼我們可以有三種方式來封裝共享單車。
基於物件的封裝
這種方式就是直接封裝,最典型的例子就是 C 語言中的結構體。
封裝共享單車的「模板」如下:
struct SharedBicycle{
車輪;
二維碼;
開鎖;
關鎖;
};
複製程式碼
基於類的封裝
大多數「物件導向」的語言,如 Java,Scala,C++等,都使用這種方式封裝,「模板」如下:
class SharedBicycle{
屬性:車輪;
屬性:二維碼;
方法:開鎖;
方法:關鎖;
}
複製程式碼
基於原型的封裝
JavaScript 就是使用這種封裝方式,「模板」如下:
function SharedBicycle(){
this.車輪 = xxx;
this.二維碼 = xxx;
}
//新增原型方法
SharedBicycle.prototype.開鎖 = function(){...};
SharedBicycle.prototype.關鎖 = function(){...};
複製程式碼
純物件導向
我們已經得知,可以用多種實現物件導向的不同技術,那麼什麼是純物件導向的語言呢?
我們知道 Java 是一門「物件導向」的語言,那麼在 Java 中是否真的「萬物皆物件」?
在 Java 中,我們可以寫這麼一段程式碼 int a = 3;
然後我們發現 a
並沒有封裝任何的屬性或者方法。
因此我們可以說 a
不是一個「物件」,Java 不是一門「純粹物件導向」的語言。
再看看 Scala ,不論是低階的 Int
,Double
,還是高階型別,都封裝有屬性或者方法,因此 Scala 才是一門「純粹物件導向」的語言。
那麼是什麼支援 Scala 一切皆為「物件」的呢?-Scala 的通用型別系統。
Scala 通用型別系統
頂型別
我們知道,在 Java 中,所有「物件」的「頂型別」都是 java.lang.Object
,但是 Java 卻忽略了 int
,double
等 JVM 「原始型別」,它們並沒有繼承 java.lang.Object
。
但是在 Scala 中,存在一個通用的「頂型別」- Any。
Scala 引入了Any
作為所有型別共同的頂型別。Any
是 AnyRef
和 AnyVal
的超類。
AnyRef
面向 Java(JVM)的物件世界,它對應 java.lang.Object
,是所有物件的超類。
AnyVal
則代表了 Java 的值世界,例如 int
以及其它 JVM 原始型別。
正是依賴這種繼承設計,我們才能夠使用 Any
定義方法,同時相容 scala.int
以及 java.lang.String
的例項。
class Person
val allThings = ArrayBuffer[Any]()
val myInt = 42 // Int, kept as low-level `int` during runtime
allThings += myInt // Int (extends AnyVal)
allThings += new Person() // Person (extends AnyRef), no magic here
複製程式碼
正是通過這種「通用型別系統」的設計,使得 Scala 擺脫「原始型別」這種邊緣情況的糾纏,從而實現「純粹的物件導向」。
說完了「頂型別」,我們再來看看「底型別」。
底型別
我們知道在 Java 中比較鬧心的就是異常處理,當我們呼叫一個丟擲異常的方法,我們必須丟擲或者處理異常。
但是在 Scala 中,我們知道一切表示式皆有型別,難道「拋異常」也是有型別的?
scala> val a = Try(throw new Exception("123"))
a: scala.util.Try[Nothing] = Failure(java.lang.Exception: 123)
複製程式碼
我們發現「拋異常」竟然是 Nothing
型別,在 Scala 中,難道 Nothing
僅僅是作為「拋異常」的型別?
scala> def fun(flag:Boolean)={
if(flag){
1 // Int
}else{
throw new Exception("123") //Nothing
}
}
fun: (flag: Boolean)Int
複製程式碼
我們發現 fun
函式並沒有報錯,而且返回值型別竟然是 Int
,這讓我們有一個大膽的猜測:Nothing
是 Int
的子型別。
[Int] -> ... -> AnyVal -> Any
Nothing -> [Int] -> ... -> AnyVal -> Any
複製程式碼
其實在 Scala 中, Nothing
不僅僅是 Int
的子型別,它更是所有型別的子型別。 這讓我們又產生了一個大膽的猜測:難道 Nothing
繼承了所有的型別?咳咳,這個問題我們以後在討論。
在 Scala 中,還有一個型別 Null
遵循著和 Nothing
一樣的原理。
scala> def fun2(flag:Boolean)={
if(flag){
"123" //String
}else{
null //Null
}
}
fun2: (flag: Boolean)String
複製程式碼
同理,我們可以得出 Null
是 String的子型別
[String] -> AnyRef -> Any
Null -> [String] -> AnyRef -> Any
複製程式碼
那我們看看 Null
是否可以相容 Int
。
scala> def fun3(flag:Boolean)={
if(flag){
123 //Int
}else{
null //Null
}
}
fun3: (flag: Boolean)Any
複製程式碼
我們發現 fun3
的返回值型別竟然是 Any
,說明 Null
不能相容 Scala 的「值型別」,其實從 Scala 的幫助手冊中我們就可以得出結論:Null
是所有引用型別的子型別
abstract final class Null extends AnyRef
複製程式碼
正因如此,fun3
的返回值型別才是 Any
,因為 Any
才是 AnyVal
和 AnyRef
公共的超類。
總結
本文以物件導向為引子,找到了一個 Java 和 Scala 共有的知識節點,從而引出 Scala 的通用型別系統。那麼在下一篇文章中,我們由此展開進一步思考,到底什麼是所謂的「型別」,以及 Scala 在型別方面存在哪些與 Java 不同的有趣的地方。