Scalaの特徴を紹介し、基本構文や関数、クラスなど、Scalaの基本的な機能について解説する入門連載
前回の記事「Scalaのトレイトでプログラマをミックスインしてやんよ」では、Scalaの重要な機能であるトレイトの基本的な使い方から始まり、複数のトレイトを積み重ねた場合にどのような順番で線形化されるかについても紹介しました。今回は型のパラメータ化について紹介します。
第1回記事では、Scala標準のREPLとScala IDEで動作を確認してみました。今後本記事のサンプルコードは、どちらで確認しても問題はありませんが、対話的に実行でき、1文ごとにコードの結果が分かって便利なので、基本的にはREPLを用いて説明していきます。
Scala IDEを使用する場合、第1回記事の『Scala IDE for Eclipseで「Hello Scala!」』を参照してプロジェクトを作成して実行してください。REPLを使用する場合は、コンソール上でscalaコマンドを実行し、REPLを起動してください。
一般的にはコンストラクタやメソッドを使用するとき、引数を使用してパラメータ化します。型のパラメータ化とは、それと同じようにクラスやメソッドなどで使用する型そのものをパラメータ化して再利用性を高める機能です。
※この機能はJavaでは「Generics(ジェネリック/総称型)」と呼ばれており、JDK 1.5から使えます。JavaでのGenericsについて知りたい方は、記事「キュー構造をJavaで実装してジェネリック型を理解する」などをご覧ください。
型パラメータを使ってみましょう。コレクションでよく使用するListは型パラメータを指定し、String型や自分で定義した型など、いろいろな型を指定できます。REPLでString型を格納するList型変数を定義します。
scala> val sList = List[String]("a","b","c") sList: List[java.lang.String] = List(a, b, c)
「List[型名](初期値)」として、Listの型パラメータを指定しています。ここでは型パラメータのStringを明示的に指定していますが、指定しない場合でも型推論によりList[String]として判断されます。
scala> val sList = List("a","b","c") sList: List[java.lang.String] = List(a, b, c)
Listの先頭を取り出すと、ちゃんとString型として取得できます。
scala> sList.head res2: java.lang.String = a
しかし、型パラメータ名を指定しないと、誤ってString以外の要素を渡そうとした場合にList[Any]型として定義されてしまいます。
scala> val sList = List("a","b",1) sList: List[Any] = List(a, b, 1)
String型以外の型を想定していなければ、おそらくsListを使用する個所のメソッド呼び出しでコンパイルエラーが発生して間違いに気付くことになるでしょう。ちゃんと定義時に型パラメータをString型として指定した場合、String型以外の要素を渡そうとすると、コンパイル時にエラーになります。
scala> val sList = List[String]("a","b",1) <console>:7: error: type mismatch; found : Int(1) required: String val sList = List[String]("a","b",1)
このように、型パラメータを適切に使えば、ScalaのListは「型安全(誤った型の状態を引き起こす処理や変換を許さない状態)」に操作できます。
下記のように、Listを型パラメータを指定せず、初期値でString型とInt型を格納した場合、List[Any]で定義されます。
scala> val list = List("a","b",1) list: List[Any] = List(a, b, 1)
この場合、要素を任意の型で取り出したいときは、どうすればいいのでしょうか。Javaの場合、instanceof演算子でその変数の実際の型を調べたり、(型)変数とすることでキャストできます。
//Javaの場合 Object o = new String("hello"); if(o instanceof String) { System.out.println((String)o); }
Scalaの場合、Anyが持つisInstanceOfメソッドを使用することで、変数の実際の型を調べられます。
scala> list(0).isInstanceOf[Int] res16: Boolean = false scala> list(0).isInstanceOf[String] res17: Boolean = true
「isInstanceOf[型名]」として、その型名が合っていればtrue、そうでなければfalseが返ります。また、キャストはasInstanceOfメソッドで可能です。
scala> list(0).asInstanceOf[String] res18: String = a
しかし、これらのメソッドは実際あまり使いません。型の確認やキャストをしたい場合、連載第4回で解説した「パターンマッチ」を使った方がシンプルに記述できるからです。まずは、パターンマッチを検討してみてください。
型パラメータの使い方は分かったので、次は型パラメータを使用したクラスを定義してみましょう。1つの型パラメータ「A」を取る、「MySample」というクラスを定義してみます。
class MySample[A]{ var param:A = _ def get:A = param def set(param:A) = this.param = param }
型パラメータは、クラス名とコンストラクタ引数の間に型パラメータ名の指定を追加します。上記の例でいうと、MySampleクラスは「A」という何らかの型を取るという意味です。型パラメータはカンマ区切りで、いくつでも記述可能です。
なお、Scalaにおいて型パラメータは、Aから順番に名前を付けることが慣習となっているようです。
ではMySampleクラスをインスタンス化して使用してみましょう。インスタンス化時に、[]を使用して型パラメータに実際の型を指定します。下記サンプルではMySampleの型パラメータAに対してStringを指定しています。
scala> val x = new MySample[String] x: MySample[String] = MySample@c595bcd scala> x.set("hello") scala> x.get res5: String = hello
これでMySampleのparamフィールドはString型として使えるようになります。setメソッドはString型を受け取るようになり、getメソッドはString型を返すようになります。
型パラメータを2つ指定するクラスを「new」でインスタンスする場合、少し変わった書き方ができます。例えば、型パラメータを2つ取る「OR」というクラスがあった場合には、クラス名を中央に書いてその前後に型パラメータを書けます。
class Collect class Error class OR[L,R] scala> new (Collect OR Error) res0: OR[Collect,Error] = OR@4ee00c09
ORという“演算子”を使っているようにも見えますが、実際には「OR」クラスをインスタンス化しています。
次に、メソッドに対して型パラメータを指定してみましょう。クラスやトレイトと同じく、メソッドも型をパラメータ化できます。メソッドの場合、メソッド名と引数の間に型パラメータを指定します。
メソッドの場合、メソッド名と引数の間に型パラメータを指定します。構文は下記のようになっています。
def 関数名[A](引数):戻り値 = 関数本体
では、型パラメータを1つ取れる関数funcを定義してみます。この関数は引数として受け取った値を、そのまま返すだけです。また、引数の型は型パラメータとして指定できます。
scala> def func[A](arg:A):A = arg func: [A](arg: A)A
では、関数funcを使ってみましょう。メソッドを呼ぶときに型パラメータを指定しなくても、型推論によりうまく動作してくれます。
scala> func("hello") //型推論により型パラメータがStringとして解釈される res1: java.lang.String = hello
上記の場合、func[String]("hello")と記述したことと同じ意味になります。
なお、型パラメータを明示的に指定するには、引数の前に[]で型を記述します。
scala> func[Int](10) res2: Int = 10
ここでは型パラメータを使用したクラスやメソッドを定義して使ってみました。型パラメータをうまく使用することで汎用性が上がったり、コレクションを便利に扱えることが分かります。次ページでは型パラメータに制限を加える方法について紹介します。
Copyright © ITmedia, Inc. All Rights Reserved.