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