S-JIS[2011-01-04/2013-06-08] 変更履歴
Javaで言うジェネリクス(総称型)は、Scalaでは「パラメータ化された型(parameterized types)」と呼ぶらしい。
(でもなんか長ったらしいので、自分はひとまずジェネリクスと呼ぶことにする^^;)
Scalaのジェネリクスは、型パラメーター(型引数)や型名を角括弧「[]」で囲む。(Javaは「<>」で囲む)
Scalaでは、型名を囲む必要がある場合は全て角括弧に統一されているようだ。(classOf[]やisInstanceOf[]等)
		
  | 
		
		
  | 
	|
クラス・トレイトのジェネリクスは、クラス名(トレイト名)の後ろに角括弧で型パラメーターを記述する。
class クラス名[型パラメーター, …] 〜 trait トレイト名[型パラメーター, …] 〜
| Scalaの例 | Java相当 | 備考 | 
|---|---|---|
		class Example[T] { protected var t: T = _ def set(t: T): Unit = { this.t = t } def get(): T = { t } }  | 
		
		public class Example<T> {
  protected T t;
  public void set(T t) {
    this.t = t;
  }
  public T get() {
    return t;
  }
}
		 | 
		型パラメーターが1つのクラスを定義した例。 | 
		val s = new Example[String] s.set("abc") val v = s.get()  | 
		
		final Example<String> s = new Example<String>();
                 s.set("abc");
final String v = s.get();
		 | 
		使う際に型パラメーターを指定する例。 | 
		def f(s:Example[_]) = println(s)  | 
		
		public void f(Example<?> s) { System.out.println(s); }  | 
		使う際に型パラメーターが何でもいい場合は、「_」を指定する。 | 
		class Example[T] extends Parent[T] { }  | 
		
		public class Example<T> extends Parent<T> {
}
		 | 
		親クラスの型パラメーターに自分の型パラメーターを指定する例。 | 
		class Example[T, U] { def method1(t: T): Unit = { println(t) } def method2(u: U): Unit = { println(u) } }  | 
		
		public class Example<T, U> {
  public void method1(T t) {
    System.out.println(t);
  }
  public void method2(U u) {
    System.out.println(u);
  }
}
		 | 
		型パラメーターが2つのクラスを定義した例。 | 
		val s = new Example[String, Int] val s = new Example[String, Int]()  | 
		
		final Example<String, Integer> s = new Example<Stirng, Integer>();  | 
		型パラメーターが2つのクラスのインスタンスを生成する例。 | 
		val s = new (String Example Int) val s = new (String Example Int)()  | 
		型パラメーターが2つのクラスをnewでインスタンス生成する場合、クラス名を中央に置く書き方(中置記法)が出来る。 (まるで演算子のようになる。) 参考: okomokさんのOr.scala new (A op B)は、AやBのインスタンスを生成するのではなく、opのインスタンスを生成している。 | 
	|
		scala> class op[L, R] defined class op scala> class A; class B; class C defined class A defined class B defined class C scala> new (A op B) res1: op[A,B] = op@138d2fc scala> new (A op B op C) res2: op[op[A,B],C] = op@d76d51  | 
	
メソッドのみに適用するジェネリクスは、メソッド名の後ろに角括弧で型パラメーターを記述する。
def メソッド名[型パラメーター, …](引数, …) 〜
| Scalaの例 | Java相当 | 備考 | 
|---|---|---|
		class Example { def method[T](t: T): T = { t } }  | 
		
		public class Example {
  public <T> T method(T t) {
    return t;
  }
}
		 | 
		型パラメーターが1つのクラスを定義した例。 | 
		val obj = new Example val s = obj.method("abc")  | 
		
		final Example obj = new Example();
final String s = obj.method("abc");
		 | 
		メソッドのジェネリクスは、JavaでもScalaでも型推論してくれる。 | 
		val s = obj.method[String](null)  | 
		
		final String s = obj.<String>method(null);  | 
		型パラメーターを明示して呼び出す例。 | 
