web-dev-qa-db-ja.com

ListとList <String>はGroovyで同じですか?

質問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を入れようとしているため、例外を除いて失敗すると予想していました。 intString-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
_
16
Lernkurve

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

ご覧のとおり、ジェネリックは破棄され、IntegersStringsListから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を追加できないことです。

実際、CompileStaticMain.groovyクラスを実行するだけで、このエラーが検出されますが、必要な場所だけでなく、できる限り使用するのが好きです。

24
tim_yates

@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
_
4
ataylor

Wikipedia から、Javaの場合:

ジェネリックスは、コンパイル時に型が正しいかどうかがチェックされます。次に、ジェネリック型情報は 型消去 と呼ばれるプロセスで削除されます。たとえば、Listは、通常は任意のオブジェクトを含む非ジェネリック型のListに変換されます。コンパイル時のチェックにより、結果のコードがタイプ正しいことが保証されます。

このタイプ情報は、コンパイラーおよびIDE用です。 GroovyはJavaに基づいており、ジェネリックスについても同じ原則を継承しています。

一方、Groovyはより動的な言語であるため、おそらく、コンパイル時に型をチェックしないのはそのためです。 GroovyのIMOは、ある種のコードコメントであり、非常に役立つ場合があります。

PS @tim_yatesは、 Genericsに関するGroovyドキュメント へのリンクを提案しました。

Groovyは現在、もう少し進んで、ジェネリック情報を「ソースレベルで」破棄しています。

3
Igor Artamonov