LL脳がscalaの勉強を始めたよ その101


Scalaコップ本の29章に入っていきますよ(`・ω・´)29章はScalaJavaの結合についてやっていきますねー

Scalaの特徴の1つとしてJavaとの親和性が挙げられることが多いですが、この説ではソレにまつわるエトセトラについてやっていきますデス

JavaからScalaを使うための注意点

まずは、Javaから見てScalaコードがどのように見えるのかを見ていきますデス(`・ω・´)

一般原則

基本的にScalaコードはコンパイラによって標準Javaバイトコードに翻訳されるみたいです。このように、Javaとの親和性を確保するようにScalaの設計は行われているのですが、いくつかJavaとは異なる独自設計を選んだ昨日もあるそうです。

ScalaJavaとが異なるものとしては、例えばこんなものがありますな

上記のようにそのままScalaコードをJava機能にマッピングできないモノについてはJavaの持っている構造を組み合わせることでJavaバイトコードへの変換を行っているみたいです。しかしながら変換方法については固定されておらず、Scalaのバージョンアップと共に最適なものを模索しているとのことです。なお、現在のScalaコンパイラーの方法はjavapコマンドでclassファイルを解析することで探索することが可能とのことです。

値型

値型については次の2通りの方法でJavaへの翻訳が行われているみたいです。

  • Scalaの値型をそのままjavaの値型に翻訳
  • 値型をJavaオブジェクトでラップして操作
    • ScalaのInt型 → java.lang.Integerでラップ

コンパイラは通常は前者の方法を使うことでパフォーマンスを確保するらしいのですが、例えばList[Any]等のようにInt以外を扱うかも知れない場合のようにコンパイラーが確証を得られない場合は後者を使うみたいです。

後者になってしまうと若干パフォーマンスが落ちてしまうみたいですねぇ(´・ω・`)

シングルトンオブジェクト

Javaはシングルトンオブジェクトを持っていないものの、似たようなものとして静的メソッドをもっているようなので、ScalaのシングルトンオブジェクトとJavaの静的メソッドとインスタンスメソッドの組み合わせで翻訳されるみたいです。

すべてのScalaシングルトンオブジェクト(例えばAppオブジェクト)はコンパイラーによってドル記号が末尾につけられたJavaクラス(App$クラス)に変換されるみたいです。このApp$クラスではAppオブジェクトの持っている全てのメソッドとフィールドを持っていて、かつ実行時に作成される唯一のクラスインスタンスを保持するためのMODULE$という名前の静的フィールドも持っているそうです。

変換例はこんな感じになりますね。Appシングルトンオブジェクトをこんなふうに定義してみますよ

object App {
  def main(args:Array[String]){
    println("Hello, Java World!")
  }
}

上記をコンパイラが翻訳してできたApp$は次のようになるみたいです(javapでApp$を眺めた結果です)

public final class App$ extends java.lang.Object
implements scala.ScalaObject {
  public static final App$ MODULE$;
  public static {};
  public App$();
  public void main(java.lang.String[]);
  public int $tag();
}

...Javaコードあんまりわかんね(´・ω・`)と思いつつ、雰囲気だけはなんとか‥.ちなみにScalaスタンドアロンシングルトンオブジェクトのように同名のクラスのないシングルトンオブジェクトを翻訳する場合、コンパイラは同名にJavaクラスを作ってシングルトンオブジェクトの各メソッドから呼び出すための転送用静的メソッドを定義するみたいです。

たとえば上記Appオブジェクトがスタンドアロンシングルトンオブジェクトだった場合は、次のようなJavaクラスが生成されるみたいです(javapでAppを眺めた結果です)

Compiled from "App.scala"
public final class App extends java.lang.Object {
  // App$からの呼び出し転送用の定義ですね
  public static final int $tag();
  public static final void main(java.lang.String[]);
}

なお、スタンドアロンシングルトンオブジェクトと同名のクラスがあった場合はコンパイラが上記のようなJavaの同名クラスの生成は行わないため、Javaコードは静的フィールドのMODULE$を介してシングルトン(APP$)にアクセスする必要があるみたいです。