型パラメーターの上限境界(upper bound、Javaのextends)・下限境界(lower 
bound、Javaのsuper?)について。
型パラメーターは、指定できるクラスの上限・下限(親クラス・子クラス)の範囲を指定することが出来る。
例として、A1←A2←A3という継承関係のクラスがあるとする。
class A1 class A2 extends A1 class A3 extends A2
| Scalaの例 | Java相当 | 備考 | 
|---|---|---|
		class Example[T] { } ○ new Example[Any] ○ new Example[A1] ○ new Example[A2] ○ new Example[A3] ○ new Example[Nothing]  | 
		
		class Example<T> {
}
		○ new Example<Object>(); ○ new Example<A1>(); ○ new Example<A2>(); ○ new Example<A3>();  | 
		境界を何も指定しない例。 この場合、上限境界にAny、下限境界にNothingを指定したのと同じ。  | 
	
		class Example[T <: A2] { } × new Example[Any] × new Example[A1] ○ new Example[A2] ○ new Example[A3] ○ new Example[Nothing]  | 
		
		class Example<T extends A2> {
}
		× new Example<Object>(); × new Example<A1>(); ○ new Example<A2>(); ○ new Example<A3>();  | 
		上限境界を指定した例。 上限境界で指定したクラス自身とその派生クラスだけ受け付ける。  | 
	
		class Example[T >: A2] { } ○ new Example[Any] ○ new Example[A1] ○ new Example[A2] × new Example[A3] × new Example[Nothing]  | 
		下限境界を指定した例。 下限境界で指定したクラス自身とその親クラスだけ受け付ける。 (Javaではextendsと同じようなsuperの指定は出来ない)  | 
	|
		class Example[T >: A3 <: A1] { } × new Example[Any] ○ new Example[A1] ○ new Example[A2] ○ new Example[A3] × new Example[Nothing]  | 
		下限境界と上限境界を同時に指定した例。 この場合、順序を変えて「 T <: A1 >: A3」と書くとエラーになる。 | 
	|
		class Example[T <% scala.runtime.RichInt] { } × new Example[Any]
○ new Example[scala.runtime.RichInt]
○ new Example[Int]
○ new Example[Nothing]
		 | 
		可視境界(view bound)の例。 FからCへの暗黙変換(例:Int→RichInt)が定義されている場合、 「 T <% C」と書けば、Fも型パラメーターに指定できるようになる。→implicitへの書き換え  | 
	|
		//上限境界では、暗黙変換は無関係 class Example[T <: scala.runtime.RichInt] { } × new Example[Any]
○ new Example[scala.runtime.RichInt]
× new Example[Int]
○ new Example[Nothing]
		 | 
	||
		class Example[T] def fu(s: Example[_ <: A2]) = println(s) × fu(new Example[Any]) × fu(new Example[A1]) ○ fu(new Example[A2]) ○ fu(new Example[A3]) ○ fu(new Example[Nothing]) def fl(s: Example[_ >: A2]) = println(s) ○ fl(new Example[Any]) ○ fl(new Example[A1]) ○ fl(new Example[A2]) × fl(new Example[A3]) × fl(new Example[Nothing]) def fb(s: Example[_ >: A3 <: A1]) = println(s) × fb(new Example[Any]) ○ fb(new Example[A1]) ○ fb(new Example[A2]) ○ fb(new Example[A3]) × fb(new Example[Nothing])  | 
		
		class Example<T> {}
		public void fu(Example<? extends A2> s) {
  System.out.println(s);
}
		public void fl(Example<? super A2> s) {
  System.out.println(s);
}
		 | 
		メソッドの引数などで使用する際に境界を指定する事も出来る。 (Javaでは、extendsとsuperを同時に指定する事は出来ない) この使い方の場合、「<%」を指定する事は出来ないようだ。  | 
	
		import java.io._ class Example[T <: Serializable with Closeable] { } class SC extends Serializable with Closeable { def close() = {} } new Example[SC]  | 
		
		import java.io.*; class Example<T extends Serializable & Closeable> { } class SC implements Serializable, Closeable {
  @Override
  public void close() throws IOException {}
}
new Example<SC>();
		 | 
		Javaでは、複数のインターフェースを継承していることを示したい場合は「&」でつなぐ。 Scalaでは、素直に“withでつないだ新しいクラス(トレイト)”を上限境界に指定すれば良さそう。  | 
	
		class Example[T <: { def close():Unit }] { }  | 
		型パラメーターには、クラス名そのものだけではなく、クラス定義を直接指定することが出来る。 ただし、これとwithを同時に使うことは出来ないようだ。  | 
	|
		class Example[T : Hoge] { }  | 
		Scala2.8で導入されたcontext bound。 | 
