Groovy 3.0 发布说明

Groovy 3.0 带来了一个全新的解析器(代号为 Parrot)以及许多其他新功能和能力。

Parrot 解析器

Groovy 3.0 有一个新的解析器,它比 Groovy 以前版本中的解析器更灵活、更易于维护。它被称为 Parrot 解析器,因为在解析器创建的早期阶段,目标是让新解析器的输出与旧解析器的输出完全一致。新解析器自此已扩展为支持额外的语法选项和语言特性。一些新功能包括

  • do-while 循环;增强的(现在支持逗号)经典 for 循环,例如 for(int i = 0, j = 10; i < j; i++, j--) {..})

  • lambda 表达式,例如 stream.map(e → e + 1)

  • 方法引用和构造函数引用

  • try-with-resources,也称为 ARM

  • 代码块,即 {..}

  • Java 样式的数组初始化器,例如 new int[] {1, 2, 3}

  • 接口中的默认方法

  • 类型注释的更多位置

  • 新运算符:恒等运算符 (===, !==),Elvis 赋值 (?=),!in!instanceof

  • 安全索引,例如 nullableVar?[1, 2]

  • 非静态内部类实例化,例如 outer.new Inner()

  • 运行时 groovydoc,即带有 @Groovydoc 的 groovydoc;作为元数据的 groovydoc 附加到 AST 节点

附注:Parrot 基于 antlr4 (com.tunnelvisionlabs:antlr4) 的高度优化版本,该版本在 BSD 许可下发布。

do/while 循环

现在支持 Java 的类 do/while 循环。示例

// classic Java-style do..while loop
def count = 5
def fact = 1
do {
    fact *= count--
} while(count > 1)
assert fact == 120

增强的经典 Java 样式 for 循环

现在支持使用逗号分隔表达式更复杂的 Java 经典 for 循环形式。示例

def facts = []
def count = 5
for (int fact = 1, i = 1; i <= count; i++, fact *= i) {
    facts << fact
}
assert facts == [1, 2, 6, 24, 120]

for 循环结合多重赋值

自 Groovy 1.6 以来,Groovy 就支持多重赋值语句

// multi-assignment with types
def (String x, int y) = ['foo', 42]
assert "$x $y" == 'foo 42'

现在这些语句可以出现在 for 循环中

// multi-assignment goes loopy
def baNums = []
for (def (String u, int v) = ['bar', 42]; v < 45; u++, v++) {
    baNums << "$u $v"
}
assert baNums == ['bar 42', 'bas 43', 'bat 44']

Java 样式的数组初始化

Groovy 一直支持使用方括号的文字列表/数组定义,并且避免了 Java 样式的花括号,以免与闭包定义冲突。然而,在花括号紧跟在数组类型声明之后的情况下,与闭包定义不存在歧义,因此现在也支持 Java 样式。

示例

def primes = new int[] {2, 3, 5, 7, 11}
assert primes.size() == 5 && primes.sum() == 28
assert primes.class.name == '[I'

def pets = new String[] {'cat', 'dog'}
assert pets.size() == 2 && pets.sum() == 'catdog'
assert pets.class.name == '[Ljava.lang.String;'

// traditional Groovy alternative still supported
String[] groovyBooks = [ 'Groovy in Action', 'Making Java Groovy' ]
assert groovyBooks.every{ it.contains('Groovy') }

Java 样式的 Lambda 语法

现在支持 Java 语法中的 lambda 表达式。

示例

(1..10).forEach(e -> { println e })

assert (1..10).stream()
                .filter(e -> e % 2 == 0)
                .map(e -> e * 2)
                .toList() == [4, 8, 12, 16, 20]

支持正常的变体,Groovy 添加了额外的功能,例如默认参数值

// general form
def add = (int x, int y) -> { def z = y; return x + z }
assert add(3, 4) == 7

// curly braces are optional for a single expression
def sub = (int x, int y) -> x - y
assert sub(4, 3) == 1

