型アノテーション


Scalaは型推論をサポートしています。

Javaで以下のような宣言をしたとします。

Map<Integer, String> map = new HashMap<Integer, String>();

このような宣言がScalaでは以下のようになります。

val map: Map[Integer, String] = new HashMap

この例では左辺で使用する型を宣言しています。
インスタンス化しているHashMapに再び型を書く必要はありません。

ここで重要なのはMapではなくHashMapだとすると次のように書くこともできます。

val map = new HashMap[Integer, String]

この場合は右辺に必要な型情報がすべて存在するので、左辺に型を記述する必要はありません。

このようにScalaでは多くの型はコンパイラによって推論されます。
もちろん明示できに型を宣言することもできます。これを型アノテーションと呼びます。

この型推論はメソッドにも適用されます。ほとんどの場合メソッドの戻り値の型は推論されます。
しかし、メソッドのパラメータにはすべて型アノテーションが必要です。

このように多くの場合に型を推論してくれるScalaですが、すべての場合で型を推論してくれるわけではありません。 次に明示的な型アノテーションが必要な場合を示します。

  • 変数を宣言する場合。しかし、次のように宣言する場合は除く 例:val hello = "Hello Scala"
  • すべてのメソッド引数
  • 次の場合のメソッドの戻り値
    • メソッド内で明示的にreturnを呼ぶとき
    • メソッドが再帰的である場合
    • メソッドが多重定義されており、多重定義されたメソッドの1つが別の多重定義されたメソッドを呼び出すとき。 (「呼び出し側のメソッド」に戻り値の型アノテーションが必要)
    • 推論された戻り値の型が意図した型よりも汎用的な型、たとえばAny型になるかもしれないとき。

次に以下の例を見てみましょう。

def double(i: Int) {
  2 * i
}

このdouble関数の戻り値はInt型ではありません。 戻り値の型はUnit型になります。

なぜでしょう。

それは等記号(イコール)を入れ忘れたからです。

def double(i: Int) = {
  2 * i
}

このようにすると戻り値の型はInt型になります。

この振る舞いには次のような理由があります。
Scalaは本体の前に等記号を持つメソッドを見るつけるとそれを関数の定義としてみなします。
そして、関数型プログラミングにおいて関数は常に値を返します。

その一方でScalaは本体の前に等記号が無いメソッドを見つけるとそれを「手続き」の定義としてみなします。
手続きはUnit型の戻り値しか持たず副作用を伴う処理を実行するものです。

よって先ほどのdouble関数は「手続き」とみなされたために戻り値の型がUnitになっていました。