Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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
like image 460
Lernkurve Avatar asked Feb 24 '14 10:02

Lernkurve


People also ask

What is a list in Groovy?

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.

What does string mean in Groovy?

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.

What is ArrayList in Groovy?

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)


2 Answers

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.

like image 192
tim_yates Avatar answered Oct 20 '22 08:10

tim_yates


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
like image 31
ataylor Avatar answered Oct 20 '22 07:10

ataylor