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


Scalaコップ本の29章の続きをやっていきますよー。29章はJavaScalaの結合で今回は前回途中で終わってしまったアノテーションでの連携云々からやっていきますデス(`・ω・´)

アノテーション(続き)

Scalaアノテーションを使ったJavaとの連携につい前回残った独自アノテーションについて見ていきますね。

独自アノテーションの開発

ScalaコンパイラではJavaリフレクションから見えるアノテーションをサポートしていないので、ScalaからJavaアノテーションの可能性をフルに引き出すことは出来ないそうです。これは(多分)将来的にScalaが独自のリフレクションを持つはずなので、その場合はScalaリフレクションでScalaアノテーションにアクセスしたくなるはずだから、Javaリフレクション用のアノテーションには対応しませんYO!とのことです。

ちなみにJavaのリフレクションは「Javaクラスからフィールドやメソッドなどの情報を取得する API」だとのことです → ここらへんを参照


どうしてもJavaリフレクションから見えるアノテーションを作るにはJavaの記法を使ってjavacでコンパイルする必要があるみたいです。例えば次のようなサンプルになりますね(´・ω・`)

// Ignore.javaとして保存します
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Ignore {}

こいつをjavacでコンパイルしておけばScalaで次のように使えるみたいです

// Tests.scalaとして保存します
object Tests {
  // 上で定義したIgnoreをアノテーションとして使って
  // testDataをtest〜という名前にも関わらずテスト対象から除外します
  @Ignore
  def testData = List(0, 1, -1, 5, -5)
  def test1 {
    assert(testData == (testData.head :: testData.tail))
  }
  def test2 {
    assert(testData.contains(testData.head))
  }
}

これらのアノテーションが付けられているかを確かめるには次のようにJavaのリフレクションAPIScalaから使うそうです。

// FindTests.scalaとして保存します
for {
  // Javaのリフレクションメソッドを使って
  // オブジェクトが属するクラスの全てのフィールドを解析します
  method <- Tests.getClass.getMethods
  if method.getName.startsWith("test")
  // アノテーション固有の情報を取得します
  if method.getAnnotation(classOf[Ignore]) == null
} {
  println("found a test method: " + method)
}

上記コードではgetAnnotationメソッドで新しいIgnoreタイプアノテーションを探索するみたいで、探索の結果は実際のアノテーションオブジェクトかnullかで返されるそうです。

対話コンソールで実際に実行してみるとこんな感じになりますね

found a test method: public void line1$object$$iw$$iw$Tests$.test2()
found a test method: public void line1$object$$iw$$iw$Tests$.test1()

うーん、Javaをやってないからチンプンカンプンなところが...近いうちにそこら辺も見ておかないとだめだな(´・ω・`)

存在型

ScalaではJavaの全ての型に対応するモノをもっているそうです。例えばJavaのPattern→ScalaのPatternやJavaIteratorScalaIterator[Component]のような対応関係になるみたいです。

しかしながらJavaIteratorIterator、型パラメータが省略されたIteratorのような生の型については既存のScala型では表現できないので、存在型という特殊な型を使うことになるそうです。ちなみに存在型はワイルドカード型や生の型に使用されるそうです。なお、存在型は主にScalaからjava型にアクセスする際に使われるものの、Scala言語の一部としてフルサポートされているみたいです。

存在型の一般形式

存在型の一般形式は次のようになるそうです

型 forSome { 宣言 }

上記形式の各要素は次のようになっています

  • 型は任意のScala
  • 宣言は抽象valとTypeのリスト

宣言された変数や型については存在するけども(抽象メンバーと同様に)未知の存在と解釈されて、型部分はそれらが何を参照しているかを知らない状態であるにも関わらず、それらの変数や型を参照できるようになるという、とりあえずいってこい的な動作になるっぽいです。

(´ε`;)ウーン…ちょっとイメージがわきにくいので具体例を見てみます

Iterator[T] forSome { type T }

