若 是在JVM平台上,Scala的類別會編譯為.class檔案,基本上在Java程式中,也可以使用這些編譯好的.class檔,不過由於某些Scala 的語法特性,在Java中並不存在,因此若要在Java程式中使用Scala所編譯好的類別,就必須注意一些語法或名稱對應的方式。
當你在Scala中定義一個類別:
class Some
使用scalac編譯後會產生Some.class檔案,若你想要在Java中使用這個Some類別:
public class Main { // 這是 Java
public static void main(String[] args) {
Some s = new Some();
}
}
你可以編譯Main.java,但在執行時若沒有指定Classpath中包括scala-library.jar的位置,就會出現錯誤:
>javac Main.java
>java Main
Exception in thread "main" java.lang.NoClassDefFoundError: scala/ScalaObject
....
>java Main
Exception in thread "main" java.lang.NoClassDefFoundError: scala/ScalaObject
....
這是因為,Scala中的自定義類別,會實作scala.ScalaObject特徵,也就是編譯出來的.class,其實相當於:
import java.rmi.RemoteException;
import scala.ScalaObject;
public class Some implements ScalaObject {
public int \$tag() throws RemoteException {
return ScalaObject.class.\$tag(this);
}
}
import scala.ScalaObject;
public class Some implements ScalaObject {
public int \$tag() throws RemoteException {
return ScalaObject.class.\$tag(this);
}
}
若要能順利執行,則必須指定Classpath包括scala-library.jar的位置:
>java -cp .;%SCALA_HOME%/lib/scala-library.jar Main
在定義類別時,使用val宣告的變數,對應至Java使用final修飾的變數。如果是類別的資料成員,無論是使用val宣告或var宣告的變數,一律對應至private資料成員,在Scala中提供的權限修飾,對應至相對應的存取方法權限。例如:
class Some {
private var m = 123
private val s = "XD"
}
編譯過後的.class會是:
import java.rmi.RemoteException;
import scala.ScalaObject;
public class Some implements ScalaObject {
private int m;
private final String s = "XD"
public Some() {
super();
m = 123;
}
private void m_\$eq(int x\$1) { m = x\$1; }
private int m() { return m; }
private String s() { return s; }
public int \$tag() throws RemoteException {
return scala.ScalaObject.class.\$tag(this);
}
}
import scala.ScalaObject;
public class Some implements ScalaObject {
private int m;
private final String s = "XD"
public Some() {
super();
m = 123;
}
private void m_\$eq(int x\$1) { m = x\$1; }
private int m() { return m; }
private String s() { return s; }
public int \$tag() throws RemoteException {
return scala.ScalaObject.class.\$tag(this);
}
}
所以,如果是這個類別:
class Some {
val s = "XD"
}
在Java中要取得s的值,則必須呼叫方法,例如:
public class Main { // 這是 Java
public static void main(String[] args) {
Some some = new Some();
System.out.println(some.s());
}
}
如果你希望編譯過後的類別具有設值方法(Setter)與取值方法(Getter),則可以使用 @BeanProperty 標註,請參考 標註(Annotation) 的內容。
在Scala中所有東西都是物件,就算是1、0.1等數值也是物件,而在編譯為.class之後,為了效率,會儘量將數值轉為Java的基本資料型態,而在必要的地方使用Integer等包裹類別(Wrapper)。例如:
class Some {
def doIt(a: Int): Int = {
val l = new java.util.ArrayList[Int]
l.add(a)
return l.get(0)
}
}
這個類別在編譯為.class之後,相當於:
import java.rmi.RemoteException;
import java.util.ArrayList;
import scala.ScalaObject;
import scala.runtime.BoxesRunTime;
public class Some implements ScalaObject {
public int doIt(int a) {
ArrayList l = new ArrayList();
l.add(BoxesRunTime.boxToInteger(a));
return BoxesRunTime.unboxToInt(l.get(0));
}
public int \$tag() throws RemoteException {
return ScalaObject.class.\$tag(this);
}
}
import java.util.ArrayList;
import scala.ScalaObject;
import scala.runtime.BoxesRunTime;
public class Some implements ScalaObject {
public int doIt(int a) {
ArrayList l = new ArrayList();
l.add(BoxesRunTime.boxToInteger(a));
return BoxesRunTime.unboxToInt(l.get(0));
}
public int \$tag() throws RemoteException {
return ScalaObject.class.\$tag(this);
}
}
在 參數與傳回值的型態部份轉為int,而必要的地方使用裝箱(Boxing)與拆箱(Unboxing)。在Scala中如果設定參數型態或傳回值型態為 Any、AnyRef或AnyVal,編譯.class後,都對應於java.lang.Object。如果傳回值設定為AnyVal,則傳回的數值會裝 箱,例如:
class Some {
def doSomething(a: AnyVal): AnyVal = 1
}
這個類別在編譯過後,.class的定義基本上如下:
import java.rmi.RemoteException;
import scala.ScalaObject;
import scala.runtime.BoxesRunTime;
public class Some implements ScalaObject {
public Some() {}
public Object doSomething(Object a) {
return BoxesRunTime.boxToInteger(1);
}
public int \$tag() throws RemoteException {
return scala.ScalaObject.class.\$tag(this);
}
}
import scala.ScalaObject;
import scala.runtime.BoxesRunTime;
public class Some implements ScalaObject {
public Some() {}
public Object doSomething(Object a) {
return BoxesRunTime.boxToInteger(1);
}
public int \$tag() throws RemoteException {
return scala.ScalaObject.class.\$tag(this);
}
}
Scala的 單 例物件 在Java中,可以使用存取靜態成員的方式來使用,例如:
object Some {
def doSomething = "XD"
}
在Java中可以這麼使用:
public class Main { // 這是 Java
public static void main(String[] args) {
System.out.println(Some.doSomething()); // XD
}
}
不過別誤以為單例物件轉換為Java之後,單例物件的名稱就是類別名稱,其中的成員直接就是實作為靜態成員!不然會誤入這個陷阱:
class Some
object Some {
def doSomething = "XD"
}
上面定義了伴侶類別與伴侶物件,在編譯過後,在Java中這麼使用是會有錯的:
public class Main { // 這是 Java
public static void main(String[] args) {
System.out.println(Some.doSomething()); // 編譯錯誤
}
}
其實,只要是Scala中使用object定義的物件,在轉換為.class後,其實真正的類別名稱是「單例物件名\$」,該類別中會有個MODULE\$成員參考到this,例如上面的例子會產生Some\$類別,其定義為:
import java.rmi.RemoteException;
import scala.ScalaObject;
public final class Some\$ implements ScalaObject {
public Some\$() {}
public String doSomething() {
return "XD";
}
public int \$tag() throws RemoteException {
return scala.ScalaObject.class.\$tag(this);
}
public static final Some\$ MODULE\$ = this;
static {
new Some\$();
}
}
import scala.ScalaObject;
public final class Some\$ implements ScalaObject {
public Some\$() {}
public String doSomething() {
return "XD";
}
public int \$tag() throws RemoteException {
return scala.ScalaObject.class.\$tag(this);
}
public static final Some\$ MODULE\$ = this;
static {
new Some\$();
}
}
在沒有伴侶類別的情況下,編譯會產生一個Some類別定義如下:
import java.rmi.RemoteException;
public final class Some {
public static final String doSomething() {
return Some\$.MODULE\$.doSomething();
}
public static final int \$tag() throws RemoteException {
return Some\$.MODULE\$.\$tag();
}
}
public final class Some {
public static final String doSomething() {
return Some\$.MODULE\$.doSomething();
}
public static final int \$tag() throws RemoteException {
return Some\$.MODULE\$.\$tag();
}
}
這也是為何你可以在Java中使用Some.doSometing()的原因。然而在有伴侶類別的情況下,所產生的Some類別定義會是:
import java.rmi.RemoteException;
import scala.ScalaObject;
public class Some implements ScalaObject {
public Some() {}
public int \$tag() throws RemoteException {
return scala.ScalaObject.class.\$tag(this);
}
}
import scala.ScalaObject;
public class Some implements ScalaObject {
public Some() {}
public int \$tag() throws RemoteException {
return scala.ScalaObject.class.\$tag(this);
}
}
這也是為什麼,你不可以在Java中使用Some.doSometing()的原因。無論是否有伴侶類別,其實想要在Java中使用Scala單例物件中定義的成員,方式都是:
public class Main { // 這是 Java
public static void main(String[] args) {
Some\$.MODULE\$.doSomething();
}
}
在Scala中若設定建構式為private或其它權限,例如:
class Some private {}
編譯為.class後,基本上建構式也會被標示為相對應的權限,例如.class的定義會是:
import java.rmi.RemoteException;
import scala.ScalaObject;
public class Some implements ScalaObject{
private Some() {}
public int \$tag() throws RemoteException {
return scala.ScalaObject.class.\$tag(this);
}
}
import scala.ScalaObject;
public class Some implements ScalaObject{
private Some() {}
public int \$tag() throws RemoteException {
return scala.ScalaObject.class.\$tag(this);
}
}
但若出現伴侶物件且嘗試建構物件時,編譯過後的.class,private就不是private了。例如:
class Some private {
}
object Some {
def apply = new Some
}
在上例中,Some在編譯過後,其.class的定義會是:
import java.rmi.RemoteException;
import scala.ScalaObject;
public class Some implements ScalaObject {
public Some() {}
public int \$tag() throws RemoteException {
return scala.ScalaObject.class.\$tag(this);
}
}
import scala.ScalaObject;
public class Some implements ScalaObject {
public Some() {}
public int \$tag() throws RemoteException {
return scala.ScalaObject.class.\$tag(this);
}
}
在Scala中有特徵(Trait),特徵可以有實作,在Java中要使用Scala中的特徵所編譯過來的.class,最簡單的方式,是就是在Scala中定義沒有任何實作的特徵,如此編譯過去的.class,就是對應至Java的介面(Interface)。例如:
trait Some {
def doIt(a: Any): Any
}
在編譯為.class後,會對應至以下的Java介面:
public interface Some {
public abstract Object doIt(Object obj);
}
public abstract Object doIt(Object obj);
}
有實作的特徵在Java中沒有直接對應的語法,可以得到如Scala中直接具有實作特徵的好處。真的要使用具實作的特徵編譯出來的.class還是有辦法的,不過只會讓Java中的程式更為複雜,所以並不鼓勵,這在之後的文件還會介紹。
Scala的 型態參數 對應至Java的泛型(Generic)語法,由於Java並不支援 共變性(Covariance) 與 逆變性(Contravariance),在Scala中如果定義型態參數時標註了正變或逆變,編譯之後的.class是不會包含任何正變、逆變資訊的。例如:
class Fruit
class Apple extends Fruit
class Some[+T]
class Util {
val sf: Some[Fruit] = new Some[Apple]
}
編譯之後的.class定義分別如下:
// Some.class
import java.rmi.RemoteException;
import scala.ScalaObject;
public class Some<T> implements ScalaObject {
public int \$tag() throws RemoteException {
return ScalaObject.class.\$tag(this);
}
}
// Util.class
import java.rmi.RemoteException;
import scala.ScalaObject;
public class Util implements ScalaObject {
private final Some<Fruit> sf;
public Util() {
this.sf = new Some();
}
public Some<Fruit> sf() { return this.sf; }
public int \$tag() throws RemoteException {
return ScalaObject.class.\$tag(this);
}
}
import java.rmi.RemoteException;
import scala.ScalaObject;
public class Some<T> implements ScalaObject {
public int \$tag() throws RemoteException {
return ScalaObject.class.\$tag(this);
}
}
// Util.class
import java.rmi.RemoteException;
import scala.ScalaObject;
public class Util implements ScalaObject {
private final Some<Fruit> sf;
public Util() {
this.sf = new Some();
}
public Some<Fruit> sf() { return this.sf; }
public int \$tag() throws RemoteException {
return ScalaObject.class.\$tag(this);
}
}
如果使用了 既存型態(Existential type),編譯後的.class,會對應至Java的型態通配字元語法。例如:
class Util {
val sf: Some[_ <: Fruit] = new Some[Apple]
}
在編譯為.class後,定義如下:
import java.rmi.RemoteException;
import scala.ScalaObject;
public class Util implements ScalaObject {
private final Some<? extends Fruit> sf;
public Util() {
this.sf = new Some();
}
public Some<? extends Fruit> sf() { return this.sf; }
public int \$tag() throws RemoteException {
return ScalaObject.class.\$tag(this);
}
}
import scala.ScalaObject;
public class Util implements ScalaObject {
private final Some<? extends Fruit> sf;
public Util() {
this.sf = new Some();
}
public Some<? extends Fruit> sf() { return this.sf; }
public int \$tag() throws RemoteException {
return ScalaObject.class.\$tag(this);
}
}