→type(抽象タイプ)での上限境界(<:)・下限境界(>:)
型パラメーターが特定のクラスのときだけ呼び出せるようなメソッドを作る(そういう制限をする)ことが出来る。[2011-07-24]
これをGeneralized Type Constraintsと言う。(日本語訳では何と呼ぶのか定まっていないようなので、造語として「型パラメーター制約」と呼ぶことにする)
どういうときに使うかと言うと、ListのtoMapメソッドの例が分かり易い。
Listの定義は(単純化して言うと)「class List[A]」なので、Listの要素にはどんなクラスでも指定することが出来る。
ところがtoMapメソッドは、要素がTuple2のときだけしか使えない。
“使えない”と言うのは、実行時に例外が発生するという意味ではなく、Tuple2以外の型だったらコンパイルエラーになるということ。
これを型パラメーターの制約で実現している。
class List[A] {
  def toMap[K,V](implicit ev: A <:< (K,V)): Map[K,V] = 〜
}
「<:<」が制約を表している。
「A <:< (K,V)」は、「Aが(K,V)(タプル)の子クラスである(タプルを継承している)必要がある」という制約を意味する。
そういう条件を満たすような暗黙の値を探し、見つかればOK(コンパイルが通る)、見つからなければNG(コンパイルエラー)になるという仕組み。
ちなみに引数の変数名の「ev」はevidence(証拠)の事らしい。実行時には何らかのインスタンスが渡ってくるので、それが渡ってくるのは「コンパイルが通った証拠」というようなニュアンスなんだろうか。
| 記号 | 説明 | 
|---|---|
A =:= B | 
		AとBが等しい。 | 
A <:< B | 
		AはBの子クラス(サブクラス)である。 | 
A <%< B | 
		Aは暗黙変換によってBになれる。 | 
なお、これらの記号の実態は、Predefに定義されたクラス。(Predefに定義されているので、自動的に使える状態になっている)
(型パラメーターが2個のクラスでは中置記法が使える。つまり「C[A,B]」は「A C B」と書ける。Cが記号なら「=:=[A,B]」「A 
=:= B」ということになる)
これらに適合させる為の暗黙変換関数もPredef内に定義されている。
Javaでは配列は共変なので、String配列(String[])をObject配列の変数(Object[])に代入することが出来る。
また、Javaのジェネリクスは共変でないので、例えばStringのList(List<String>)はObjectのListの変数(List<Object>)に代入することは出来ない。
(StringはObjectを継承しているが、StringのコレクションはObjectのコレクションに代入できるか?という問題。なお、Javaでは配列が共変なのは危険だとされている。
Scalaの配列はJavaと異なり、非変である)
Scalaでは、変位指定(変位指定アノテーション、variance annotations)によって非変(nonvariant)・共変(covariant)・反変(contravariant)を指定することが出来る。
例として、A1←A2←A3という継承関係のクラスがあるとする。
class A1 class A2 extends A1 class A3 extends A2
| Scalaの例 | Java相当 | 備考 | 
|---|---|---|
		class MyList[T] { } var list:MyList[A2] = _ × list = new MyList[A1] ○ list = new MyList[A2] × list = new MyList[A3]  | 
		
		class MyList<T> {
}
		MyList<A2> list = null; × list = new MyList<A1>(); ○ list = new MyList<A2>(); × list = new MyList<A3>();  | 
		型パラメーターに特に何も指定しないと、非変(nonvariant)になる。 この場合、変数に指定されている型にしか代入できない。 (ちなみに、ScalaのListは(不変オブジェクトなので)共変)  | 
	
		List<String> list = new ArrayList<String>();  | 
	||
		class MyArray[+T] { } var array:MyArray[A2] = _ × array = new MyArray[A1] ○ array = new MyArray[A2] ○ array = new MyArray[A3]  | 
		
		A2[] array = null; × array = new A1[] {};
○ array = new A2[] {};
○ array = new A3[] {};
		 | 
		型パラメーターの前に「+」(共変アノテーション)を付けると共変(covariant)になる。 子クラスのコレクションを親クラスのコレクションに代入できる。 関連?→共変戻り値型  | 
	
		Object[] array = new String[] {};
		 | 
	||
		class MyList[-T] { } var list:MyList[A2] = _ ○ list = new MyList[A1] ○ list = new MyList[A2] × list = new MyList[A3]  | 
		型パラメーターの前に「-」(反変アノテーション)を付けると反変(contravariant)になる。 親クラスのコレクションを子クラスのコレクションに代入できる。  | 
	
