質問1
GroovyでList
(オブジェクトのリスト)と_List<String>
_(文字列のリスト)のどちらが使用されているかは関係ありませんか?
以下のコード例では、両方のリストが最終的にArrayList
(オブジェクトのArrayList)になります。 2番目のリストは_ArrayList<String>
_(文字列のArrayList)であると予想していました。
Groovyは、クラスがコンパイルされるときに型情報を失い、コンパイルされたクラスが実行されるときにそれを推測しますか?
例1
_List untypedList = ["a", "b", "c"]
List<String> typedList = ["a", "b", "c"]
println "Untyped list List: ${untypedList.getClass()}"
println "Typed list List<String>: ${typedList.getClass()}"
_
出力1
_Untyped list List: class Java.util.ArrayList
Typed list List<String>: class Java.util.ArrayList // Would have expected ArrayList<String>
_
質問2
以下の例の行typedList << new Integer(1)
は、文字列のリストにint
を入れようとしているため、例外を除いて失敗すると予想していました。 int
をString
-typed List
に追加できる理由を誰かが説明できますか?
出力は、それがInteger
のままであることを示しています。つまり、オンザフライでString
"1"に変換されていません。
例2
_List untypedList = ["a", "b", "c"]
List<String> typedList = ["a", "b", "c"]
untypedList << new Integer(1)
typedList << new Integer(1) // Why does this work? Shouldn't an exception be thrown?
println "Types:"
println "Untyped list List: ${untypedList.getClass()}"
println "Typed list List<String>: ${typedList.getClass()}"
println "List contents:"
println untypedList
println typedList
println "Untyped list:"
untypedList.each { println it.getClass() }
println "Typed list:"
typedList.each { println it.getClass() }
_
出力2
_Types:
Untyped list List: class Java.util.ArrayList
Typed list List<String>: class Java.util.ArrayList
List contents:
[a, b, c, 1]
[a, b, c, 1]
Untyped list:
class Java.lang.String
class Java.lang.String
class Java.lang.String
class Java.lang.Integer
Typed list:
class Java.lang.String
class Java.lang.String
class Java.lang.String
class Java.lang.Integer
_
Groovy "normally"を実行すると、ジェネリックはコンパイル前に破棄されるため、開発者への役立つリマインダーとしてのみソースに存在します。
ただし、@CompileStatic
または@TypeChecked
を使用して、Groovyにこれらのジェネリックを尊重させ、コンパイル時に種類を確認することができます。
例として、次のプロジェクト構造があるとします。
project
|---- src
| |---- main
| |---- groovy
| |---- test
| |---- ListDelegate.groovy
| |---- Main.groovy
|---- build.gradle
コードで:
build.gradle
apply plugin: 'groovy'
repositories {
mavenCentral()
}
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.2.1'
}
task( runSimple, dependsOn:'classes', type:JavaExec ) {
main = 'test.Main'
classpath = sourceSets.main.runtimeClasspath
}
ListDelegate.groovy
package test
class ListDelegate<T> {
@Delegate List<T> numbers = []
}
Main.groovy
package test
class Main {
static main( args ) {
def del = new ListDelegate<Integer>()
del << 1
del << 'tim'
println del
}
}
ここで、gradle runSimple
を実行すると、次の出力が得られます。
:compileJava UP-TO-DATE
:compileGroovy
:processResources UP-TO-DATE
:classes
:runSimple
[1, tim]
BUILD SUCCESSFUL
Total time: 6.644 secs
ご覧のとおり、ジェネリックは破棄され、Integers
とStrings
をList
からIntegers
に追加するだけで機能しました。
ここで、ListDelegate.groovy
を次のように変更すると、次のようになります。
package test
import groovy.transform.*
@CompileStatic
class ListDelegate<T> {
@Delegate List<T> numbers = []
}
そして、もう一度実行します。
:compileJava UP-TO-DATE
:compileGroovy
:processResources UP-TO-DATE
:classes
:runSimple
[1, tim]
BUILD SUCCESSFUL
Total time: 6.868 secs
同じ出力が得られます!!これは、ListDelegate
が静的にコンパイルされている間、Main
クラスはまだ動的であるため、ListDelegate
...を構築する前にジェネリックを破棄するためです。したがって、Main.groovy
を変更することもできます。に:
package test
import groovy.transform.*
@CompileStatic
class Main {
static main( args ) {
def del = new ListDelegate<Integer>()
del << 1
del << 'tim'
println del
}
}
そして今、gradle runSimple
を再実行すると、次のようになります。
:compileJava UP-TO-DATE
:compileGroovy
startup failed:
/Users/tyates/Code/Groovy/generics/src/main/groovy/test/Main.groovy: 10:
[Static type checking] - Cannot find matching method test.ListDelegate#leftShift(Java.lang.String).
Please check if the declared type is right and if the method exists.
@ line 10, column 9.
del << 'tim'
^
1 error
:compileGroovy FAILED
これは、ご想像のとおり、宣言されたString
のリストにInteger
を追加できないことです。
実際、CompileStatic
Main.groovy
クラスを実行するだけで、このエラーが検出されますが、必要な場所だけでなく、できる限り使用するのが好きです。
@tim_yatesが指摘しているように、_@TypeChecked
_/_@CompileStatic
_アノテーションを使用してコンパイル時チェックを有効にすることができます。
もう1つの方法は、コレクションを Collections.checkedList()
でラップして、ランタイム型チェックを有効にすることです。これはジェネリックまたは宣言された型を使用しませんが、実行時に強制することは、緩く型付けされた動的コードに適している場合があります。これは、Groovyに固有ではないJavaプラットフォーム機能です。
例:
_// no type checking:
list1 = ["a", "b", "c"]
list1 << 1
assert list1 == ["a", "b", "c", 1]
// type checking
list2 = Collections.checkedList(["a", "b", "c"], String)
list2 << 1
// ERROR Java.lang.ClassCastException:
// Attempt to insert class Java.lang.Integer element into collection with element type class Java.lang.String
_
Wikipedia から、Javaの場合:
ジェネリックスは、コンパイル時に型が正しいかどうかがチェックされます。次に、ジェネリック型情報は 型消去 と呼ばれるプロセスで削除されます。たとえば、Listは、通常は任意のオブジェクトを含む非ジェネリック型のListに変換されます。コンパイル時のチェックにより、結果のコードがタイプ正しいことが保証されます。
このタイプ情報は、コンパイラーおよびIDE用です。 GroovyはJavaに基づいており、ジェネリックスについても同じ原則を継承しています。
一方、Groovyはより動的な言語であるため、おそらく、コンパイル時に型をチェックしないのはそのためです。 GroovyのIMOは、ある種のコードコメントであり、非常に役立つ場合があります。
PS @tim_yatesは、 Genericsに関するGroovyドキュメント へのリンクを提案しました。
Groovyは現在、もう少し進んで、ジェネリック情報を「ソースレベルで」破棄しています。