如果你是一名Java程序員,并且關注這編程語言方面的發展,比如經常去TIOBE網站了解編程語言流行度排行,那么你應該聽說過Scala,如果你還沒有開始學習Scala,或者打算下個禮拜開始學的話,請先看看下面這篇文章,看看能不能改變你的想法。下面的內容為Programming In Scala 這本書的節選,到目前為止,國內好像還沒引進,你可以從亞馬遜上面購買http://booksites.artima.com/programming_in_scala (有國內的朋友翻譯了其中的前11章,真是非常感謝),
Scala是為你準備的嗎?你必須自己看明白并做決定。除了伸展性之外,我們發現喜歡用Scala編程實際上還有很多理由。最重要的四個將在本節討論的方面該是:兼容性,簡短,高層級抽象和高級的靜態類別。
Scala是兼容的
Scala不需要你從Java平臺后退兩步然后跳到Java語言前面去。它允許你在現存代碼中加點兒東西——在你已有的東西上建設——因為它被設計成無縫地與Java實施互操作。Scala程序會被編譯為JVM的字節碼。它們的執行期性能通常與Java程序一致。Scala代碼可以調用Java方法,訪問Java字段,繼承自Java類和實現Java接口。這些都不需要特別的語法,顯式接口描述,或粘接代碼。實際上,幾乎所有Scala代碼都極度依賴于Java庫,而經常無須在程序員意識到這點。
交互式操作的另一個方面是Scala極度重用了Java類型。Scala的Int類型代表了Java的原始整數類型int,Float代表了float,Boolean代表boolean,等等。Scala的數組被映射到Java數組。Scala同樣重用了許多標準Java庫類型。例如,Scala里的字串文本"abc"是java.lang.String,而拋出的異常必須是java.lang.Throwable的子類。
Scala不僅重用了Java的類型,還把它們“打扮”得更漂亮。例如,Scala的字串支持類似于toInt和toFloat的方法,可以把字串轉換成整數或者浮點數。因此你可以寫str.toInt替代Integer.parseInt(str)。如何在不打破互操作性的基礎上做到這點呢?Java的String類當然不會有toInt方法。實際上,Scala有一個解決這種高級庫設計和互操作性不相和諧的通用方案。Scala可以讓你定義隱式轉換:implicit conversion,這常常用在類型失配,或者選用不存在的方法時。在上面的例子里,當在字串中尋找toInt方法時,Scala編譯器會發現String類里沒有這種方法,但它會發現一個把Java的String轉換為Scala的RichString類的一個實例的隱式轉換,里面定義了這么個方法。于是在執行toInt操作之前,轉換被隱式應用。
Scala代碼同樣可以由Java代碼調用。有時這種情況要更加微妙,因為Scala是一種比Java更豐富的語言,有些Scala更先進的特性在它們能映射到Java前需要先被編碼一下。第29章說明了其中的細節。
Scala是簡潔的
Scala程序一般都很短。Scala程序員曾報告說與Java比起來代碼行數可以減少到1/10。這有可能是個極限的例子。較保守的估計大概標準的Scala程序應該有Java寫的同樣的程序一半行數左右。更少的行數不僅意味著更少的打字工作,同樣意味著更少的話在閱讀和理解程序上的努力及更少的出錯可能。許多因素在減少代碼行上起了作用。
首先,Scala的語法避免了一些束縛Java程序的固定寫法。例如,Scala里的分號是可選的,且通常不寫。Scala語法里還有很多其他的地方省略了東西。比方說,比較一下你在Java和Scala里是如何寫類及構造函數的。在Java里,帶有構造函數的類經常看上去是這個樣子:
// 在Java里
class MyClass {
private int index;
private String name;
public MyClass(int index, String name) {
this.index = index;
this.name = name;
}
}
在Scala里,你會寫成這樣:
class MyClass(index: Int, name: String)
根據這段代碼,Scala編譯器將制造有兩個私有成員變量的類,一個名為index的Int類型和一個叫做name的String類型,還有一個用這些變量作為參數獲得初始值的構造函數。這個構造函數還將用作為參數傳入的值初始化這兩個成員變量。一句話,你實際拿到了與羅嗦得多的Java版本同樣的功能。Scala類寫起來更快,讀起來更容易,最重要的是,比Java類更不容易犯錯。
有助于Scala的簡潔易懂的另一個因素是它的類型推斷。重復的類型信息可以被忽略,因此程序變得更有條理和易讀。
但或許減少代碼最關鍵的是因為已經存在于你的庫里而不需要寫的代碼。Scala給了你許多工具來定義強有力的庫讓你抓住并提煉出通用的行為。例如,庫類的不同方面可以被分成若干特質,而這些有可以被靈活地混合在一起。或者,庫方法可以用操作符參數化,從而讓你有效地定義那些你自己控制的構造。這些構造組合在一起,就能夠讓庫的定義既是高層級的又能靈活運用。
Scala是高層級的
程序員總是在和復雜性糾纏。為了高產出的編程,你必須明白你工作的代碼。過度復雜的代碼成了很多軟件工程崩潰的原因。不幸的是,重要的軟件往往有復雜的需求。這種復雜性不可避免;必須(由不受控)轉為受控。
Scala可以通過讓你提升你設計和使用的接口的抽象級別來幫助你管理復雜性。例如,假設你有一個String變量name,你想弄清楚是否String包含一個大寫字符。在Java里,你或許這么寫:
// 在Java里
boolean nameHasUpperCase = false;
for (int i = 0; i < name.length(); ++i) {
if (Character.isUpperCase(name.charAt(i))) {
nameHasUpperCase = true;
break;
}
}
在Scala里,你可以寫成:
val nameHasUpperCase = name.exists(_.isUpperCase)
Java代碼把字串看作循環中逐字符步進的低層級實體。Scala代碼把同樣的字串當作能用論斷:predicate查詢的字符高層級序列。明顯Scala代碼更短并且——對訓練有素的眼睛來說——比Java代碼更容易懂。因此Scala代碼在通盤復雜度預算上能極度地變輕。它也更少給你機會犯錯。
論斷,_.isUpperCase,是一個Scala里面函數式文本的例子。它描述了帶一個字符參量(用下劃線字符代表)的函數,并測試其是否為大寫字母。
原則上,這種控制的抽象在Java中也是可能的。為此需要定義一個包含抽象功能的方法的接口。例如,如果你想支持對字串的查詢,就應引入一個只有一個方法hasProperty的接口CharacterProperty:
// 在Java里
interface CharacterProperty {
boolean hasProperty(char ch);
}
然后你可以在Java里用這個接口格式一個方法exists:它帶一個字串和一個CharacterProperty并返回真如果字串中有某個字符符合屬性。然后你可以這樣調用exists:
// 在Java里
exists(name, new CharacterProperty {
boolean hasProperty(char ch) {
return Character.isUpperCase(ch);
}
});
然而,所有這些真的感覺很重。重到實際上多數Java程序員都不會惹這個麻煩。他們會寧愿寫個循環并漠視他們代碼里復雜性的累加。另一方面,Scala里的函數式文本真地很輕量,于是就頻繁被使用。隨著對Scala的逐步了解,你會發現越來越多定義和使用你自己的控制抽象的機會。你將發現這能幫助避免代碼重復并因此保持你的程序簡短和清晰。
Scala是靜態類型的
靜態類型系統認定變量和表達式與它們持有和計算的值的種類有關。Scala堅持作為一種具有非常先進的靜態類型系統的語言。從Java那樣的內嵌類型系統起步,能夠讓你使用泛型:generics參數化類型,用交集:intersection聯合類型和用抽象類型:abstract type隱藏類型的細節。這些為建造和組織你自己的類型打下了堅實的基礎,從而能夠設計出即安全又能靈活使用的接口。
如果你喜歡動態語言如Perl,Python,Ruby或Groovy,你或許發現Scala把它的靜態類型系統列為其優點之一有些奇怪。畢竟,沒有靜態類型系統已被引為動態語言的某些主要長處。絕大多數普遍的針對靜態類型的論斷都認為它們使得程序過度冗長,阻止程序員用他們希望的方式表達自己,并使軟件系統動態改變的某些模式成為不可能。然而,這些論斷經常針對的不是靜態類型的思想,而是指責特定的那些被意識到太冗長或太不靈活的類型系統。例如,Alan Kay,Smalltalk語言的發明者,有一次評論:“我不是針對類型,而是不知道有哪個沒有完痛的類型系統,所以我還是喜歡動態類型。”
我們希望能在書里說服你,Scala的類型系統是遠談不上會變成“完痛”。實際上,它漂亮地說明了兩個關于靜態類型通??紤]的事情(的解決方案):通過類型推斷避免了贅言和通過模式匹配及一些新的編寫和組織類型的辦法獲得了靈活性。把這些絆腳石搬掉后,靜態類型系統的經典優越性將更被賞識。其中最重要的包括程序抽象的可檢驗屬性,安全的重構,以及更好的文檔。
可檢驗屬性。靜態類型系統可以保證消除某些運行時的錯誤。例如,可以保證這樣的屬性:布爾型不會與整數型相加;私有變量不會從類的外部被訪問;函數帶了正確個數的參數;只有字串可以被加到字串集之中。
不過當前的靜態類型系統還不能查到其他類型的錯誤。比方說,通常查不到無法終結的函數,數組越界,或除零錯誤。同樣也查不到你的程序不符合式樣書(假設有這么一份式樣書)。靜態類型系統因此被認為不很有用而被忽視。輿論認為既然這種類型系統只能發現簡單錯誤,而單元測試能提供更廣泛的覆蓋,又為何自尋煩惱呢?我們認為這種論調不對頭。盡管靜態類型系統確實不能替代單元測試,但是卻能減少用來照顧那些確需測試的屬性的單元測試的數量。同樣,單元測試也不能替代靜態類型。總而言之,如Edsger Dijkstra所說,測試只能證明存在錯誤,而非不存在。因此,靜態類型能給的保證或許很簡單,但它們是無論多少測試都不能給的真正的保證。
安全的重構。靜態類型系統提供了讓你具有高度信心改動代碼基礎的安全網。試想一個對方法加入額外的參數的重構實例。在靜態類型語言中,你可以完成修改,重編譯你的系統并容易修改所有引起類型錯誤的代碼行。一旦你完成了這些,你確信已經發現了所有需要修改的地方。對其他的簡單重構,如改變方法名或把方法從一個類移到另一個,這種確信都有效。所有例子中靜態類型檢查會提供足夠的確認,表明新系統和舊系統可以一樣的工作。
文檔。靜態類型是被編譯器檢查過正確性的程序文檔。不像普通的注釋,類型標注永遠都不會過期(至少如果包含它的源文件近期剛剛通過編譯就不會)。更進一步說,編譯器和集成開發環境可以利用類型標注提供更好的上下文幫助。舉例來說,集成開發環境可以通過判定選中表達式的靜態類型,找到類型的所有成員,并全部顯示出來。
雖然靜態類型對程序文檔來說通常很有用,當它們弄亂程序時,也會顯得很討厭。標準意義上來說,有用的文檔是那些程序的讀者不可能很容易地從程序中自己想出來的。在如下的方法定義中:
def f(x: String) = ...
知道f的變量應該是String是有用的。另一方面,以下例子中兩個標注至少有一個是討厭的:
val x: HashMap[Int, String] = new HashMap[Int, String]()
很明顯,x是以Int為鍵,String為值的HashMap這句話說一遍就夠了;沒必要同樣的句子重復兩遍。
Scala有非常精于此道的類型推斷系統,能讓你省略幾乎所有的通常被認為是討厭的類型信息。在上例中,以下兩個不太討厭的替代品也能一樣工作:
val x = new HashMap[Int, String]()
val x: Map[Int, String] = new HashMap()
Scala里的類型推斷可以走的很遠。實際上,就算用戶代碼絲毫沒有顯式類型也不稀奇。因此,Scala編程經??瓷先ビ悬c像是動態類型腳本語言寫出來的程序。尤其顯著表現在作為粘接已寫完的庫控件的客戶應用代碼上。而對庫控件來說不是這么回事,因為它們常常用到相當精妙的類型去使其適于靈活使用的模式。這很自然。綜上,構成可重用控件接口的成員的類型符號應該是顯式給出的,因為它們構成了控件和它的使用者間契約的重要部分。