Javapを使用してJavaのScala.Noneの使用について学ぶ方法は?
-
27-10-2019 - |
質問
前の質問で、 JavaからScala.Noneにアクセスします, 、人々が使用していたようです javap
アクセス方法を把握するため scala.None
Javaから。彼らがそれをどのようにしたか知りたいです。参考までに、答えは次のとおりです。
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
私もjavapを走らせました scala.None
と scala.Option
. 。からどのように把握できますか javap
出力:
None
の唯一のオブジェクトですNone.type
拡張するタイプOption
-
apply()
コンパニオンオブジェクトの方法が必要です
?
解決
SCALAコードがJVM-ByteCodeにコンパイルされる方法はルールがあります。潜在的な名前の衝突のため、生成されたコードは常に直感的ではありませんが、ルールが既知の場合は、Java内のコンパイルされたScalaコードにアクセスすることが可能です。
注意: これを書いている間、私はJavacとEclipse-JavacがJavaのScalaコードにアクセスする際に異なる行動をとることに気付きました。以下のコードは、そのうちのコードと他のコードではコンパイルされない可能性があります。
クラス、コンストラクター、メソッド
ここには特別なルールはありません。次の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);
ParameterListのないメソッドの場合、空のparameterListが作成されていることに注意してください。複数のパラメーターリストが単一のパラメーターリストにマージされます。
フィールド、値
クラスの場合 class X(var i: Int)
ゲッターとセッターが作成されます。クラスの場合 class X(val i: Int)
ゲッターのみが作成されます:
//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はこれらの特別な標識のそれぞれに対して特定の名前を生成します。クラスがあります scala.reflect.nameTransformer OPSをエンコード/デコードできます。
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オブジェクトのフィールドとメソッドにアクセスする静的メソッドが含まれています。後者は、インスタンス化できないシングルトンクラスです。クラスのシングルトンインスタンスを保持するフィールドがあります。 MODULE$
, 、シングルトンへのアクセスを可能にします:
X.i();
X$.MODULE$.i();
ケースクラス
SCALAコンパイラは、ケースクラスのApply-Methodを自動的に生成し、フィールドのゲッターを生成します。ケースクラス case class X(i: Int)
簡単にアクセスできます:
new X(3).i();
X$.MODULE$.apply(3);
特性
特性 trait T { def m }
, 、抽象メンバーのみを含む、インターフェイスにコンパイルされ、名前のクラスファイルに配置されます 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も、このクラスへのアクセスをコンパイルしません。
特性がどのように編集されるかについてのいくつかの詳細を見つけることができます ここ.
機能
関数リテラルは、クラス関数の匿名インスタンスとしてコンパイルされます。 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
名前を取得します apply
.
これは、私たちの場合、次のことを意味します。
X$$anonfun$1.class
fの場合X$$anonfun$g$1.class
gの場合X$$anonfun$h$1$$anonfun$apply$1.class
hの場合X$$anonfun$i$1.class
と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);
}
コンストラクターとApply-Methodがあります(オプションに1つと拡張オプションがあることがわかっているため)。それらの1つを使用して幸せになります:
new Some<Integer>(3);
Some.apply(3);
なし
// javap scala.None
public final class scala.None extends java.lang.Object{
...
}
コンストラクターも適用されておらず、オプションを拡張していません。だから、私たちは見ていきます None$
:
// javap -private scala.None$
public final class scala.None$ extends scala.Option implements ... {
...
public static final scala.None$ MODULE$;
private scala.None$();
}
うん! aを見つけました 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$
. 。 Anを期待するApply-Methodがあります scala.collection.Seq
. 。したがって、リストを取得するには、最初にseqが必要です。しかし、seqを見ると、適用測定はありません。さらに、Seqのスーパークラスとで見ると scala.collection.Seq$
seqを期待する適用測定のみを見つけることができます。じゃあ何をすればいいの?
Scalacがリストまたは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コードがBytecodeにコンパイルされる方法がいくつかあることを知っています。しかし、上記の情報では、これらのルールを自分で見つけることができるはずだと思います。
他のヒント
私は他の答えと競合していませんが、人々はしばしばこれに気付かないように思われるので、あなたはこれを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