参考: Naotsuguさんの型パラメータの変位指定 …特に最後のまとめが使い分けの参考になる
Javaのジェネリクスでは出来ない事がある。Scalaではどうか?[/2013-06-08]
| Scala相当 | Javaの例 | 備考 | |
|---|---|---|---|
| クラス取得 | 
		//×出来ない(コンパイルエラー) class Example[T] { def printClass(): Unit = { val c = classOf[T] println(c) } }  | 
		
		//×出来ない(コンパイルエラー)
public class Example<T> {
  public void printClass() {
    final Class c = T.class;
    System.out.println(c);
  }
}
		 | 
		型パラメーターからjava.lang.Classを取得することは出来ない。 | 
		class Example[T](implicit m: ClassTag[T]) { def printClass(): Unit = { val c = m.runtimeClass println(c) } } val s = new Example[String] s.printClass()  | 
		ScalaではClassTagを使えば型パラメーターを受け取ることが出来るので、 そこからjava.lang.Class(型消去されたもの)を取得できる。  | 
	||
| new | 
		//×出来ない(コンパイルエラー) class Example[T] { def create(): T = { new T() } }  | 
		
		//×出来ない(コンパイルエラー)
public class Example<T> {
  public T create() {
    return new T();
  }
}
		 | 
		型パラメーターからnewでインスタンスを生成する事は出来ない。 | 
		class Example[T](implicit m: ClassTag[T]) { def create(): T = { m.runtimeClass.newInstance().asInstanceOf[T] } } val s = new Example[String] val v = s.create()  | 
		ClassTagを使えばjava.lang.Classが取得できるので、 後はリフレクション(newInstance())を用いればインスタンスを生成できる。 ただし、型パラメーターに指定するクラスに引数無しのpublicコンストラクターが必要。 (例えばIntは生成できない)  | 
	||
		class Example[T](implicit m: ClassTag[T]) { def create(size:Int): Array[T] = { m.newArray(size) } } val s = new Example[Int] val a = s.create(10)  | 
		
		//×出来ない(コンパイルエラー)
public class Example<T> {
  public T[] create(int size) {
    return new T[size];
  }
}
		 | 
		配列の生成なら、ClassTagにそれ専用のメソッドがある。 | |
| クラス判断 | 
		//×コンパイルは通るが、正しく動作しない class Example[T] { def check(o:AnyRef): Boolean = { o.isInstanceOf[T] } }  | 
		
		//×出来ない(コンパイルエラー)
public class Example<T> {
  public boolean check(Object o) {
    return (o instanceof T);
  }
}
		 | 
		型パラメーターを使って、インスタンスがその型かどうかを判断する事は出来ない。 | 
		def check(o:AnyRef): Boolean = o match { case _:T => true case _ => false }  | 
		instanceOfでの判断と同様、期待した動作にならない。[2011-06-25] | ||
		class Example[T](implicit m: ClassTag[T]) { def check(o:AnyRef): Boolean = { (o ne null) && m.runtimeClass.isInstance(o) } } val d = new Example[java.util.Date] assert( d.check(new java.util.Date()) ) assert( d.check(new java.sql.Date(0)) ) assert(!d.check("abc") ) assert(!d.check(null) ) val q = new Example[java.sql.Date] assert( q.check(new java.sql.Date(0)) ) assert(!q.check(new java.util.Date()) )  | 
		ClassTagを使えばjava.lang.Classが取得できるので、 リフレクション(isInstance())を用いればクラスを判断できる。  | 
	||
		class Example[T](implicit m: ClassTag[T]) { def check[S](o:S)(implicit s: ClassTag[S]): Boolean = { m.runtimeClass.isAssignableFrom(s.runtimeClass) } }  | 
		双方のClassTagを取得してみた例。 | ||
| static | 
		//×出来ない(コンパイルエラー)
public class Example<T> {
  private static T value;
}
		
  | 
		Javaではstaticなフィールドやメソッドにはクラスの型パラメーターを適用できない。 が、Scalaにはstaticなメンバーという概念は無いので、無関係。  |