Are List and List<String> the same in Groovy?

arraylist, groovy, list

Solution

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.

Problem

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

Original source

Related problems