Scala コードを JVM バイトコードにコンパイルする方法には規則があります。名前が衝突する可能性があるため、生成されたコードは常に直感的に理解できるとは限りませんが、ルールがわかっている場合は、Java 内でコンパイルされた Scala コードにアクセスすることができます。
注意:これを書いているときに、Java から Scala コードにアクセスする際に javac と eclipse-javac の動作が異なることに気付きました。以下のコードは、そのうちの 1 つでコンパイルできても、他のコードではコンパイルできない可能性があります。
クラス、コンストラクタ、メソッド
ここには特別なルールはありません。次の Scala クラス
class X(i: Int) {
def m1 = i*2
def m2(a: Int)(b: Int) = a*b
def m3(a: Int)(implicit b: Int) = a*b
}
通常の Java クラスと同じようにアクセスできます。次の名前のファイルにコンパイルされますX.class
。
X x = new X(7);
x.m1();
x.m2(3, 5);
x.m3(3, 5);
パラメータリストのないメソッドの場合、空のパラメータリストが作成されることに注意してください。複数のパラメーター リストが 1 つにマージされます。
フィールド、値
クラスのclass X(var i: Int)
ゲッターとセッターが作成されます。クラスのclass X(val i: Int)
場合、Getter のみが作成されます。
//Scala
val x = new X(5)
x.i = 3 // Setter
x.i // Getter
//Java
X x = new X(5);
x.i_$eq(3); // Setter
x.i(); // Getter
Java では、識別子に特殊記号を含めることは許可されていないことに注意してください。したがって、scalac はこれらの特殊記号のそれぞれに対して特定の名前を生成します。ops をエンコード/デコードできるクラスscala.reflect.NameTransformerがあります。
scala> import scala.reflect.NameTransformer._
import scala.reflect.NameTransformer._
scala> val ops = "~=<>!#%^&|*/+-:\\?@"
ops: String = ~=<>!#%^&|*/+-:\?@
scala> ops map { o => o -> encode(o.toString) } foreach println
(~,$tilde)
(=,$eq)
(<,$less)
(>,$greater)
(!,$bang)
(#,$hash)
(%,$percent)
(^,$up)
(&,$amp)
(|,$bar)
(*,$times)
(/,$div)
(+,$plus)
(-,$minus)
(:,$colon)
(\,$bslash)
(?,$qmark)
(@,$at)
クラスclass X { var i = 5 }
は、コンストラクターでフィールドが作成されたときと同じスキーマによって変換されます。i
この変数は非公開であるため、Java から変数に直接アクセスすることはできません。
オブジェクト
Java には Scala オブジェクトのようなものはありません。したがって、scalac はいくつかの魔法を行う必要があります。オブジェクトに対して、object X { val i = 5 }
2 つの JVM クラス ファイルが生成されます:X.class
とX$.class
. 最初のものはインターフェイスのように機能し、Scala オブジェクトのフィールドとメソッドにアクセスするための静的メソッドが含まれています。後者は、インスタンス化できないシングルトン クラスです。という名前のクラスのシングルトン インスタンスを保持する Field があり、シングルトンMODULE$
へのアクセスを許可します。
X.i();
X$.MODULE$.i();
ケースクラス
Scala コンパイラは、ケース クラスの apply-method とフィールドの Getter を自動的に生成します。ケース クラスcase class X(i: Int)
には簡単にアクセスできます。
new X(3).i();
X$.MODULE$.apply(3);
特徴
trait T { def m }
抽象メンバーのみを含むtraitは、 という名前のクラス ファイルに配置されるインターフェイスにコンパイルされますT.class
。したがって、Java クラスで簡単に実装できます。
class X implements T {
public void m() {
// do stuff here
}
}
トレイトに具体的なメンバーが含まれている場合<trait_name>$class.class
、通常のインターフェイスに加えて、生成されたという名前のクラス ファイルがあります。特性
trait T {
def m1
def m2 = 5
}
Java 内で簡単に実装することもできます。クラスファイルT$class.class
にはトレイトの具体的なメンバーが含まれていますが、Java からアクセスすることはできないようです。javac も eclipse-javac も、このクラスへのアクセスをコンパイルしません。
特性がどのようにコンパイルされるかについての詳細は、ここにあります。
機能
関数リテラルは、クラス FunctionN の無名インスタンスとしてコンパイルされます。Scala オブジェクト
object X {
val f: Int => Int = i => i*2
def g: Int => Int = i => i*2
def h: Int => Int => Int = a => b => a*b
def i: Int => Int => Int = a => {
def j: Int => Int = b => a*b
j
}
}
上記のように、通常のクラスファイルにコンパイルされます。さらに、各関数リテラルは独自のクラスファイルを取得します。そのため、関数値に対して、という名前のクラス ファイル<class_name>$$anonfun$<N>.class
が生成されます。ここで、N は連続した数値です。関数メソッド (関数を返すメソッド) の場合、 という名前のクラス ファイル<class_name>$$anonfun$<method_name>$<N>.class
が生成されます。関数名の各部分はドル記号で区切られ、anonfun
識別子の前にも 2 つのドル記号があります。ネストされた関数の場合、ネストされた関数の名前が外側の関数に追加されます。これは、内側の関数が のようなクラス ファイルを取得することを意味します<class_name>$$anonfun$<outer_method_name>$<N>$$anonfun$<inner_method_name>$<N>.class
。内部関数に名前がない場合、に見られるh
ように name を取得しますapply
。
これは、私たちの場合、次のようになることを意味します。
X$$anonfun$1.class
のための
X$$anonfun$g$1.class
グラム
X$$anonfun$h$1$$anonfun$apply$1.class
時間
X$$anonfun$i$1.class
iとX$$anonfun$i$1$$anonfun$j$1$1.class
j について
それらにアクセスするには、apply-method を使用します。
X.f().apply(7);
X.g().apply(7);
X.h().apply(3).apply(5);
X.i().apply(3).apply(5);
質問に答えて
あなたが知っておくべき:
- 通常の Scala クラスは、コンストラクターまたは適用メソッドによってアクセスできます。
- apply-method があるよりもコンストラクターがない場合
- コンストラクターも適用メソッドもない場合、クラスが呼び出されるのと同じ方法で名前が付けられた別のクラスファイルがあり、最後にドル記号が追加されます。
MODULE$
このクラスでフィールドを検索
- コンストラクターと適用メソッドは継承されるため、サブクラスで何も見つからない場合はスーパークラスを検索してください
いくつかの例
オプション
// javap scala.Option
public abstract class scala.Option extends java.lang.Object implements ... {
...
public static final scala.Option apply(java.lang.Object);
public scala.Option();
}
javap には、コンストラクターと適用メソッドがあると言われています。さらに、クラスは抽象的であると言います。したがって、apply-method のみを使用できます。
Option.apply(3);
いくつか
// javap scala.Some
public final class scala.Some extends scala.Option implements ... {
...
public scala.Some(java.lang.Object);
}
これにはコンストラクターと適用メソッドがあります (Option にはコンストラクターがあり、Some は Option を拡張することがわかっているため)。それらのいずれかを使用して幸せになります:
new Some<Integer>(3);
Some.apply(3);
なし
// javap scala.None
public final class scala.None extends java.lang.Object{
...
}
コンストラクターも適用メソッドも持たず、Option を拡張しません。したがって、以下を見ていきますNone$
。
// javap -private scala.None$
public final class scala.None$ extends scala.Option implements ... {
...
public static final scala.None$ MODULE$;
private scala.None$();
}
うん!MODULE$
オプションのフィールドと適用メソッドが見つかりました。さらに、プライベート コンストラクターを見つけました。
None$.apply(3) // returns Some(3). Please use the apply-method of Option instead
None$.MODULE$.isDefined(); // returns false
new None$(); // compiler error. constructor not visible
リスト
scala.collection.immutable.List
は抽象的であるため、 を使用する必要がありますscala.collection.immutable.List$
。を期待する apply-method がありますscala.collection.Seq
。したがって、リストを取得するには、最初に Seq が必要です。しかし、Seq を見ると、apply メソッドはありません。さらに、Seq のスーパークラスを見ると、scala.collection.Seq$
Seq を期待する apply-methods しか見つかりません。じゃあ何をすればいいの?
scalac が List または Seq のインスタンスを作成する方法を確認する必要があります。最初に Scala クラスを作成します。
class X {
val xs = List(1, 2, 3)
}
scalac でコンパイルし、javap でクラス ファイルを確認します。
// javap -c -private X
public class X extends java.lang.Object implements scala.ScalaObject{
...
public X();
Code:
0: aload_0
1: invokespecial #20; //Method java/lang/Object."<init>":()V
4: aload_0
5: getstatic #26; //Field scala/collection/immutable/List$.MODULE$:Lscala/collection/immutable/List$;
8: getstatic #31; //Field scala/Predef$.MODULE$:Lscala/Predef$;
11: iconst_3
12: newarray int
14: dup
15: iconst_0
16: iconst_1
17: iastore
18: dup
19: iconst_1
20: iconst_2
21: iastore
22: dup
23: iconst_2
24: iconst_3
25: iastore
26: invokevirtual #35; //Method scala/Predef$.wrapIntArray:([I)Lscala/collection/mutable/WrappedArray;
29: invokevirtual #39; //Method scala/collection/immutable/List$.apply:(Lscala/collection/Seq;)Lscala/collection/immutable/List;
32: putfield #13; //Field xs:Lscala/collection/immutable/List;
35: return
}
コンストラクターは興味深いものです。これは、1、2、および 3 で埋められた int の配列が作成される (l. 12) ことを示しています (l. 14-25)。その後、このアレイはscala.Predef$.wrapIntArray
(l. 26) に配信されます。この結果scala.collection.mutable.WrappedArray
は、再びリスト (l. 29) に配信されます。最後に、リストがフィールドに格納されます (l. 32)。Java でリストを作成したいときは、同じことをしなければなりません:
int[] arr = { 1, 2, 3 };
WrappedArray<Object> warr = Predef$.MODULE$.wrapIntArray(arr);
List$.MODULE$.apply(warr);
// or shorter
List$.MODULE$.apply(Predef$.MODULE$.wrapIntArray(new int[] { 1, 2, 3 }));
これは醜く見えますが、機能します。Scala ライブラリへのアクセスをラップする見栄えの良いライブラリを作成すると、Java から Scala を簡単に使用できるようになります。
概要
Scala コードをバイトコードにコンパイルする方法には、他にもいくつかの規則があることは知っています。しかし、上記の情報があれば、これらのルールを自分で見つけることができるはずです。