Являются ли List и List <String> одинаковыми в Groovy?

Вопрос 1

Не имеет значения, используется ли List (список объектов) или List<String> (список строк) в Groovy?

В приведенном ниже примере кода оба списка заканчиваются как ArrayList (ArrayList объектов). Ожидал бы, что второй список будет ArrayList<String> (ArrayList of Strings).

Означает ли 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

Ответы

Ответ 1

При запуске Groovy "обычно" генераторы отбрасываются перед компиляцией, поэтому существуют только в источнике как полезные напоминания разработчику.

Однако вы можете использовать @CompileStatic или @TypeChecked, чтобы сделать Groovy честь этих Generics и проверить типы вещей при компиляции.

В качестве примера рассмотрим, что у меня есть следующая структура проекта:

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, и эта ошибка будет поднята, но мне всегда нравится использовать ее там, где я могу, а не только там, где мне нужно.

Ответ 2

От Wikipedia, для Java:

Общие данные проверяются во время компиляции для правильности ввода. Общий Затем информация типа затем удаляется в процессе, называемом type Стирание. Например, List будет преобразован в не общий тип List, который обычно содержит произвольные объекты. Проверка времени компиляции гарантирует, что полученный код тип-правильно.

Данные этого типа предназначены для компилятора и IDE. Groovy основан на Java и наследует те же принципы для дженериков.

С другой стороны, Groovy является более динамичным языком, поэтому, вероятно, это причина, по которой он не проверяет типы во время компиляции. IMO для Groovy это своего рода комментарий к коду, иногда очень полезный.

PS @tim_yates предложил ссылку на Groovy docs о Generics, которая подтверждает:

Groovy в настоящее время делает немного дальше и отбрасывает информацию генериков "на уровне источника".

Ответ 3

Как отмечает @tim_yates, можно включить проверку времени компиляции с помощью аннотаций @TypeChecked/@CompileStatic.

Другой альтернативой является включение проверки типа времени выполнения путем переноса коллекции с помощью Collections.checkedList(). Хотя это не использует дженерики или объявленный тип, принудительное выполнение во время выполнения иногда лучше подходит для слабо типизированного динамического кода. Это функция платформы Java, не относящаяся к groovy.

Пример:

// 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