BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ ニュース switch式とsealed型を加えたGroovy 4.0.0

switch式とsealed型を加えたGroovy 4.0.0

原文(投稿日:2022/02/25)へのリンク

Apache Groovyのバージョン4.0.0には、switch式、sealed型、組込みの型チェック、組込みマクロメソッドの他、インキュベーション機能としてレコード、JavaShell、POJOアノテーション、Groovyコントラクト、Groovy-Integrated Query、TOMLのサポートが導入されている。それら以外にも、いくつかの小さな改善や、今回の最新バージョンで削除された機能による非互換的な変更が含まれる。

また、MavenのgroupIdorg.codehaus.groovyからorg.apache.groovyに変更されているので、Groovy 4.0.0にアップグレードする場合には、依存関係を修正することが推奨されている。

Java Platform Module System(JPMS)は、複数のモジュールに同名のクラスやパッケージを置くことを許可していない。Groovy 3ではまだクラスの重複バージョンが提供されていたが、Groovy 4ではこれらが削除されているため、groovy.xml.XmlSlurpergroovy.xml.XmlParsergroovy.ant.AntBuildergroovy.test.GroovyTestCaseなどの新たなクラスを使用する必要がある。

既存のswitch文に加えて、switch式がサポートされるようになった。

def result = switch(i) {
    case 0 -> 'January'
    case 1 -> 'February'
    //...
    default -> throw new IllegalStateException('Invalid number of month')
}

コードブロックを使用して、複数の文を記述することもできる。

case 0 -> { def month = 'January'; def year = Year.now().getValue();
    month + " " + year }

switchの実装はJavaとは違い、可能な値すべてのcaseブランチを記述する必要はない。defaultブランチが提供されていない場合は、Groovyがnullを返すdefaultブランチを暗黙的に追加する。

sealedキーワードまたは@Sealedアノテーションを使って、sealedクラスを作ることが可能になった。許可されるサブクラスは、同時コンパイルされる場合には自動的に検出される。sealedキーワードにpermitsクラウズを使用するか、@Sealedアノテーションにpermitted subclass属性を使用することで、許可されるサブクラスを明示的に定義することもできる。

@Sealed(permittedSubclasses = [Dog, Cat]) interface Animal {}
@Singleton final class Dog implements Animal {
    String toString() { 'Dog' }
}
@Singleton final class Cat implements Animal {
    String toString() { 'Cat' }
}
sealed interface Animal permits Dog, Cat {}
@Singleton final class Dog implements Animal {
    String toString() { 'Dog' }
}
@Singleton final class Cat implements Animal {
    String toString() { 'Cat' }
}

新たなインキュベーション機能のひとつであるレコードは、Groovyの既存機能である@Immutableに非常に近いものだ。

record Student(String firstName, String lastName) { }

定義されたクラスは暗黙的にfinalとなり、private finalフィールドの firstNamelastNamefirstName()lastName()メソッド、2つを引数とするデフォルトコンストラクタ、0LserialVersionUIDtoStringequals()hashCode()の各メソッドが自動的に生成される。

弱い型チェックと強い型チェックの両方が、組み込み型チェックとして提供される。今回のリリースでは、いくつかの型チェッカを含んだgroovy-typechekersモジュールが導入された。このモジュールをプロジェクトに追加した上で@TypeCheckedアノテーションを使えば、コンパイル中に記述の有効性を検証することが可能になる。以下の文は閉じカッコが不足しており、通常ならば実行時にエラーとなるものだ。

