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 自 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 語法

現在支援 Lambda 運算式的 Java 語法。

範例

(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 禁止編譯器知道如何撰寫所需最佳化程式碼。注意:此範例有點牽強,可以重構為呼叫兩個最佳化方法參考之一,但希望您能理解這個概念。

如果您想利用動態實作背後的 Closure 特性,則適用相同的警告,例如

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 嘗試使用資源

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 的特質機制提供更強大的 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()
注意
孵化狀態:雖然此功能會保留,但其目前的實作(使用特質)具有孵化狀態。它會產生與 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 快取的頻率,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()

字串、字元序列和 G 字串上的 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 轉換

允許自動將 null 檢查防護措施新增至方法或建構函式,以確保所有參數都提供非 null 值 (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 加入類別路徑,而非模組路徑。這會將 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 語句有預設分支,現在必須是最後一個分支

  • 如果您延伸 ProcessingUnit 並覆寫 setConfiguration,請改寫 configure (GROOVY-9122)

  • 如果您覆寫 GroovyClassLoader,請注意 sourceCacheclassCache 的類型已從 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 最低版本。

更多資訊

你可以瀏覽 JIRA 中 Groovy 3.0 已關閉的全部問題單

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).