標籤:
1. 在Scala裡使用Scala類
如果在單獨的檔案裡建立Scala類,就可以輕鬆地使用它們,就像(無需顯式編譯)在Scala指令碼裡使用一樣。不過,如果想在編譯過的Scala或Java代碼裡使用Scala類,那就必須編譯了。
舉例如下:
package com.cn.pengclass Person(val firstName:String, val lastName:String) { override def toString():String = firstName + " " + lastName}
package com.cn.pengclass Dog(name:String) { override def toString():String = name}
下面是使用上面兩個類的指令碼:
package com.cn.pengobject use { def main(args:Array[String]){ val george = new Person("George", "Washington") val geogesDogs = List(new Dog("Captain"), new Dog("Clode"), new Dog("Forester"), new Dog("Searcher")) printf("%s had several dogs %s...", george,geogesDogs mkString ", ") } }
指令碼會產生如下輸出:
George Washington had several dogs Captain, Clode, Forester, Searcher...
2. 在Scala裡使用Java類
在Scala裡可以直接使用Java類。如果要用的Java類是標準JDK的一部分,直接用就是了。如果它不在java.lang裡,就要匯入類的包。下面用到了java.util和java.lang.reflect包:
package com.cn.pengimport java.util.Dateimport java.lang.reflect._object UseJDKClasses extends App { println("Today is " + new Date()) val methods = getClass.getMethods() methods.foreach{method:Method => println(method.getName())}}
程式運行結果如下:
Today is Sat Apr 04 16:53:55 CST 2015maindelayedEndpoint$com$cn$peng$UseJDKClasses$1argsdelayedInitscala$App$_setter_$executionStart_$eqexecutionStartscala$App$$_argsscala$App$$initCodemethodsscala$App$$_args_$eqscala$App$_setter_$scala$App$$initCode_$eqwaitwaitwaitequalstoStringhashCodegetClassnotifynotifyAll
如果想用的Java類是你自己建立的,或是來自第三方,請確保scalac的classpath指向位元組碼的位置。假定我們有如下的Java檔案:
package investments;public enum InvestmentType { SHORT_TERM, BOND, STOCK, REAL_ESTATE, COMMODITIES, COLLECTIBLES, MUTUAL_FUNDS}
package investments;public class Investment { private String investmentName; private InvestmentType investmentType; public Investment(String name, InvestmentType type){ investmentName = name; investmentType = type; } public int yield() { return 0; }}
在Scala代碼裡使用這些類,同使用Scala類一樣。下面是一個在Scala裡建立Investment執行個體的例子:
package com.cn.pengimport investments._object UseInvestment { def main(args: Array[String]){ val investment = new Investment("XYZ Corporation", InvestmentType.STOCK) println(investment.getClass()) }}
運行結果如下:
class investments.Investment
Investment類的yield()方法需要小心使用。如果Java代碼有方法或欄位的名字(比如trait或yield等)於Scala的關鍵字衝突,調用它們會導致Scala編譯器死掉。比如,下面的代碼是不行的:
val theYield1 = investment.yield //ERROR val theYield2 = investment.yield() //ERROR
幸運的是,Scala提供了一個解決方案。把衝突的變數/方法放到反引號裡,就可以繞開這個問題。改一下代碼就可以讓上面的兩個調用工作了:
val theYield1 = investment.`yield` val theYield2 = investment.`yield`()
3. 在Java裡使用Scala類
Scala提供了與Java之間完整的雙向互通性。因為Scala能編譯成位元組碼,所以在Java裡使用Scala類相當容易。預設情況下,Scala並不遵循JavaBean的約定,要用@scala.reflect.BeanProperty這個註解產生符合JavaBean預定的getter和setter。還可以從Scala類繼承Java類,不過,要運行使用了Scala類的Java代碼,classpath裡需要有scala-library.jar。在本節裡,我們會看到Scala的構造在Java端會表現出怎樣的不同。
3.1 有普通函數和高階函數的Scala類
遵循標準Java構造的Scala類相當直白,在Java端使用它們很容易。我們寫一個Scala類:
package automobilesclass Car(val year:Int) { private[this] var miles : Int = 0 def drive(distance:Int) {miles += distance} override def toString():String = "year: " + year + " miles: " + miles}
下面是個使用這個Scala類的Java類:
package com.cn.peng;import automobiles.Car;public class UseCar { public static void main(String[] args){ Car car = new Car(2014); System.out.println(car); car.drive(10); System.out.println(car); }}
程式運行結果如下:
year: 2014 miles: 0year: 2014 miles: 10
在Java裡使用Scala類相當簡單。不過,不是所有的Scala類都那麼友善。比如,如果Scala類有方法接收閉包,這些方法在Java裡就不可用,因為Java目前尚不支援閉包。下面Equipment類的simulate()方法對Java就是停用;不過,我們可以用run()方法:
package com.cn.pengclass Equipment { //Not usable from Java def simulate(input:Int)(calculator:Int => Int):Int = { //... calculator(input) } def run(duration:Int){ println("running") //... }}
因此,設計API的時候,如果類主要是給Java用,請在提供高階函數的同時也提供普通函數,讓這個類對Java完全可用。
3.2 同trait一起工作
沒有方法實現的trait在位元組碼層面上就是簡單的介面。Scala不支援interface關鍵字。因此,如果想在Scala裡建立介面,就建立一個沒有實現的trait。下面是個Scala trait的例子,他也是個介面:
package com.cn.pengtrait Writable { def wirte(message: String):Unit}
上面的trait裡面有個抽象的方法,混入這個trait的類都應該實現這個方法。在Java端,Writable可以看做與其它介面一樣;它對Scala根本沒有依賴。所以,可以這樣實現(implement)它:
package com.cn.peng;public class AWritableJavaClass implements Writable{ @Override public void wirte(String message) { // TODO Auto-generated method stub }}
不過,如果trait有方法實現,那麼Java類就不能實現這個trait/interface,雖然它們可以使用它。因此,在Java裡不能實現下面的Printable,但可以持有一個Printable的引用:
package com.cn.pengtrait Printable { def print(){} //default print nothing}
如果想讓Java類實現trait,就讓它純粹些;換句話說,不要有實現。在這種情況下,任何公用的實現都應該放到抽象基類裡,而不是trait裡。不過,如果只是想讓Java類使用trait,就沒有任何限制。
3.3 單例對象和伴生對象
Scala將對象(單例對象或伴生對象)編譯成一個“單例類”——這個類的名字末尾有一個特殊$符。這樣,下面所示的Object Single,會產生一個類名Single$。不過,Scala處理單例對象和伴生對象有些不同,稍後可以看到。
Scala把單例對象編譯到一個單例類(它用的是Java的靜態方法)中,此外,還會建立一個普通的類,它把調用傳遞給單例類。所以,下面這段代碼建立了一個單例對象Single,而Scala則建立了兩個類:Single$和用來傳遞調用的類Single。
package com.cn.pengobject Single { def greet(){println("Hello from Single")}}
在Java裡使用上面的單例對象,就像使用由static方法的Java類一樣,如下所示:
package com.cn.peng;public class SingleUser { public static void main(String[] args){ Single.greet(); }}
上面代碼的輸出如下:
Hello from Single
如果對象是同名類的伴生對象,Scala會建立兩個類,一個類表示Scala類(下面例子裡的Buddy),另一個類表示伴生對象(下面例子裡的Buddy$):
package com.cn.pengclass Buddy { def greet(){println("Hello from Buddy class")}}object Buddy { def greet(){println("Hello from Buddy object")}}
訪問伴生類可以直接使用類的名字。訪問伴生對象需要使用特殊的符號MODULE$,如下例所示:
package com.cn.peng;public class BuddyUser { public static void main(String[] args){ new Buddy().greet(); Buddy$.MODULE$.greet(); }}
輸出如下:
Hello from Buddy classHello from Buddy object
4. 繼承類
Scala類可以繼承Java類,反之亦然。大多數情況下,這應該夠用了。之前也討論過,如果方法接收閉包作為參數,重寫起來就有些麻煩。異常也是這個問題。
Scala沒有throws字句。在Scala裡,任意方法都可以拋出異常,無需顯式聲明成方法簽名的一部分。不過,如果在Java裡重寫這樣的方法,試圖拋出異常,就會陷入麻煩。看個例子,假設Scala定義了Bird:
package com.cn.pengabstract class Bird { def fly(); //...}
還有另一個類Ostrich:
package com.cn.pengclass Ostrich extends Bird { def fly(){ throw new NoFlyException } //...}
其中NoFlyException定義如下:
package com.cn.pengclass NoFlyException extends Exception{}
在上面的代碼裡,Ostrich的fly()拋出異常沒有任何問題。不過,如果要在Java裡實現一個不能飛的鳥,就會有麻煩,如下所示:
package com.cn.peng;public class Penguin extends Bird{ public void fly() throws NoFlyException { throw new NoFlyException(); } //...}
首先,如果只是拋出異常,Java會報錯“unreported exception NoFlyException;must be caught or declared to be thrown." 一旦加上了throws子句,java又會報錯”Exception NoFlyException is not compatible with throws clause in Bird.fly()“。
即便Scala很靈活,並不強求一定要指定拋出哪些異常,但是要想在Java裡繼承這些方法,就要告訴Scala編譯器,把這些細節記錄在方法簽名裡。Scala為此提供了一個後門:定義@throws註解。
雖然Scala支援註解,但它卻不提供註解的文法。如果想建立自己的註解,就不得不用Java來做。@throws是已經提供好的註解,用以表示方法拋出的受控異常。這樣,對我們來說,要在Java裡實現Penguin,必須在把Bird改成這樣:
package com.cn.pengabstract class Bird { @throws(classOf[NoFlyException]) def fly(); //...}
現在,編製上面的代碼,Scala編譯器會在位元組碼裡為fly()方法放上必要的簽名。經過了這個修改,Java類Penguin就可以正常編譯了。
與Java互操作