官术网_书友最值得收藏!

1.3 為什么要用Scala

Scala究竟是不是你的菜?這個問題需要你自己觀察和判斷。我們發現除了伸縮性,其實還有很多因素讓人喜歡Scala編程。本節將介紹其中最重要的四點:兼容性、精簡性、高級抽象和靜態類型。

Scala是兼容的

從Java到Scala,Scala并不需要你從Java平臺全身而退。它允許你對現有的代碼增加價值(在現有基礎之上“添磚加瓦”),這得益于它的設計目標就是與Java的無縫互調。[9]Scala程序會被編譯成JVM字節碼,其運行期性能通常也與Java程序相當。Scala代碼可以調用Java方法,訪問Java字段,從Java類繼承,實現Java接口。要實現這些,并不需要特殊的語法、顯式的接口描述或膠水代碼glue code)。事實上,幾乎所有Scala代碼都重度使用Java類庫,而程序員們通常察覺不到這一點。

關于互操作性還有一點要說明,那就是Scala也重度復用了Java的類型。Scala的Int是用Java的基本類型int實現的,Float是用Java的float實現的,Boolean是用Java的boolean實現的,等等。Scala的數組也被映射成Java的數組。Scala還復用了Java類庫中很多其他類型,比如,Scala的字符串字面量"abc"是一個java.lang.String,而拋出的異常也必須是java.lang.Throwable的子類。

Scala不僅會復用Java的類型,也會對Java原生的類型進行“再包裝”,讓這些類型更好用。比如,Scala的字符串支持toInttoFloat這樣的方法,可以將字符串轉換成整數或浮點數。這樣就可以寫為str.toInt而不是Integer.parseInt(str)。如何在不打破互操作性的前提下實現呢?Java的String類當然沒有toInt方法了!事實上,Scala對于此類因高級類庫設計和互操作性之間的矛盾而產生的問題有一個非常通用的解決方案:Scala允許定義豐富的擴展,當代碼中選中了(類型定義中)不存在的成員時,擴展方法的機制就會被啟用。[10]在上述示例中,Scala首先在字符串的類型定義上查找toInt方法,而String類定義中并沒有toInt這個成員(方法),不過它會找到一個將Java的String轉換成Scala的StringOps類的隱式轉換,StringOps類定義了這樣一個成員(方法)。因此在真正執行toInt操作之前,上述隱式轉換就會被應用。

我們也可以從Java中調用Scala的代碼。具體的方式有時候比較微妙,因為就編程語言而言,Scala比Java的表達更豐富,所以Scala的某些高級特性需要加工后才能映射到Java。更多細節請參考《Scala高級編程》[11]

Scala是精簡的

Scala編寫的程序通常都比較短。很多Scala程序員都表示,與Java相比,代碼行數相差可達10倍之多。更為保守地估計,一個典型的Scala程序的代碼行數應該只有用Java編寫的、同樣功能的程序的代碼行數的一半。更少的代碼不僅僅意味著打更少的字,也讓閱讀和理解代碼更快,缺陷也更少。更少的代碼行數,歸功于如下幾個因素。

首先,Scala的語法避免了Java程序中常見的一些樣板(boilerplate)代碼。比如,在Scala中分號是可選的,通常大家也不寫分號。Scala的語法噪音更少還體現在其他幾個方面,比如,可以比較一下分別用Java和Scala來編寫類和構造方法。Java的類和構造方法通常類似這樣:

而在Scala中,你可能更傾向于寫成如下的樣子:

對于這段代碼,Scala解釋器會生成帶有兩個私有實例變量(一個名稱為indexInt和一個名稱為nameString)和一個接收這兩個變量初始值的參數的構造方法的類。這個構造方法會用傳入的參數值來初始化它的兩個實例變量。簡單來說,用更少的代碼做到了與Java本質上相同的功能。[12]Scala的類寫起來更快,讀起來更容易,而最重要的是,它比Java的類出錯的可能性更小。

Scala的類型推斷是讓代碼精簡的另一個幫手。重復的類型信息可以被刪除,這樣代碼就更加緊湊、可讀。

