【问题标题】:Are List and List<String> the same in Groovy?Groovy 中的 List 和 List<String> 是否相同?
【发布时间】:2014-03-25 21:57:24
【问题描述】:

问题 1

在 Groovy 中使用 List(对象列表)还是 List&lt;String&gt;(字符串列表)是否无关紧要?

在下面的代码示例中,两个列表最终都是ArrayList(对象的ArrayList)。本来希望第二个列表是ArrayList&lt;String&gt;(字符串数组列表)。

Groovy 在编译类时会丢失类型信息,并在编译类执行时进行推断吗?

示例 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()}"

输出 1

Untyped list List:       class java.util.ArrayList
Typed list List<String>: class java.util.ArrayList // Would have expected ArrayList<String>

问题 2

我预计下面示例中的行 typedList &lt;&lt; new Integer(1) 会因异常而失败,因为我试图将 int 放入字符串列表中。谁能解释为什么我可以将int 添加到String-typed List

输出显示它仍然是一个Integer,即它没有即时转换为String“1”。

示例 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() }

输出 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

【问题讨论】:

  • @tim_yates:所以根据您发布的链接的答案是“[...] 这意味着类型参数在编译时未检查并且在运行时不可用。”这意味着我不能阻止任何人将int 放入List&lt;String&gt; 中,这反过来意味着我只能确定List&lt;String&gt; 包含object 类型的对象。对吗?
  • 是的。除非你使用@CompileStatic

标签: list arraylist groovy


【解决方案1】:

来自Wikipedia,对于 Java:

在编译时检查泛型的类型正确性。通用的 然后在名为type erasure 的进程中删除类型信息。例如,List 将被转换为 非泛型 List,通常包含任意对象。 编译时检查保证生成的代码是 类型正确。

此类型信息适用于编译器和 IDE。 Groovy 基于 Java 并继承了相同的泛型原则。

另一方面,Groovy 是更动态的语言,所以可能这就是它不在编译时检查类型的原因。对于 Groovy,IMO 是某种代码注释,有时非常有用。

PS @tim_yates 建议了一个指向Groovy docs about Generics 的链接,这证实了:

Groovy 目前做得更进一步,“在源代码级别”丢弃了泛型信息。

【讨论】:

    【解决方案2】:

    当运行 Groovy“正常” 时,泛型在编译之前被丢弃,因此只存在于源代码中,作为对开发人员的有用提醒。

    但是,您可以使用 @CompileStatic@TypeChecked 让 Groovy 尊重这些泛型并在编译时检查事物的类型。

    例如,假设我有以下项目结构:

    project
     |---- src
     |      |---- main
     |             |---- groovy
     |                    |---- test
     |                           |---- ListDelegate.groovy
     |                           |---- Main.groovy
     |---- build.gradle
    

    附上代码:

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

    现在,运行 gradle runSimple 会给我们输出:

    :compileJava UP-TO-DATE
    :compileGroovy
    :processResources UP-TO-DATE
    :classes
    :runSimple
    [1, tim]
    
    BUILD SUCCESSFUL
    
    Total time: 6.644 secs
    

    如您所见,泛型已被丢弃,它只是将IntegersStrings 添加到List 中,据说只有Integers

    现在,如果我们将 ListDelegate.groovy 更改为:

    package test
    
    import groovy.transform.*
    
    @CompileStatic
    class ListDelegate<T> {
        @Delegate List<T> numbers = []
    }
    

    然后再次运行:

    :compileJava UP-TO-DATE
    :compileGroovy
    :processResources UP-TO-DATE
    :classes
    :runSimple
    [1, tim]
    
    BUILD SUCCESSFUL
    
    Total time: 6.868 secs
    

    我们得到相同的输出!!这是因为虽然ListDelegate 现在是静态编译的,但我们的Main 类仍然是动态的,所以在构造ListDelegate 之前仍然会丢弃泛型......所以我们也可以将Main.groovy 更改为:

    package test
    
    import groovy.transform.*
    
    @CompileStatic
    class Main {
        static main( args ) {
            def del = new ListDelegate<Integer>()
            del << 1
            del << 'tim'
            println del
        }
    }
    

    现在重新运行gradle runSimple 给我们:

    :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
    

    正如您所料,没有将String 添加到我们声明的Integer 列表中。

    其实,你只需要CompileStaticMain.groovy这个类,这个错误就会被捡起来,但我总是喜欢在我能用的地方使用它,而不仅仅是我需要的地方。

    【讨论】:

    • 耶茨先生的回答真棒!
    • 非常有用,如果我的方法返回 [boolean, String] 的列表,在方法定义中我应该省略返回类型还是将其设置为 List
    • 我会将它包装在一个新类中,其中包含一个布尔值和一个字符串?
    【解决方案3】:

    正如@tim_yates 所说,可以使用@TypeChecked/@CompileStatic 注释启用编译时检查。

    另一种选择是通过使用Collections.checkedList() 包装集合来启用运行时类型检查。虽然这不使用泛型或声明的类型,但在运行时强制执行它有时更适合松散类型的动态代码。这是一个 Java 平台功能,并非特定于 groovy。

    例子:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-11-21
      • 2023-03-18
      • 1970-01-01
      • 1970-01-01
      • 2010-12-05
      • 2022-01-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多