JVM上で動く言語でありながら、まるでPHPの様にサクサクコーディングが出来る素敵な言語です。
強い動的型付けである為、型の宣言をする必要が無く、ソースがスッキリします。
ただし型の宣言は省略出来るのであって、宣言出来ない訳ではありません。つまり、
「型の指定はしなくても良いけど、する事も出来る」
という訳です。
じゃあ型を指定する場合は一体何が嬉しいの?という点を今回纏めてみました。
以下早速サンプルソースです。動的型付け言語の特徴として、コンパイル時点ではなく、実行時点でなければエラーの検出が出来ないという特徴を活かして説明しています。
*2012/02/12 追記:PHPでは仮引数でクラスやインタフェースを指定する事が出来ます。
型を指定しない場合
def test(a){
println a;
a = a.toLong() //Stringが渡されるとこの時点で例外が発生
println a.getClass()
}
test(1)
test("a")
以下、コンパイルと実行
[koji:groovy]$ groovyc type_check.groovy
[koji:groovy]$ groovy type_check
1
class java.lang.Long
a
Caught: java.lang.NumberFormatException: For input string: "a"
java.lang.NumberFormatException: For input string: "a"
at type_check.test(type_check.groovy:4)
at type_check$test.callCurrent(Unknown Source)
at type_check.run(type_check.groovy:10)
[koji:groovy]$ groovy type_check
1
class java.lang.Long
a
Caught: java.lang.NumberFormatException: For input string: "a"
java.lang.NumberFormatException: For input string: "a"
at type_check.test(type_check.groovy:4)
at type_check$test.callCurrent(Unknown Source)
at type_check.run(type_check.groovy:10)
test(1)は問題なく実行され、test("a")はメソッドの途中まで実行されて、toLong()を実行するタイミングで例外が発生しています。
コレはString型にはtoLong()メソッドが存在しない為です。
型を指定する場合
def test(Integer a){
println a;
a = a.toLong()
println a.getClass()
}
test(1)
test("a") //そもそもStringをとるtestメソッドが存在しないのでこの時点で例外が発生
println a;
a = a.toLong()
println a.getClass()
}
test(1)
test("a") //そもそもStringをとるtestメソッドが存在しないのでこの時点で例外が発生
以下、コンパイルと実行
[koji:groovy]$ groovyc type_check.groovy
[koji:groovy]$ groovy type_check
1
class java.lang.Integer
Caught: groovy.lang.MissingMethodException: No signature of method: type_check.test() is applicable for argument types: (java.lang.String) values: [a]
Possible solutions: test(java.lang.Integer), getAt(java.lang.String), wait(), use([Ljava.lang.Object;), wait(long), is(java.lang.Object)
groovy.lang.MissingMethodException: No signature of method: type_check.test() is applicable for argument types: (java.lang.String) values: [a]
Possible solutions: test(java.lang.Integer), getAt(java.lang.String), wait(), use([Ljava.lang.Object;), wait(long), is(java.lang.Object)
at type_check.run(type_check.groovy:10)
[koji:groovy]$
[koji:groovy]$ groovy type_check
1
class java.lang.Integer
Caught: groovy.lang.MissingMethodException: No signature of method: type_check.test() is applicable for argument types: (java.lang.String) values: [a]
Possible solutions: test(java.lang.Integer), getAt(java.lang.String), wait(), use([Ljava.lang.Object;), wait(long), is(java.lang.Object)
groovy.lang.MissingMethodException: No signature of method: type_check.test() is applicable for argument types: (java.lang.String) values: [a]
Possible solutions: test(java.lang.Integer), getAt(java.lang.String), wait(), use([Ljava.lang.Object;), wait(long), is(java.lang.Object)
at type_check.run(type_check.groovy:10)
[koji:groovy]$
test(1)は問題なく実行されていますが、こちらの場合はtest("a")を実行したタイミングで例外が発生しています。
コレはtest()というメソッド名でStringを受け取るメソッドが存在しないために発生しているエラーです。
ざっくりまとめると、
前者の場合:PHP等の動的型付けなスクリプト言語では一般的なパターン
後者の場合:Java等の静的型付けなお固い言語では一般的なパターン
となります。
ただし注意しなければ成らないのが、後者の場合でもGroovyのコンパイルでは型チェックが出来ないという事です。プログラムが実行され、該当箇所が実際に実行されるまでエラーになるかどうか分かりません。
コレはGroovyが動的型付け言語であるためです。(型チェックが出来ないのは前者も同様)
例えば、以下のように後者のパターンの実行方法を変えた場合にはエラーは発生しません。
test(1)
if( 0 ) {
test("a") //ここは実行されないからプログラム的にはエラー無し
}
if( 0 ) {
test("a") //ここは実行されないからプログラム的にはエラー無し
}
じゃあ態々メソッドの引数に型の指定をしてもメリット無いじゃないかと思われるかもしれませんが、当然メリットもあります。
まず、型を指定しておくと実行時例外にはなるものの、メソッドの中身は実行されないため、渡された引数が本当に意図した型なのかという事をメソッドの中で一々チェックしなてくても良い点。
2点目が、Groovyで作成したライブラリなどをJavaで利用する時。Groovyのソース上で型を指定しておくとJava側で意図しない値(今回の例であればString)を渡すようなプログラムを書いた場合、Javaのコンパイル時点でその誤りを検出出来ます。
Groovyでメソッドの引数に型を指定しない場合の前者、型を指定した場合の後者のjavapは以下のようになります。(該当メソッド部分のみ抽出)
前者の場合
[koji:groovy]$ javap type_check | grep test
public java.lang.Object test(java.lang.Object);
public java.lang.Object test(java.lang.Object);
後者の場合
[koji:groovy]$ javap type_check | grep test
public java.lang.Object test(java.lang.Integer);
public java.lang.Object test(java.lang.Integer);
どういった場合に型を指定するかはプログラマや状況によるとは思いますが、基本的には仮引数に関しては型を指定してあげた方が良いと思います。