33

以前の質問Accessing scala.None from Javajavapでは、以前は Javaからアクセスする方法を理解していたようscala.Noneです。彼らがどのようにそれをしたか知りたいです。参考までに、答えは次のとおりです。

scala.Option$.MODULE$.apply(null);

これは次のように短縮できます。

scala.Option.apply(null);

このプログラム ( ) が与えられた場合OptionTest.scala:

object OptionTest extends App {
  val x = scala.None
  val y = scala.Some("asdf")
}

私はjavapこのようにそれを実行しました:

javap -s -c -l -private OptionTest

これはjavap出力の一部です。

public static final scala.None$ x();
  Signature: ()Lscala/None$;
  Code:
   0:   getstatic  #11; //Field OptionTest$.MODULE$:LOptionTest$;
   3:   invokevirtual  #55; //Method OptionTest$.x:()Lscala/None$;
   6:   areturn

scala.Noneとで javap も実行しましたscala.Optionjavap出力から次のことをどのように判断しますか。

  1. NoneNone.type拡張するタイプの唯一のオブジェクトですOption
  2. apply()コンパニオン オブジェクトのメソッドが必要です

?

4

2 に答える 2

72

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.classX$.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.classiとX$$anonfun$i$1$$anonfun$j$1$1.classj について

それらにアクセスするには、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 コードをバイトコードにコンパイルする方法には、他にもいくつかの規則があることは知っています。しかし、上記の情報があれば、これらのルールを自分で見つけることができるはずです。

于 2012-02-19T19:31:41.573 に答える
15

私は他の答えと競合していませんが、人々はこれに気付かないことが多いので、replでこれを行うことができます.

scala> :paste
// Entering paste mode (ctrl-D to finish)

object OptionTest extends App {
  val x = scala.None
  val y = scala.Some("asdf")
}

// Exiting paste mode, now interpreting.

defined module OptionTest

scala> :javap -v OptionTest$
Compiled from "<console>"
public final class OptionTest$ extends java.lang.Object implements scala.App,scala.ScalaObject
  SourceFile: "<console>"
  Scala: length = 0x

  [lots of output etc]   

  public scala.None$ x();
    Code:
     Stack=1, Locals=1, Args_size=1
     0: aload_0
     1: getfield    #65; //Field x:Lscala/None$;
     4: areturn
于 2012-02-24T18:46:09.473 に答える