Scala 與 Java 的交互操做

這個章節主要講解Scala和Java進行互操做。java


  • Javapnode

  • 閉包

  • 異常app

  • Traitide

  • 對象函數

  • 閉包函數(closures functions)工具


Javapthis


javap是JDK附帶的一個工具,而不是JRE。它們之間仍是有差異的。Javap反編譯class文件,而且向你展現它裏面放的是什麼。使用起來很簡單。spa


[local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap MyTraitscala

Compiled from "Scalaisms.scala"

public interface com.twitter.interop.MyTrait extends scala.ScalaObject{

    public abstract java.lang.String traitName();

    public abstract java.lang.String upperTraitName();

}


若是你想了解底層的話,你能夠查看對應的字節碼


[local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap -c MyTrait\$class

Compiled from "Scalaisms.scala"

public abstract class com.twitter.interop.MyTrait$class extends java.lang.Object{

public static java.lang.String upperTraitName(com.twitter.interop.MyTrait);

  Code:

   0: aload_0

   1: invokeinterface #12,  1; //InterfaceMethod com/twitter/interop/MyTrait.traitName:()Ljava/lang/String;

   6: invokevirtual #17; //Method java/lang/String.toUpperCase:()Ljava/lang/String;

   9: areturn

 

public static void $init$(com.twitter.interop.MyTrait);

  Code:

   0: return

 

}


若是你在Java平臺上有什麼問題,你能夠經過javap來排查。



從Java的角度來使用Scala的_class_須要注意的四個要點以下:


  • 類參數

  • 類常量

  • 類變量

  • 異常


咱們來建立一個簡單的scala類來展現這幾個要點


package com.twitter.interop

 

import java.io.IOException

import scala.throws

import scala.reflect.{BeanProperty, BooleanBeanProperty}

 

class SimpleClass(name: String, val acc: String, @BeanProperty var mutable: String) {

  val foo = "foo"

  var bar = "bar"

  @BeanProperty

  val fooBean = "foobean"

  @BeanProperty

  var barBean = "barbean"

  @BooleanBeanProperty

  var awesome = true

 

  def dangerFoo() = {

    throw new IOException("SURPRISE!")

  }

 

  @throws(classOf[IOException])

  def dangerBar() = {

    throw new IOException("NO SURPRISE!")

  }

}


類參數


  • 默認狀況下,類參數實際上就是Java裏構造函數的參數。這就意味着你不能在這個class以外訪問它們。


  • 把類參數定義成一個val/var的方式和下面的代碼相同


class SimpleClass(acc_: String) {

  val acc = acc_

}


下面就能夠經過Java代碼來訪問它們了。


常量(Val)


  • 常量(val)都會定義有對應的供Java代碼訪問的方法。你能夠經過」foo()」方法來取得常量(val)「foo」的值。


變量(Var)


  • 變量(var)會多定義一個_$eq方法。你能夠這樣調用來設置變量的值:


foo$_eq("newfoo");


BeanFactory


你能夠經過@BeanProperty註解來標註val和var。這樣就會生成相似於POJO的getter/setter方法。假如你想要訪問isFoo變量,使用BooleanBeanProperty註解。那麼難以理解的foo$eq就能夠換成:


setFoo("newfoo");

getFoo();


異常


Scala裏沒有受檢異常(checked exception),可是Java裏有。這是一個語言層面上的問題,咱們這裏不進行討論,可是在Java裏你對異常進行捕獲的時候你仍是要注意的。dangerFoo和dangerBar的定義裏對這進行了示範。在Java裏,你不能這樣作。


// exception erasure!

// 異常擦除!

try {

    s.dangerFoo();

} catch (IOException e) {

    // UGLY

    // 很是醜陋

}


Java編譯器會由於s.dangerFoo不會拋出IOException而報錯。咱們能夠經過捕獲Throwable來繞過這個錯誤,可是這樣作沒多大用處。


不過,做爲一個Scala用戶,比較正式的方式是使用throws註解,就像咱們以前在dangerBar上的同樣。這個手段使得咱們可以使用Java裏的受檢異常(checked exception)。


延伸閱讀


支持Java互操做的註解的完整列表在http://www.scala-lang.org/node/106。


Trait


咱們怎樣能夠獲得一個接口和對應的實現呢?咱們簡單看看trait的定義


trait MyTrait {

  def traitName:String

  def upperTraitName = traitName.toUpperCase

}


這個trait有一個抽象的方法(traitName)和一個已實現的方法(upperTraitName)。對於這樣的trait,Scala會生成什麼樣的代碼呢?它會生成一個MyTrait接口,同時還會生成對應的實現類MyTrait$class。


MyTrait的實現和你猜測的差很少:


[local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap MyTrait

Compiled from "Scalaisms.scala"

public interface com.twitter.interop.MyTrait extends scala.ScalaObject{

    public abstract java.lang.String traitName();

    public abstract java.lang.String upperTraitName();

}


不過MyTrait$class的實現更加有趣:


[local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap MyTrait\$class

Compiled from "Scalaisms.scala"

public abstract class com.twitter.interop.MyTrait$class extends java.lang.Object{

    public static java.lang.String upperTraitName(com.twitter.interop.MyTrait);

    public static void $init$(com.twitter.interop.MyTrait);

}


MyTrait$class類只有一個靜態的接受一個MyTrait實例做爲參數的方法。這樣給了咱們在Java如何實現trait的一條線索。


首先要作的是:


package com.twitter.interop;

 

public class JTraitImpl implements MyTrait {

    private String name = null;

 

    public JTraitImpl(String name) {

        this.name = name;

    }

 

    public String traitName() {

        return name;

    }

}


而後咱們會獲得下面的錯誤:


[info] Compiling main sources...

[error] /Users/mmcbride/projects/interop/src/main/java/com/twitter/interop/JTraitImpl.java:3: com.twitter.interop.JTraitImpl is not abstract and does not override abstract method upperTraitName() in com.twitter.interop.MyTrait

[error] public class JTraitImpl implements MyTrait {

[error]        ^


咱們_能夠_本身來實現他們。不過還有一種更加詭異的方式。


package com.twitter.interop;

 

    public String upperTraitName() {

        return MyTrait$class.upperTraitName(this);

    }


咱們只須要把相應的方法調用代理到Scala的實現上。而且咱們還能夠根據實際須要進行重寫。


對象


在Scala裏,是用對象來實現靜態方法和單例模式的。若是在Java裏使用它們就會顯得比較怪。在語法上沒有什麼比較優雅的方式來使用它們,可是在Scala 2.8裏就沒有那麼麻煩了。


Scala對象會被編譯成一個名稱帶有「$」後綴的類。咱們來建立一個類以及對應的對象(Object)。咱們來建立一個類以及對應的伴生對象(companion object)。


class TraitImpl(name: String) extends MyTrait {

  def traitName = name

}

 

object TraitImpl {

  def apply = new TraitImpl("foo")

  def apply(name: String) = new TraitImpl(name)

}


咱們能夠經過下面這種奇妙的方式在Java裏訪問它:


MyTrait foo = TraitImpl$.MODULE$.apply("foo");


如今你也許會問本身,這到底是神馬?這是一個很正常的反應。咱們如今一塊兒來看看TraintImpl$內部到底是怎麼實現的。


local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap TraitImpl\$

Compiled from "Scalaisms.scala"

public final class com.twitter.interop.TraitImpl$ extends java.lang.Object implements scala.ScalaObject{

    public static final com.twitter.interop.TraitImpl$ MODULE$;

    public static {};

    public com.twitter.interop.TraitImpl apply();

    public com.twitter.interop.TraitImpl apply(java.lang.String);

}


其實它裏面沒有任何靜態方法。相反,它還有一個靜態成員叫作MODULE$。實際上方法的調用都是代理到這個成員變量上的。這種實現使得訪問起來以爲比較噁心,可是若是你知道怎麼使用MODULE$的話,其實仍是很實用的。


轉發方法(Forwarding Method)


在Scala 2.8裏,處理Object會比較簡單點。若是你有一個類以及對應的伴生對象(companion object),2.8 的編譯器會在伴生對象裏生成轉發方法。若是使用2.8的編譯器進行構建,那麼你能夠經過下面的方法來訪問TraitImpl對象:


MyTrait foo = TraitImpl.apply("foo");


閉包函數


Scala最重要的一個特色就是把函數做爲一等公民。咱們來定義一個類,它裏面包含一些接收函數做爲參數的方法。


class ClosureClass {

  def printResult[T](f: => T) = {

    println(f)

  }

 

  def printResult[T](f: String => T) = {

    println(f("HI THERE"))

  }

}


在Scala裏我能夠這樣調用:


val cc = new ClosureClass

cc.printResult { "HI MOM" }


可是在Java裏卻沒有這麼簡單,不過也沒有想象的那麼複雜。咱們來看看ClosureClass最終到底編譯成怎樣:


[local ~/projects/interop/target/scala_2.8.1/classes/com/twitter/interop]$ javap ClosureClass

Compiled from "Scalaisms.scala"

public class com.twitter.interop.ClosureClass extends java.lang.Object implements scala.ScalaObject{

    public void printResult(scala.Function0);

    public void printResult(scala.Function1);

    public com.twitter.interop.ClosureClass();

}


這個看起來也不是很可怕。」f: => T」 轉換成」Function0″,」f: String => T」 轉換成 「Function1″。Scala定義了從Function0到Function22,一直支持到22個參數。這麼多確實已經足夠了。


如今咱們只須要弄清楚,怎麼在Java去實現這個功能。事實上,Scala提供了AbstractFunction0和AbstractFunction1,咱們能夠這樣來傳參:


@Test public void closureTest() {

    ClosureClass c = new ClosureClass();

    c.printResult(new AbstractFunction0() {

            public String apply() {

                return "foo";

            }

        });

    c.printResult(new AbstractFunction1() {

            public String apply(String arg) {

                return arg + "foo";

            }

        });

}


注意咱們還可使用泛型來參數化參數的類型。

相關文章
相關標籤/搜索