…(´ε`;)ウーン…シングルトンオブジェクト周りは結構複雑ですな。スタンドアロンの方が扱いやすいのかしら?

インターフェースとしてのトレイト

トレイトはこんぱいるされると同名のJavaインターフェースが作られるみたいです。生成されたインターフェースはJavaの型として使えるので、この型の変数を通じてScalaオブジェクトのメソッドが呼び出せるそうです。

なお、抽象メソッドだけで書いたScalaのトレイトはそのままJavaインターフェースに直接翻訳されるので完全一致するものの、ソレ以外の場合はその他のコードを生成するのでJavaでトレイト(の動作)を実装するのは大変とのことです。まあ、できなくはない、とのことですが...

アノテーション

ScalaアノテーションJavaに関わる部分のみを取り上げてみていきますねー

標準アノテーションの特別な効果

Javaプラットフォームをターゲットとしたいくつかのアノテーションを使うと、コンパイラーがJava用の特別な情報を生成するみたいです。コンパイラアノテーションを処理する順序としては次のようになるみたいです。

Javaと連携する標準アノテーションとしては次のようなものがありますね

  • 使うべきではない機能(@deprecated)
    • @deprecatedがつけられたメソッドやクラスに対し、コンパイラは生成されたコードにJavaの同種のアノテーションを追加
    • Javaコードが使うべきではないScalaメソッドにアクセスした場合に警告メッセージを生成
  • 揮発性フィールド(@volatile)
    • @volatileが付けられたフィールドに対応するコードには、コンパイラJavaのvolatile修飾子を付与
    • @volatileフィールドはJavaの流儀に従って振舞う
    • volatileフィールドに対するアクセス順序付けはJavaモリーモデルがvolatileフィールドに対して定めている規則に従う
  • リアライゼーション(@serializable, @SerialVersionUID(1234L), @transient)
    • @serializableはJavaのSerializableインターフェースが追加
    • @SerialVersionUID(1234L)はprivate finel static long SerialVersionUID = 1234のようなJavaフィールド定義に変換
    • @transientがつけられた変数にはJavaのtransient修飾子が付与

...と上記のような追加情報が生成されるようです。Javaの世界の人と会話する際に使えそうなので頑張って覚えたいと思います(`・ω・´)

投げられた例外

ScalaにはJavaのメソッドに対するthrows宣言にあたるものがないので、投げられた例外がキャッチされたことをチェックしないそうです。なので全てのScalaメソッドは例外の宣言がないJavaメソッドに変換されるとのことです。これはJavaの例外処理コードを書く作業があまりにも負担が大きいため、適切に運用されていない(適当に例外処理されている)→だったら省略すればいいじゃん(乱暴な意訳)という流れのようです。

ただし、Javaとの併用をきちんと行うためにはメソッドが投げうる例外への対処を行わないと行けない場面がでてくるので、Scalaでは@throwsアノテーションでこれに対応することにしているようです。@throwsアノテーションは例えば次のように使用するみたいデスね(´・ω・`)次のサンプルではRMIリモートインターフェースに含まれるメソッドに対応するためにRemoteException例外をリストアップしているそうです。

import java.io._
class Reader(fname:String){
  private val in = 
    new BufferedReader(new FileReader(frname))
  // throwsアノテーションでIOExceptionをリストアップします
  @throws(classOf[IOException])
  def read() = in.read()
}

上記のコードはJavaから見ると次のように見えるみたいです

public class Reader extends java.lang.Object implements 
scala.ScalaObject{
  public Reader(java.lang.String);
  // IOExceptionを投げるかも、宣言をしていますね
  public int read() throws java.io.IOException;
  public int $tag();
}
Javaアノテーション

Javaフレームワークの既存アノテーションScalaコードから直接使うことができるみたいです。コンパイルされたScalaコードをJavaフレームワークから見ると、それらのアノテーションJavaで書いたのと同じように見えるそうです。

実際にJavaフレームワークアノテーションScalaで使うサンプルとしてJUnit4を扱ってみます。自動テストを書いて実行するJUnit4ではアノテーションを使ってコードのどの部分がテストなのかを示すそうです。

とりあえずサンプルを書いてみますよ、テスト部分には次のようにアノテーションを付けるだけですね

import org.junit.Test
import org.junit.Assert.assertEquals
class SetTest {
  @Test
  def testMultiAdd {
    val set = Set() + 1 + 2 + 3 + 1 + 2 + 3
    // setのサイズが3の場合はOKです
    asserEquals(3, set.size)
  }
}

junitのテストは本来はorg.junit.Testアノテーションで表現されるらしいのですが、上記コードではorg.junit.Testをインポートしているので単純に@Testアノテーションで表現しております。

ではjunitを実際に実行してみますよ

// コンパイルします
% scalac -cp junit/junit-4.8.2.jar SetTest.scala
% scala -cp junit/junit-4.8.2.jar:. org.junit.runner.JUnitCore SetTest
JUnit version 4.8.2
.
Time: 0.012

OK (1 test)

おお、無事テストが行われました(´・ω・`)以前Java関連のテストフレームワークが上手く動かなかったのでチョット不安でしたがうまくいってよかった…

いじょうー

時間切れで以上です。次回はアノテーションの続きの独自アノテーションの開発からやっていきますよー