從 Java 到 Scala(一):物件導向談起

ScalaCool發表於2019-03-04

本文由 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 ,不論是低階的 IntDouble,還是高階型別,都封裝有屬性或者方法,因此 Scala 才是一門「純粹物件導向」的語言。

那麼是什麼支援 Scala 一切皆為「物件」的呢?-Scala 的通用型別系統。

Scala 通用型別系統

頂型別

我們知道,在 Java 中,所有「物件」的「頂型別」都是 java.lang.Object,但是 Java 卻忽略了 intdouble等 JVM 「原始型別」,它們並沒有繼承 java.lang.Object

但是在 Scala 中,存在一個通用的「頂型別」- Any。

從 Java 到 Scala(一):物件導向談起

Scala 引入了Any 作為所有型別共同的頂型別。AnyAnyRefAnyVal 的超類。

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,這讓我們有一個大膽的猜測:NothingInt 的子型別。

[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
複製程式碼

同理,我們可以得出 NullString的子型別

[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 才是 AnyValAnyRef 公共的超類。

總結

本文以物件導向為引子,找到了一個 Java 和 Scala 共有的知識節點,從而引出 Scala 的通用型別系統。那麼在下一篇文章中,我們由此展開進一步思考,到底什麼是所謂的「型別」,以及 Scala 在型別方面存在哪些與 Java 不同的有趣的地方。

Scala 型別的型別(一)。

從 Java 到 Scala(一):物件導向談起

相關文章