@TypeChecked(extensions = 'groovy.typecheckers.RegexChecker')
def year() {
    def year = '2022'
    def matcher = year =~ /(\d{4}/
}

しかし型チェッカが、コンパイル時に次のようなエラーを指摘してくれる。

Groovyc: [Static type checking] — Bad regex: Unclosed group near index 6
(\d{4}

型チェックと同じように、いくつかのマクロメソッドがgroovy-macro-libraryモジュールを通じて利用可能になった。例えばSVマクロでは、変数名と値からStringを生成することができる。

def studentName = "James"
def age = 42
def courses = ["Introduction to Java" , "Java Concurrency", "Data structures"]

println SV(studentName, age, courses)
studentName=James, age=42, courses=[Introduction to Java, Java Concurrency,
    Data structures]

NVマクロはNamedValueを生成して、名前と値の処理を可能にする。

def namedValue = NV(age)
assert namedValue instanceof NamedValue
assert namedValue.name == 'age' && namedValue.val == 42

同じインキュベーション機能のJavaShellでは、Javaコードスニペットを実行することができる。

import org.apache.groovy.util.JavaShell

def student = 'record Student(String firstName, String lastName) {}'
Class studentClass = new JavaShell().compile('Student', student)
assert studentClass.newInstance("James", "Gosling")
    .toString() == 'Student[firstName=James, lastName=Gosling]'

POJOアノテーションも同じくインキュベーション機能で、GroovyをLombokのようなプリプロセッサとして使用できるようになる。@POJOアノテーションは、そのクラスが高度なGroovyオブジェクトではなくPOJOであることと、@CompileStaticアノテーションの処理が必要であることを示すものだ。

インキュベーションモジュールのgroovy-contractsは、クラス不変(class-invariants)、およびクラスとインターフェースの事前および事後条件の指定を可能にする。

import groovy.contracts.*

@Invariant({ coursesCompleted >= 0 })
class Student {
    int coursesCompleted = 0
    boolean started = false

    @Requires({ started })
    @Ensures({ old.coursesCompleted < coursesCompleted })
    def processCourses(newCoursesCompleted) {
        coursesCompleted += newCoursesCompleted
    }
}

startedtrueでなければならない、などの事前条件(precondition)が満たされない場合には、エラーメッセージが表示される。

def student = new Student()
student.processCourses(2)
org.apache.groovy.contracts.PreconditionViolation: <groovy.contracts.Requires>
    Student.java.lang.Object processCourses(java.lang.Object) 

started
|
false

処理されたコースの数が減少した場合など、事後条件(post-condition)が満たされない場合にも、別のエラーメッセージが表示される。

def student = new Student()
student.setStarted(true)
student.processCourses(-2)
org.apache.groovy.contracts.PostconditionViolation: <groovy.contracts.Ensures>
    Student.java.lang.Object processCourses(java.lang.Object) 

old.coursesCompleted < coursesCompleted
|   |                | |
|   0                | -2
|                    false

インキュベーション機能のGroovy-Integrated Query (GINQまたはGQuery)は、リストやマップ、カスタムドメインオブジェクト、その他の構造的データのコレクションをクエリ可能にするもので、SQLに非常に近い。

from student in students
orderby student.age
where student.age > 20
select student.firstName, student.lastName, student.age
from student in students
leftjoin university in universities on student.university == university.name
select student.firstName, student.lastName, university.city

同じくインキュベーションではあるが、TOMLベースファイルのサポートは、groovy-tomlモジュールを使うことで、オブジェクトグラフの構築および解析を可能にする。

def tomlBuilder = new TomlBuilder()
tomlBuilder.records {
    student {
        firstName 'James'
        // ...
    }
}

def tomlSlurper = new TomlSlurper()
def toml = tomlSlurper.parseText(tomlBuilder.toString())

assert 'James' == toml.records.student.firstName

その他にも、GStringtoString値のキャッシングによるパフォーマンス向上など、数多くの小さな改善が実施されている。レンジでは、これまではインクルーシブ(1..10)と右辺エクスクルーシブ(1..<10)が指定可能だったが、左辺(1<..10)および両辺(1<..<10)のエクスクルーシブも指定できるようになった。小数値の先行0が省略可能になり、.50.5の両方がサポートされた。

Groovy 4には非互換的な変更もいくつかある。Antlr2パーザの削除や、コールサイトベースのバイトコード生成が不可能になったことなどがその例だ。groovy-jaxbgroovy-bsfなど、いくつかのモジュールも削除されている。groovy-yamlモジュールがgroovy-allpomに含まれるようになった一方で、groovy-testngモジュールが削除された。

非互換的な変更と新機能の完全なリストがリリースノートに掲載されている。

作者について

この記事に星をつける

おすすめ度
スタイル

BT