不過可能最重要的因素是有些代碼根本不用寫,類庫都幫你寫好了。Scala提供了大量的工具來定義功能強大的類庫,讓你可以捕獲那些公共的行為,并將它們抽象出來。例如,類庫中各種類型的不同切面可以被分到不同的特質中,然后以各種靈活的方式組裝、混合在一起。又如,類庫的方法也可以接收用于描述具體操作的參數,這樣一來,事實上你就可以定義自己的控制結構。綜上所述,Scala讓我們能夠定義出抽象級別高,同時用起來又很靈活的類庫。

Scala是高級的

程序員們一直都在應對不斷增加的復雜度。要保持高效的產出,就必須理解當前處理的代碼。許多走下坡路的軟件項目都是因為受到過于復雜的代碼的影響。不幸的是,重要的軟件通常需求都比較復雜。這些復雜度并不能被簡單地規避,必須對其進行妥善的管理。

Scala給你的幫助在于提升接口設計的抽象級別,讓你更好地管理復雜度。舉例來說,假設你有一個String類型的變量name,你想知道這個String是否包含大寫字母。在Java 8之前,你可能會編寫這樣一段代碼:

而在Scala中,你可以這樣寫:

Java代碼將字符串當作低級別的實體,在循環中逐個字符地遍歷。而Scala代碼將同樣的字符串當作更高級別的字符序列,用前提predicate)來查詢。很顯然,Scala代碼要短得多,并且(對于受過訓練的雙眼來說)更加易讀。因此,Scala對整體復雜度預算的影響較小,讓你犯錯的機會也更少。

這里的前提_.isUpper是Scala的函數字面量。[13]它描述了一個接收字符作為入參(以下畫線表示),判斷該字符是否為大寫字母的函數。[14]

Java 8引入了對lambdastream)的支持,讓你能夠在Java中執行類似的操作。具體代碼如下:

雖然與之前版本的Java相比有了長足的進步,但是Java 8的代碼依然比Scala代碼更啰唆。Java代碼這種額外的“重”,以及Java長期以來形成的使用循環的傳統,讓廣大Java程序員們雖然可以使用exists這樣的新方法,但是最終都選擇直接寫循環,并安于這類更復雜代碼的存在。

另一方面,Scala的函數字面量非常輕,因此經常被使用。隨著對Scala的深入了解,你會找到越來越多的機會定義自己的控制抽象。你會發現,這種抽象讓你避免了很多重復代碼,讓你的程序保持短小、清晰。

Scala是靜態類型的

靜態的類型系統根據變量和表達式所包含和計算的值的類型來對它們進行歸類。Scala與其他語言相比,一個重要的特點是它擁有非常先進的靜態類型系統。Scala不僅擁有與Java類似的、允許嵌套類的類型系統,還允許用泛型generics)對類型進行參數化parameterize),用交集intersection)組合類型,以及用抽象類型abstract type)隱藏類型的細節。[15]這些特性為我們構建和編寫新的類型打下了堅實的基礎,讓我們可以設計出既安全又好用的接口。

如果你喜歡動態語言,如Perl、Python、Ruby或Groovy,那么也許會覺得奇怪,我們為什么把靜態類型系統當作Scala的強項。畢竟,我們常聽到有人說,沒有靜態類型檢查是動態語言的一個主要優勢。對靜態類型最常見的批評是程序因此變得過于冗長繁復,讓程序員不能自由地表達他們的意圖,也無法實現對軟件系統的某些特定的動態修改。不過,這些反對的聲音并不是籠統地針對靜態類型這個概念本身的,而是針對特定的類型系統的,人們覺得這些類型系統過于啰唆,或者過于死板。舉例來說,Smalltalk的發明人Alan Kay曾經說過:“我并不是反對(靜態)類型,但我并不知道哪個(靜態)類型系統用起來不是一種折磨,因此我仍喜歡動態類型。”[16]

通過本書,我們希望讓你相信Scala的類型系統并不是“折磨”。事實上,它很好地解決了靜態類型的兩個常見的痛點:通過類型推斷規避了過于啰唆的問題,通過模式匹配,以及其他編寫和組合類型的新方式避免了死板。掃清了這些障礙,大家就能更好地理解和接受靜態類型系統的好處。其中包括:程序抽象的可驗證屬性、安全的重構和更好的文檔。