// parameter types are optional
def mult = (x, y) -> x * y
assert mult(3, 4) == 12

// no parentheses required for a single parameter with no type
def isEven = n -> n % 2 == 0
assert isEven(6)
assert !isEven(7)

// no arguments case
def theAnswer = () -> 42
assert theAnswer() == 42

// any statement requires braces
def checkMath = () -> { assert 1 + 1 == 2 }
checkMath()

// example showing default parameter values (no Java equivalent)
def addWithDefault = (int x, int y = 100) -> x + y
assert addWithDefault(1, 200) == 201
assert addWithDefault(1) == 101

实现细节和静态优化

对于动态 Groovy,lambda 表达式会转换为等效的 Groovy 闭包。因此 (e) → { println e }{e → println e} 相同。为了在使用 @CompileStatic 时提供更类似 Java 的体验,我们支持静态 Groovy 的本机 lambda 表达式。

方法引用

现在支持使用双冒号语法的 Java 8 方法引用语法。让我们先看一下支持的一些情况,然后再介绍一些实现细节。

以下示例说明了引用类的静态方法和实例方法

import java.util.stream.Stream

// class::staticMethod
assert ['1', '2', '3'] ==
        Stream.of(1, 2, 3)
                .map(String::valueOf)
                .toList()

// class::instanceMethod
assert ['A', 'B', 'C'] ==
        ['a', 'b', 'c'].stream()
                .map(String::toUpperCase)
                .toList()

以下示例说明了引用实例变量的方法

// instance::instanceMethod
def sizeAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'::length
assert sizeAlphabet() == 26

// instance::staticMethod
def hexer = 42::toHexString
assert hexer(127) == '7f'

以下示例说明了引用构造函数

// normal constructor
def r = Random::new
assert r().nextInt(10) in 0..9

// array constructor refs are handy when working with various Java libraries, e.g. streams
assert [1, 2, 3].stream().toArray().class.name == '[Ljava.lang.Object;'
assert [1, 2, 3].stream().toArray(Integer[]::new).class.name == '[Ljava.lang.Integer;'

// works with multi-dimensional arrays too
def make2d = String[][]::new
def tictac = make2d(3, 3)
tictac[0] = ['X', 'O', 'X']
tictac[1] = ['X', 'X', 'O']
tictac[2] = ['O', 'X', 'O']
assert tictac*.join().join('\n') == '''
XOX
XXO
OXO
'''.trim()

// also useful for your own classes
import groovy.transform.Canonical
import java.util.stream.Collectors

@Canonical
class Animal {
    String kind
}

def a = Animal::new
assert a('lion').kind == 'lion'

def c = Animal
assert c::new('cat').kind == 'cat'

def pets = ['cat', 'dog'].stream().map(Animal::new)
def names = pets.map(Animal::toString).collect(Collectors.joining( "," ))
assert names == 'Animal(cat),Animal(dog)'

实现细节和静态优化

虽然在大多数情况下你可以忽略实现细节,但在某些场景下,了解方法引用背后的实现是有帮助的。对于动态 Groovy,方法引用被实现为闭包方法引用。因此 String::toUpperCaseString.&toUpperCase 相同。为了在使用 @CompileStatic 时提供更类似 Java 的体验,我们支持静态 Groovy 的本机方法引用。

