list - collection - groovy map collect
¿La lista y la lista<String> son iguales en Groovy? (3)
Como @tim_yates observa, es posible habilitar las verificaciones de tiempo de compilación con las anotaciones @TypeChecked
/ @CompileStatic
.
Otra alternativa es habilitar la verificación del tipo de tiempo de ejecución envolviendo la colección con Collections.checkedList()
. Si bien esto no usa los genéricos o el tipo declarado, su cumplimiento en el tiempo de ejecución a veces encaja mejor con el código dinámico escrito de forma flexible. Esta es una característica de la plataforma Java no específica para groovy.
Ejemplo:
// 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
Pregunta 1
¿Es irrelevante si se utiliza una List
(lista de objetos) o una List<String>
(lista de Cadenas) en Groovy?
En el ejemplo de código a continuación, ambas listas terminan siendo una ArrayList
(ArrayList de objetos). Habría esperado que la segunda lista fuera una ArrayList<String>
(ArrayList of Strings).
¿Groovy pierde la información de tipo cuando se compila la clase y la deduce cuando se ejecuta la clase compilada?
Ejemplo 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()}"
Salida 1
Untyped list List: class java.util.ArrayList
Typed list List<String>: class java.util.ArrayList // Would have expected ArrayList<String>
Pregunta 2
Hubiera esperado que la línea typedList << new Integer(1)
en el ejemplo a continuación falle con una excepción porque estoy tratando de poner un int
en una lista de cadenas. ¿Alguien puede explicar por qué puedo agregar un int
a la List
tipos de String
?
La salida muestra que sigue siendo un Integer
, es decir, no se convierte sobre la marcha a una String
"1".
Ejemplo 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() }
Salida 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
Cuando se ejecuta Groovy "normalmente" , los genéricos se desechan antes de la compilación, por lo que solo existen en la fuente como recordatorios útiles para el desarrollador.
Sin embargo, puede usar @CompileStatic
o @TypeChecked
para hacer que Groovy @TypeChecked
estos genéricos y verifique los tipos de cosas en la compilación.
Como ejemplo, considérese que tengo la siguiente estructura de proyecto:
project
|---- src
| |---- main
| |---- groovy
| |---- test
| |---- ListDelegate.groovy
| |---- Main.groovy
|---- build.gradle
Con el código:
construir.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
}
}
Ahora, ejecutar gradle runSimple
nos da la salida:
:compileJava UP-TO-DATE
:compileGroovy
:processResources UP-TO-DATE
:classes
:runSimple
[1, tim]
BUILD SUCCESSFUL
Total time: 6.644 secs
Como puede ver, los genéricos se desecharon, y simplemente funcionó agregando Integers
y Strings
a la List
de supuestamente solo Integers
Ahora, si cambiamos ListDelegate.groovy
a:
package test
import groovy.transform.*
@CompileStatic
class ListDelegate<T> {
@Delegate List<T> numbers = []
}
Y corre otra vez:
:compileJava UP-TO-DATE
:compileGroovy
:processResources UP-TO-DATE
:classes
:runSimple
[1, tim]
BUILD SUCCESSFUL
Total time: 6.868 secs
Obtenemos la misma salida !! Esto se debe a que, si bien ListDelegate
ahora está compilado de forma estática, nuestra clase Main
sigue siendo dinámica, por lo que aún desecha los genéricos antes de construir ListDelegate
... Por lo tanto, también podemos cambiar Main.groovy
a:
package test
import groovy.transform.*
@CompileStatic
class Main {
static main( args ) {
def del = new ListDelegate<Integer>()
del << 1
del << ''tim''
println del
}
}
Y ahora, volviendo a ejecutar gradle runSimple
danos:
: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
Lo que es, como cabría esperar, no agregar una String
a nuestra Lista de Integer
declarada.
De hecho, solo necesita CompileStatic
la clase Main.groovy
y este error se Main.groovy
, pero siempre me gusta usarlo donde pueda, no solo donde lo necesito.
De Wikipedia , para Java:
Los genéricos se verifican en tiempo de compilación para ver si son correctos. La información de tipo genérico se elimina luego en un proceso llamado borrado de tipo . Por ejemplo, la Lista se convertirá a la Lista de tipo no genérico, que normalmente contiene objetos arbitrarios. La verificación en tiempo de compilación garantiza que el código resultante es de tipo correcto.
Esta información de tipo es para el compilador y el IDE. Groovy se basa en Java y hereda los mismos principios para los genéricos.
Por otro lado, Groovy es un lenguaje más dinámico, por lo que probablemente sea la razón por la que no verifica los tipos en el tiempo de compilación. OMI para Groovy es una especie de comentario de código, a veces muy útil.
PS @tim_yates sugirió un enlace a los documentos de Groovy sobre Genéricos , que confirma:
Groovy actualmente hace un poco más y desecha la información genérica "en el nivel de origen".