程序抽象的可驗證屬性。靜態類型系統可以證明某類運行期錯誤不可能發生。例如,它可以證明:布爾值不能和整數相加;私有變量不能從其所屬的類之外被訪問;函數調用時的入參個數不會錯;字符串的集只能添加字符串。

目前的靜態類型系統也有一些無法被檢測到的錯誤。比如,不會自動終止的函數、數組越界或除數為0等。它也不能檢查你的程序是不是滿足它的規格說明書(假設確實有規格說明書的話)。有人據此認為靜態類型系統實際上沒什么用。他們說,既然這樣的類型系統只能檢測出簡單的錯誤,而單元測試提供了更廣的測試覆蓋范圍,為什么還要用靜態類型系統檢查呢?我們認為這些說法沒有抓住問題的本質。雖然靜態類型系統不可能完全取代單元測試,但是它能減小單元測試需要覆蓋的范圍,因為對于那些常規屬性的檢查,靜態類型系統已經幫我們做了。不過,正如Edsger Dijkstra所說,測試只能證明錯誤存在,而不能證明沒有錯誤。[17]因此,雖然靜態類型帶來的保障可能比較簡單,但是這些是真正的保障,不是單元測試能夠提供的。

安全的重構。靜態類型系統提供了一個安全網,讓你有十足的信心和把握對代碼庫進行修改。假設我們要對方法添加一個額外的參數,如果是靜態類型語言,則可以執行修改,重新編譯,然后簡單地訂正那些引起編譯錯誤的代碼行即可。一旦完成了這些修改和訂正,我們就能確信所有需要改的地方都改好了。其他很多簡單的重構也是如此,比如,修改方法名或者將方法從一個類移到另一個類。在所有這些場景里,靜態類型檢查足以確保新系統會像老系統那樣運行起來。

更好的文檔。靜態類型是程序化的文檔,解釋器會檢查其正確性。與普通的文檔不同,類型標注永遠不會過時(主要包含類型標注的源代碼通過了編譯)。不僅如此,解釋器和集成開發環境(IDE)也可以利用類型標注來提供更好的上下文相關的幫助。比如,IDE可以通過對表達式的靜態類型判斷,查找該類型下的所有成員,將它們顯示出來,供我們選擇。

雖然靜態類型通常對程序文檔有用,但是有時候它的確比較煩人,讓程序變得雜亂無章。通常來說,有用的文檔是那些讓讀代碼的人較難僅通過代碼推斷出來的部分。比如,下面這樣的方法定義:

讓讀者知道f的參數是String,是有意義的。而在下面這個示例中,至少兩組類型標記中的一組是多余的:

很顯然,只需要說一次x是以Int為鍵,以String為值的HashMap就足夠了,不需要重復兩遍。

Scala擁有設計精良的類型推斷系統,讓你在絕大多數通常被認為冗余的地方省去類型標注或聲明。在之前的示例中,如下兩種寫法也是等效的:

Scala的類型推斷可以做得很極致。事實上,完全沒有類型標注的Scala代碼也并不少見。正因如此,Scala程序通常看上去有點像是用動態類型的腳本語言編寫的。這一點對于業務代碼來說尤其明顯,因為業務代碼通常都是將預先編寫好的組件黏合在一起的;而對于類庫組件來說就不那么適用了,因為這些組件通常都會利用那些相當精巧的類型機制來滿足各種靈活的使用模式的需要。這是很自然的一件事。畢竟,構成可復用組件的接口定義的各個成員的類型簽名必須被顯式給出,因為這些類型簽名構成了組件和組件使用者之間最基本的契約。

主站蜘蛛池模板: 西贡区| 娱乐| 凤凰县| 南城县| 板桥市| 阳高县| 庆安县| 多伦县| 额济纳旗| 邓州市| 东兴市| 汉寿县| 永顺县| 奈曼旗| 寿光市| 禹城市| 静乐县| 临洮县| 英吉沙县| 嘉黎县| 南漳县| 朝阳县| 六枝特区| 桂阳县| 蓬安县| 哈尔滨市| 舟曲县| 泰和县| 安溪县| 正宁县| 新丰县| 盈江县| 深泽县| 东安县| 贺兰县| 福建省| 石棉县| 大足县| 邮箱| 本溪市| 辽阳市|