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 节点

P.S. Parrot 基于高度优化的 antlr4(com.tunnelvisionlabs:antlr4),该库采用 BSD 许可。

do/while 循环

现在支持 Java 的 class 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 从 Groovy 1.6 开始就支持多重赋值语句。

// 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 在涉及流类型的情况下有所不同。孵化状态表明我们保留更改这些流类型用例行为的权利。尽管一些用户表示希望在使用 @CompileStatic 和流类型时 var 的行为更接近 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()

字符串、字符序列和 GStrings 上的 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 中相应的标志启用。这在 Groovy 中提供了一种受 Ruby 等语言启发的特性,这些语言可以将文档嵌入到标准二进制 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 应添加到 classpath 而不是 module path。这将 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-antgroovy-xml,但仅当您使用带有 QName 参数的方法时。您可以在现有代码中或与受影响类的旧版本一起继续使用旧类,直到 Groovy 4。

模块:groovy-ant

groovy.util

groovy.ant

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

模块:groovy-console

groovy.ui.ConsoleApplet

不适用

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

对于像 XmlParserXmlSlurper 这样的类,您需要分别导入 groovy.xml.XmlParsergroovy.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 语句有 default 分支,现在要求它是最后一个分支

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

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

  • 您可能会注意到 Groovy 工具的帮助输出和 Picocli 的 CliBuilder 用法在空白定位方面有一些细微变化 (GROOVY-8925)

  • 在静态和动态 Groovy 之间,对 String 的迭代已保持一致 (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 的最低版本。

更多信息

您可以在 JIRA 中浏览 Groovy 3.0 的所有已关闭的 ticket

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 的大多数命令行工具(例如 groovygroovycgroovyshgroovydoc 等)的脚本,并且您依赖于确切的错误消息措辞(例如,可能在测试中),那么您可能需要调整这些测试中的措辞 (GROOVY-9627)。

  • 对于涉及任何以大写字母开头的字段名称的特殊情况,Groovy 现在更符合 JavaBeans 规范 (GROOVY-9618)。

3.0.8 附录

重大变更

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