このコードを文章で表現すると「何らかの型Tがあるとき、これはそのT型のためのIteratorでる(This is an Iterator of T's for some type T)」という表現になるそうで、強引に噛み砕くと「Tは未知の型なのでとりあえず何でもいいんだけども、この特定のIteratorにとっては、なにかしら決まった型になることだけは分かっている(詳細はしらんけど)」っていう雰囲気ですかね?

上と同様にjavaIteratorScalaで表現すると次のようになるみたいです。

Iterator[T] forSome { type T <: Component }

こちらも文章で表現してみると「Componentのサブ型の何らかのT型があるとき、それはそのT型のためのIteratorで、Tは未知の方ではあるもののComponentのサブ型であるということだけははっきりしている」となるみたいです。

存在型のためのプレースホルダ

ちなみにこれまで書いてきた存在型の表現は次のようにプレースホルダーを使うことで簡単に記述することができるみたいです。

Iterator[_]

上記はこのコードと同じ意味ですネ(´・ω・`)

Iterator[T] forSome { type T }

Scalaでは関数リテラルプレースホルダーと同様に、型のかわりに_を使うことで存在型してくれるそうです。ちなみに同じ型の中で2個の _ を使うとforSome節は2個の型を含むものになるそうです。

また、プレースホルダーを使った上限境界や下限境界も使えるみたいです。上限境界を適用したサンプルは例えばこんな感じになりますね

Iterator[T] forSome { type T <: Component }

こいつは次のように書いたものと同等になるそうです

Iterator[T] forSome { type T <: Component }
存在型の使い方サンプル
import java. util.*;    

public class Wild {
  Collection<?> contents() {
    Collection<String> stuff = new Vector<String>();
    stuff.add("a");
    stuff.add("b");
    stuff.add("see");
    return stuff;
  }
}

... とこちらのJavaクラスをScalaからアクセスするとうまい具合に存在型を使えるはず…なのですが、コップ本のサンプルを動かすとなぜか下記エラーが発生して進めず、残念。

java.lang.IllegalAccessError: tried to access method Wild.contents()Ljava/util/Collection; from class 

ちょっと原因が追いきれないので後回しにしますデス(´;ω;`)

複雑な条件での存在型

条件が複雑な場合での存在型の取り扱いはちょろっと手間がかかるみたいです。たとえばScalaのミュータブル集合を作ってcontentsの要素による初期化を行う場合について考えてみます。たとえばこんな感じのサンプルを作りたいですネー

import scala.collection.mutable.Set
val iter = (new Wild).contents.iterator
val set = Set,empry[???] // 何の型がはいるかわからないデス(´・ω・`)
while (iter.hasMore)
 // ...

しかしながら集合の要素型を指定できないので上手く書けなくなってしまいます。こんな時には次の2つのテクニックを使うのがいいみたいです。

  • メソッドに存在型を渡すときにはforSome節からメソッドに型パラメータを移して、メソッド本体の中では型パラメータを使ってforSome節内の型を参照
  • メソッドから存在型を返す代わりに、forSome節内の1つ1つの型の抽象メンバーを持つオブジェクトを返す

上記2つのテクニックを適用したものが次のようになるみたいです。イメージ的に存在型の代わりに抽象メンバーを使ってゴニョゴニョする感じですかね?

import scala.collection.mutable.Set
import java.util.Collection
// 抽象メンバーを持つオブジェクトを定義
abstract class SetAndType {
  type Elem
  val set:Set[Elem]
}
def javaSet2ScalaSet[T](jset:Collection[T]):setAndType = {
  val sset = Set.empty[T] // Tとして扱います
  val iter = jset.iterator
  while(iter.hasNext)
    sset += iter.next()
  // 存在型の代わりに抽象メンバーをもつオブジェクトを返します
  return new SetAndType {
    qtype Elem = T
    val set = sset
  }
}

...とここでコップ本的まとめでは「存在型を使って高度なことを遣るような場面では、存在型を抽象メンバーに変換する」とのことなので、最初っから抽象メンバー使え、な?ってことみたいです。そんなわけで普段はScalaコードで存在型を使わないみたいです(´・ω・`)

いじょー

...と、強引に突っ走ってみましたが以上で写経風味全開の29章はおわりですー。次回はScalaの特徴の1つであるアクターと並行プログラミングについてやりますねー

あと4章か…が、頑張ります