Question 1
Is it irrelevant whether a List
(list of objects) or a List<String>
(list of Strings) is used in Groovy?
In the code example below, both lists end up being an ArrayList
(ArrayList of objects). Would have expected the second list to be an ArrayList<String>
(ArrayList of Strings).
Does Groovy lose the type information when the class is compiled and infer it when the compiled class is executed?
Example 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()}"
Output 1
Untyped list List: class java.util.ArrayList
Typed list List<String>: class java.util.ArrayList // Would have expected ArrayList<String>
Question 2
I would have expected the line typedList << new Integer(1)
in the example below to fail with an exception because I'm trying to put an int
into a list of Strings. Can anyone explain why I can add an int
to the String
-typed List
?
The output shows that it remains an Integer
, i.e. it's not on-the-fly converted to a String
"1".
Example 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() }
Output 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
The List is a structure used to store a collection of data items. In Groovy, the List holds a sequence of object references. Object references in a List occupy a position in the sequence and are distinguished by an integer index.
A String literal is constructed in Groovy by enclosing the string text in quotations. Groovy offers a variety of ways to denote a String literal. Strings in Groovy can be enclosed in single quotes ('), double quotes (“), or triple quotes (“””). Further, a Groovy String enclosed by triple quotes may span multiple lines.
By default, Groovy creates an instance of java. util. ArrayList. However, we can also specify the type of list to create: def linkedList = [1,2,3] as LinkedList ArrayList arrList = [1,2,3] Next, lists can be used to create other lists by using a constructor argument: def copyList = new ArrayList(arrList)
When running Groovy "normally", generics are thrown away before compilation, so only exist in the source as helpful reminders to the developer.
However, you can use @CompileStatic
or @TypeChecked
to make Groovy honour these Generics and check the types of things at compilation.
As an example, consider I have the following project structure:
project
|---- src
| |---- main
| |---- groovy
| |---- test
| |---- ListDelegate.groovy
| |---- Main.groovy
|---- build.gradle
With the code:
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
}
}
Now, running gradle runSimple
gives us the output:
:compileJava UP-TO-DATE
:compileGroovy
:processResources UP-TO-DATE
:classes
:runSimple
[1, tim]
BUILD SUCCESSFUL
Total time: 6.644 secs
So as you can see, the generics were thrown away, and it just worked adding Integers
and Strings
to out List
of supposedly only Integers
Now, if we change ListDelegate.groovy
to:
package test
import groovy.transform.*
@CompileStatic
class ListDelegate<T> {
@Delegate List<T> numbers = []
}
And run again:
:compileJava UP-TO-DATE
:compileGroovy
:processResources UP-TO-DATE
:classes
:runSimple
[1, tim]
BUILD SUCCESSFUL
Total time: 6.868 secs
We get the same output!! This is because whilst ListDelegate
is now statically compiled, our Main
class is still dynamic, so still throws away generics before constructing the ListDelegate
... So we can also change Main.groovy
to:
package test
import groovy.transform.*
@CompileStatic
class Main {
static main( args ) {
def del = new ListDelegate<Integer>()
del << 1
del << 'tim'
println del
}
}
And now re-running gradle runSimple
give us:
: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
Which is, as you'd expect, failing to add a String
to our declared List of Integer
.
In fact, you only need to CompileStatic
the Main.groovy
class and this error will be picked up, but I always like to use it where I can, not just where I need to.
As @tim_yates notes, it is possible to enable compile time checks with the @TypeChecked
/@CompileStatic
annotations.
Another alternative is to enable runtime type checking by wrapping the collection with Collections.checkedList()
. While this doesn't use the generics or the declared type, enforcing it at runtime sometimes fits in better with loosely typed dynamic code. This is a Java platform feature not specific to groovy.
Example:
// 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With