Are List and List the same in Groovy?

前端 未结 3 1868
無奈伤痛
無奈伤痛 2020-12-10 11:32

Question 1

Is it irrelevant whether a List (list of objects) or a List (list of Strings) is used in Groovy?

相关标签:
3条回答
  • 2020-12-10 12:21

    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
    
    0 讨论(0)
  • 2020-12-10 12:28

    From Wikipedia, for Java:

    Generics are checked at compile-time for type-correctness. The generic type information is then removed in a process called type erasure. For example, List will be converted to the non-generic type List, which ordinarily contains arbitrary objects. The compile-time check guarantees that the resulting code is type-correct.

    This type information is for compiler and IDE. Groovy is based on Java and inherits same principles for generics.

    At other hand, Groovy is more dynamic language, so probably, it's the reason why it doesn't check types on compile time. IMO for Groovy it's some kind of code comment, sometimes very useful.

    PS @tim_yates suggested a link to Groovy docs about Generics, that confirms:

    Groovy currently does a little further and throws away generics information "at the source level".

    0 讨论(0)
  • 2020-12-10 12:33

    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.

    0 讨论(0)
提交回复
热议问题