对于此示例(使用 JDK 12 中的 String.transform

@groovy.transform.CompileStatic
def method() {
  assert 'Hi'.transform(String::toUpperCase) == 'HI'
}

编译器会生成与 Java 为这种情况生成的字节码非常相似的字节码(对于字节码专家而言,涉及 INVOKEDYNAMIC、方法句柄和 LambdaMetafactory)。如果您已经在使用 @CompileStatic 来提高编译时类型安全或性能,那么代码在语义上将是等效的,但会像 Java 一样进行优化。

如果您有使用动态特性的代码,那么您不应将 @CompileStatic 与您的方法引用一起使用,例如

def convertCase(boolean upper, String arg) {
    arg.transform(String::"${upper ? 'toUpperCase' : 'toLowerCase'}")
}
assert convertCase(true, 'Hi') == 'HI'
assert convertCase(false, 'Bye') == 'bye'

因为这里的 GString 会阻止编译器知道如何编写所需的优化代码。注意:此示例有点牵强,可以重构为调用两个优化方法引用之一,但希望您能理解其中的意思。

如果您想使用动态实现背后的闭包特性,则也适用相同的警告,例如

def upper = String::toUpperCase
assert upper('hi') == 'HI'
def upperBye = upper.curry('bye')
assert upperBye() == 'BYE'

!in 和 !instanceof 运算符

当需要否定形式时,除了必须将包含 ininstanceof 中缀运算符的表达式括起来并将感叹号运算符放在括号前面之外,现在还支持内联变体。示例

/* assert !(45 instanceof Date) // old form */
assert 45 !instanceof Date

assert 4 !in [1, 3, 5, 7]

Elvis 赋值运算符

Groovy 引入了 Elvis 运算符 示例

import groovy.transform.ToString

@ToString
class Element {
    String name
    int atomicNumber
}

def he = new Element(name: 'Helium')
he.with {
    name = name ?: 'Hydrogen'   // existing Elvis operator
    atomicNumber ?= 2           // new Elvis assignment shorthand
}
assert he.toString() == 'Element(Helium, 2)'

恒等比较运算符

===!== 都受支持,它们分别与调用 is() 方法和否定调用 is() 方法相同。

import groovy.transform.EqualsAndHashCode

@EqualsAndHashCode
class Creature { String type }

def cat = new Creature(type: 'cat')
def copyCat = cat
def lion = new Creature(type: 'cat')

assert cat.equals(lion) // Java logical equality
assert cat == lion      // Groovy shorthand operator

assert cat.is(copyCat)  // Groovy identity
assert cat === copyCat  // operator shorthand
assert cat !== lion     // negated operator shorthand

安全索引

String[] array = ['a', 'b']
assert 'b' == array?[1]      // get using normal array index
array?[1] = 'c'              // set using normal array index
assert 'c' == array?[1]

array = null
assert null == array?[1]     // return null for all index values
array?[1] = 'c'              // quietly ignore attempt to set value
assert null == array?[1]

def personInfo = [name: 'Daniel.Sun', location: 'Shanghai']
assert 'Daniel.Sun' == personInfo?['name']      // get using normal map index
personInfo?['name'] = 'sunlan'                  // set using normal map index
assert 'sunlan' == personInfo?['name']

personInfo = null
assert null == personInfo?['name']              // return null for all map values
personInfo?['name'] = 'sunlan'                  // quietly ignore attempt to set value
assert null == personInfo?['name']

"var" 保留类型

Groovy 支持 def 类型占位符。它可以与字段、局部变量、方法参数以及方法的返回值类型一起使用。在动态 Groovy 中,当类型在编译时被认为不重要时,您使用 def - 正常的运行时类型仍然适用。对于静态 Groovy,它在类型推断优于显式类型时使用。

在 Groovy 3.0 中,可以使用新的类型占位符:var。它提供了与 Java 10 中的 var 保留类型等效的语法(但您可以从 JDK 8 开始将它与 Groovy 3 一起使用)。它可以用于字段、局部变量和参数。它还可以用于 lambda 参数(Java 11 功能)。在所有情况下,它都可以被视为 def 的别名。

var two = 2                                                      // Java 10
IntFunction<Integer> twice = (final var x) -> x * two            // Java 11
assert [1, 2, 3].collect{ twice.apply(it) } == [2, 4, 6]
注意
孵化状态:var@CompileStatic 一起使用被认为是孵化功能。目前,它是 def 的直接别名,这意味着在这种情况下,类型推断将可用,这在大多数情况下会产生与 Java 相似的行为。在涉及流类型的情况下,Groovy 的行为与 Java 不同。孵化状态表示我们保留更改这些流类型情况下的行为的权利。虽然一些用户表示希望在将 var@CompileStatic 和流类型一起使用时,行为更接近 Java,但我们目前认为更改行为的额外复杂性是不值得的。但是,我们仍在探索该领域的可能性。

ARM try-with-resources

Groovy 通常为 Java 7 的 try-with-resources 语句提供了更好的替代方案,用于自动资源管理 (ARM)。该语法现在支持迁移到 Groovy 的 Java 程序员,并且仍然希望使用旧的样式

class FromResource extends ByteArrayInputStream {
    @Override
    void close() throws IOException {
        super.close()
        println "FromResource closing"
    }

    FromResource(String input) {
        super(input.toLowerCase().bytes)
    }
}

class ToResource extends ByteArrayOutputStream {
    @Override
    void close() throws IOException {
        super.close()
        println "ToResource closing"
    }
}

def wrestle(s) {
    try (
            FromResource from = new FromResource(s)
            ToResource to = new ToResource()
    ) {
        to << from
        return to.toString()
    }
}

def wrestle2(s) {
    FromResource from = new FromResource(s)
    try (from; ToResource to = new ToResource()) { // Enhanced try-with-resources in Java 9+
        to << from
        return to.toString()
    }
}

assert wrestle("ARM was here!").contains('arm')
assert wrestle2("ARM was here!").contains('arm')

这将产生以下输出

ToResource closing
FromResource closing
ToResource closing
FromResource closing

嵌套代码块

Java 中一个不常用的结构是匿名代码块。它通常不被推荐,因为它通常表明需要将相关代码重构为方法。但它有时在限制作用域方面很有用,并且现在可以在 Groovy 中使用

{
    def a = 1
    a++
    assert 2 == a
}
try {
    a++ // not defined at this point
} catch(MissingPropertyException ex) {
    println ex.message
}
{
    {
        // inner nesting is another scope
        def a = 'banana'
        assert a.size() == 6
    }
    def a = 1
    assert a == 1
}

但请注意,在 Groovy 中,在任何方法调用之后都有一个看起来像代码块的结构将被视为尝试将闭包作为方法调用中的最后一个参数传递。即使在新行之后也是如此。因此,在任何其他块(例如 if-then-else 语句或另一个匿名代码块)之后开始一个匿名代码块都是安全的。在任何其他地方,您可能需要用分号终止之前的语句。在这种情况下,请参阅上面关于重构代码的说明!:-)

Java 样式的非静态内部类实例化

现在支持 Java 语法中的非静态内部类实例化。

public class Computer {
    public class Cpu {
        int coreNumber

        public Cpu(int coreNumber) {
            this.coreNumber = coreNumber
        }
    }
}

assert 4 == new Computer().new Cpu(4).coreNumber

接口默认方法

Java 8 支持向接口添加默认实现。Groovy 的 traits 机制为继承实现行为提供了更强大的 OO 抽象,但 Java 用户现在已经熟悉了默认方法,因此 Groovy 现在支持相同的语法

interface Greetable {
    String target()

    default String salutation() {
        'Greetings'
    }

    default String greet() {
        "${salutation()}, ${target()}"
    }
}

class Greetee implements Greetable {
    String name
    @Override
    String target() { name }
}

def daniel = new Greetee(name: 'Daniel')
assert 'Greetings, Daniel' == "${daniel.salutation()}, ${daniel.target()}"
assert 'Greetings, Daniel' == daniel.greet()
注意
孵化状态:虽然此功能将保留,但其当前实现使用 traits,并且处于孵化状态。它产生的结果与 Java 实现相同,但字节码不那么紧凑。我们仍在探索也支持接口中本机默认方法的方法。

配置新解析器的系统属性

  • 可以在 Groovy 3.x 中将 groovy.antlr4 设置为 false 以禁用新解析器(如果需要,通过 JAVA_OPTS 设置)。此属性在正常使用中不需要,但是,至少在最初,如果您有一个有问题的源文件似乎无法与新解析器一起使用,您也许可以还原到旧解析器来编译该文件。您将无法使用旧解析器中的任何新语言特性。旧解析器已弃用,将在 Groovy 4 中删除。

  • groovy.attach.groovydoc:在解析 Groovy 源代码时是否将 groovydoc 附加到节点作为元数据(默认值:false)

  • groovy.attach.runtime.groovydoc:是否将 @Groovydoc 注释附加到所有具有 groovydoc 的成员(即 /** …​ */

  • groovy.antlr4.cache.threshold:清除 DFA 缓存的频率,该缓存用于存储解析过程中的符号信息(默认值:64)。DFA 缓存清除的越频繁,解析性能越差,但使用的内存越少。实现可能会限制阈值,使其不低于某个最小值。
    注意:这是一种高级内部设置,它会影响解析器的内存分配行为。只有在编译大型 Groovy 文件时遇到内存问题时,您才需要调整此值。

  • groovy.clear.lexer.dfa.cache:到达阈值后是否清除 Groovy 词法分析器的 DFA 缓存(默认值:false)
    注意:这是一种高级内部设置,它会影响解析器的内存分配行为。只有在编译大型 Groovy 文件时遇到内存问题时,您才需要调整此值。

GDK 改进

Groovy 向现有的 Java 类添加了许多扩展方法。在 Groovy 3 中,添加了大约 80 种这样的扩展方法。我们在这里仅重点介绍其中的一部分

数组和可迭代对象上的 average()

assert 3 == [1, 2, 6].average()

字符串、CharSequence 和 GString 上的 takeBetween()

assert 'Groovy'.takeBetween( 'r', 'v' ) == 'oo'

数组和可迭代对象上的 shuffle()shuffled()

def orig = [1, 3, 5, 7]
def mixed = orig.shuffled()
assert mixed.size() == orig.size()
assert mixed.toString() ==~ /\[(\d, ){3}\d\]/

Future 上的 collect{ }

Future<String> foobar = executor.submit{ "foobar" }
Future<Integer> foobarSize = foobar.collect{ it.size() } // async
assert foobarSize.get() == 6

LocalDate 上的 minus()

def xmas = LocalDate.of(2019, Month.DECEMBER, 25)
def newYear = LocalDate.of(2020, Month.JANUARY, 1)
assert newYear - xmas == 7 // a week apart

其他改进

@NullCheck AST 转换

允许自动向方法或构造函数添加空检查保护,以确保所有参数都使用非空值提供 (GROOVY-8935)。

嵌入式 Groovydoc

您现在可以通过多种方式嵌入 Groovydoc 注释

  • 它们可以在 AST 中提供,供 AST 变换和其他工具使用。我们改进的 groovydoc 工具(仍在开发中)就是基于此功能。在幕后,groovydoc 内容存储为节点元数据,但简单的 API 隐藏了此实现细节。此功能通过使用 `groovy.attach.groovydoc` 系统属性或 `CompilerConfiguration` 中的相应标志来启用。

  • 以特殊 `/**@` 开头注释分隔符开头的 Groovydoc 注释也可以嵌入到类文件中(在幕后它存储在 @Groovydoc 注解中),并且在运行时可通过反射或其他工具访问。这通过使用 `groovy.attach.runtime.groovydoc` 系统属性或 `CompilerConfiguration` 中的相应标志来启用。这提供了一种受 Ruby 等语言启发的 Groovy 功能,可以将文档嵌入到标准二进制 jar 中,因此始终可用,而不是依赖于单独的 javadoc jar。

以下是一个演示在 AST 中访问 groovydoc 注释的示例

import org.codehaus.groovy.control.*

def cc = new CompilerConfiguration(optimizationOptions:
    [(CompilerConfiguration.GROOVYDOC): true])

def ast = new CompilationUnit(cc).tap {
    addSource 'myScript.groovy', '''
        /** class doco */
        class MyClass {
            /** method doco */
            def myMethod() {}
        }
    '''
    compile Phases.SEMANTIC_ANALYSIS
}.ast

def classDoc = ast.classes[0].groovydoc
assert classDoc.content.contains('class doco')
def methodDoc = ast.classes[0].methods[0].groovydoc
assert methodDoc.content.contains('method doco')

以下是一个演示运行时 groovydoc(设置和未设置标志)的示例

import org.codehaus.groovy.control.*

def extract(shell) {
    shell.evaluate( '''
        /**@
         * Some class groovydoc for Foo
         */
        class Foo {}
        Foo.class
        '''
    ).groovydoc.content.replaceAll('[^\\w\\s]', '').trim()
}

// first without the flag set
assert extract(new GroovyShell()) == ''

// now with embedding turned on
def cc = new CompilerConfiguration(optimizationOptions:
    [(CompilerConfiguration.RUNTIME_GROOVYDOC): true])
assert extract(new GroovyShell(cc)) == 'Some class groovydoc for Foo'

JSR308 改进(正在进行中)

Groovy 在最近的版本中一直在改进对 JSR-308 的支持。作为实现新语法的部分,已添加了其他支持。

拆分包更改(来自 beta-2)

Java 平台模块系统要求不同模块中的类具有不同的包名。Groovy 有自己的“模块”,但这些模块在历史上并没有根据上述要求进行结构化。因此,在使用 JDK9+ 时,应将 Groovy 2.x 和 3.0 添加到类路径而不是模块路径。这将 Groovy 的类放入未命名模块中,其中不会强制执行拆分包命名要求。

Groovy 3 正在进行更改,以允许代码库向符合规则的方向发展,并允许 Groovy 用户开始迁移过程。Groovy 4 是我们完全符合规范的工件的目标版本,但您可以在使用 Groovy 3 的同时开始提前准备您的类。

作为此更改的一部分,一些类正在移动包。在那些移动类的一个子集中,Groovy 3 拥有两个这样的类副本是有意义的

  • 一个具有旧包名的已弃用类

  • 一个具有新包名的新的类

这可以帮助迁移。在许多情况下,您将能够在不更改的情况下重新编译现有的 Groovy 类,它们将使用旧版本的类。您可能会注意到弃用警告,具体取决于您如何编辑类。您应该尽快迁移,因为如果您还没有迁移到新类位置,您的类可能不再在 Groovy 4 下编译。请注意,在某些情况下,即使在 Groovy 3 下也需要一些工作。请阅读下面表格中的“注意事项”列以获取更多详细信息。

原始类/包名(如果适用,3.0 及更低版本)

新复制的类/包名
(3.0 及更高版本)

注意事项

模块:groovy

groovy.xml.QName

groovy.namespace

您需要在迁移到使用该类作为方法参数(包括 `groovy-ant` 和 `groovy-xml`)的受影响模块的同时,迁移到使用新类,但仅当您使用具有 `QName` 参数的方法时。您可以继续在现有代码或使用受影响类的旧版本中使用旧类,直到 Groovy 4。

模块:groovy-ant

groovy.util

groovy.ant

在使用 `AntBuilder` 的类/脚本中添加 `import groovy.ant.AntBuilder`,否则您将仍然使用已弃用的版本。

模块:groovy-console

groovy.ui.ConsoleApplet

N/A

`java.applet` API 已弃用。在 Groovy 4 中没有计划为该 Groovy 类提供替代品。

groovy.inspect

groovy.console

`groovyConsole` 通常用作命令行工具,其在这种形式下的使用不受影响。如果您直接使用任何类,则可以在迁移之前使用旧版本。您不应混合使用旧类和新类。

groovy.inspect.swingui

groovy.console.ui

groovy.ui

groovy.console.ui

模块:groovy-groovysh

org.codehaus.groovy.tools.shell

org.apache.groovy.groovysh

`groovysh` 通常用作命令行工具,其在这种形式下的使用不受影响。如果您直接使用任何类,则可以在迁移之前使用旧版本。您不应混合使用旧类和新类。

模块:groovy-jmx

groovy.util.GroovyMBean

groovy.jmx

您需要在 Groovy 4 之前添加 `GroovyMBean` 的导入。请随时在您自己的代码中使用旧类,但 `JmxBuilder` 仅使用新类。您不应混合使用旧类和新类。

模块:groovy-nio

org.codehaus.groovy.runtime.
NioGroovyMethods

org.apache.groovy.nio.extensions.
NioExtensions

在正常使用情况下,相关的扩展方法将从新位置自动可用。

org.codehaus.groovy.runtime.
WritablePath

org.apache.groovy.nio.runtime

我们建议您通过其接口引用 `WritablePath`,在这种情况下您无需执行任何操作。如果您必须引用该类,我们建议更改导入并重新编译所有受影响的类。如果这很困难,您可以在准备好迁移之前使用旧类(并直接使用相关的 `NioGroovyMethods` 方法)。您不应混合使用旧类和新类。

模块:groovy-swing

org.codehaus.groovy.binding

org.apache.groovy.swing.binding

如果您在现有代码中或从仍在使用旧类的旧类中使用旧类,您可以继续使用它们。`SwingBuilder` 现在使用新类。

groovy.model

groovy.swing.model

groovy.inspect.swingui

org.apache.groovy.swing.table

模块:groovy-test

org.codehaus.groovy.runtime.
ScriptTestAdapter

org.apache.groovy.test

如果旧类已经在使用中,则它仍然可以用于您自己的类,但不会被 Groovy 3 的 JUnit 相关测试套件类识别。

groovy.transform.
NotYetImplemented

groovy.test.
NotYetImplemented

两者都指向(移动但其他方面不变的)AST 变换类。

groovy.util

groovy.test

对于像 `GroovyTestCase` 这样的类,您需要导入 `groovy.test.GroovyTestCase` 以不获取已弃用的版本。您需要在 Groovy 4 之前执行此操作。

groovy.lang

groovy.test

模块:groovy-xml

groovy.util

groovy.xml

对于像 `XmlParser` 和 `XmlSlurper` 这样的类,您需要分别导入 `groovy.xml.XmlParser` 和 `groovy.xml.XmlSlurper` 以不获取已弃用的版本。您需要在 Groovy 4 之前执行此操作。如果您在 `groovy.util.slurpersupport.GPathResult` 上使用 `groovy.xml.XmlUtil.serialize`,则您需要切换到使用 `groovy.util.XmlUtil` 中的已弃用方法,因为 `groovy.xml.XmlUtil` 仅处理新类。

org.codehaus.groovy.tools.xml.DomToGroovy

org.apache.groovy.xml.tools

其他重大更改

除了拆分包更改之外,还存在以下其他重大更改

  • 对于 JDK13+ 用户,请考虑使用 `stripIndent(true)` 而不是 `stripIndent()` (GROOVY-9423)

  • 如果 Groovy switch 语句有默认分支,现在要求它成为最后一个分支

  • 如果您扩展 `ProcessingUnit` 并覆盖 `setConfiguration`,请改用覆盖 `configure` (GROOVY-9122)

  • 如果您覆盖 `GroovyClassLoader`,请注意 `sourceCache` 和 `classCache` 的类型已从 `Map` 更改为更强的类型 (GROOVY-9112)

  • 您可能会注意到 Groovy 工具的帮助输出以及使用 Picocli 的 CliBuilder 使用的空格位置的一些细微更改 (GROOVY-8925)

  • 遍历字符串在静态和动态 Groovy 之间变得一致 (GROOVY-8882)

  • Groovy 3 的 Alpha 版本错误地允许您在打印空映射时省略方括号,但现在需要它们,例如 `println([:])`

  • Groovy 过去在发行版中捆绑了 picocli 的版本,但这将人们锁定在使用提供的版本上。您现在可能需要在某些脚本中添加额外的 `@Grab`。(GROOVY-9165)

  • 为了避免通常不必要的导入处理,`ImportCustomizer` 每模块应用一次,而不是以前每类应用一次 (GROOVY-8399)。如果您需要旧的行为,请参阅 (GROOVY-9407) 中的解决方法。

已知问题

  • 3.0.0 缺少在 JDK9+ 上完整运行而无需警告所需的某些类。

JDK 要求

Groovy 3.0 要求 JDK9+ 进行构建,并且 JDK8 是我们支持的 JRE 的最低版本。

更多信息

3.0.3 附录

使用 `@NotYetImplemented` 注解的 JUnit 3 用户应考虑以下方法之一

  • 坚持使用旧的/已弃用的 `@groovy.transform.NotYetImplemented` 注解

  • 如果您使用的是现在推荐的 `@groovy.test.NotYetImplemented` 注解,请使用 `exception=junit.framework.AssertionFailedError` 注解属性

  • 考虑升级到 JUnit 4 或 5

有关更多详细信息,请参阅 (GROOVY-9492).

3.0.4 附录

我们将 TestNG 的 Groovy 依赖项提升到 7.2.0。不幸的是,在发布时,该版本仅存在于 jcenter 中,而不是 Maven central 中。如果您使用的是 groovy-all pom 或 bom,则可以将 jcenter 添加到您的配置中作为存储库,如果它还没有列出

Gradle:build.gradle
repositories {
    jcenter()
    ...
}
Maven:pom.xml
<repositories>
  <repository>
    <id>central</id>
    <name>bintray</name>
    <url>http://jcenter.bintray.com</url>
  </repository>
</repositories>

或者,如果您没有使用 TestNG,您可以排除 `groovy-testng`,例如

Gradle:build.gradle
dependencies {
    implementation("org.codehaus.groovy:groovy-all:3.0.4") {
        exclude(group: 'org.codehaus.groovy', module: 'groovy-testng')
    }
}
Maven:pom.xml
<dependencies>
  <dependency>
    <groupId>org.codehaus.groovy</groupId>
    <artifactId>groovy-all</artifactId>
    <version>3.0.4</version>
    <scope>compile</scope>
    <exclusions>
      <exclusion>
        <groupId>org.codehaus.groovy</groupId>
        <artifactId>groovy-testng</artifactId>
      </exclusion>
    </exclusions>
  </dependency>
</dependencies>

3.0.5 附录

潜在的意外依赖项更改

  • 我们已将 TestNG 恢复到 7.1.0,因此 3.0.4 版本说明中提到的解决方法不再需要。如果您特别需要该版本的 TestNG,您可以排除 7.1.0 并显式包含 7.2.0。

重大更改

  • 如果您使用的是 `SecureASTCustomizer` 并依赖于错误消息的精确措辞,例如在测试中,那么您可能需要调整这些测试中的措辞 (GROOVY-9594).

  • 如果您使用的是 `groovy-cli-picocli` 模块或对 Groovy 的大多数命令行工具(例如 `groovy`、`groovyc`、`groovysh`、`groovydoc` 等)进行脚本编写,并且您依赖于错误消息的精确措辞,例如在测试中,那么您可能需要调整这些测试中的措辞 (GROOVY-9627).

  • Groovy 现在更符合 JavaBeans 规范,适用于涉及任何名称以大写字母开头的字段的边缘情况 (GROOVY-9618).

3.0.8 附录

重大更改

  • 解析器中的一个回归问题已修复,该问题区分了特定边缘情况下的变量声明和命令表达式,以与 Groovy 3 之前的行为一致。以前,像 `foo bar` 和 `_foo bar` 这样的表达式被视为命令表达式,而 `Foo bar` 被认为是变量表达式。`foo` 和 `Foo` 情况保持不变,但 `_foo` 情况意外地发生了翻转。任何依赖于翻转行为的人员都应更改其代码以与之前的行为一致。边缘情况仅涉及具有显式类型的变量声明,其中类型以美元符号或下划线字符开头。(GROOVY-9936).