Groovy 4.0: 10 новых возможностей, которые делают его ВЕЛИКИМ!

В последней версии Groovy 4.0 появилось много интересных возможностей. В этом видео я делюсь с вами своим ТОП-10 новых возможностей, которые делают последнюю версию Groovy потрясающей!

Показано

Особенность №1: Переключение выражений

В Groovy всегда были гораздо более мощные операторы switch по сравнению с Java. Значения регистра класса, значения регистра регулярного выражения, значения регистра коллекции, значения регистра закрытия, или, в конце концов, значения регистра равных значений. Все эти возможности делали оператор switch первоклассным гражданином в мире Groovy. А теперь, следуя последним обновлениям в языке программирования Java, Groovy также поддерживает выражение switch. Основное различие между оператором switch и выражением switch заключается в том, что последнее использует синтаксис, совместимый с Java, и возвращает значение. Вы по-прежнему можете использовать различные комбинации в качестве случаев, но новый синтаксис сделает ваш код немного более элегантным.

switch (value) {
    case null -> 'just a null'
    case 0 -> 'zero'
    case 1 -> 'one'
    case { it instanceof List && it.empty } -> 'an empty list'
    case List -> 'a list'
    case '007' -> 'James Bond'
    case ~/d+/ -> 'a number'
    default -> 'unknown'
}
Вход в полноэкранный режим Выход из полноэкранного режима

Особенность №2: Записи

Записи, удобный неизменяемый тип «носителя данных», был представлен в Java 16. Теперь они доступны и в Groovy. Синтаксис тот же, хотя Groovy также вводит аннотацию @RecordType, которую вы можете использовать как взаимозаменяемую. И даже если это не является такой же революцией, как для Java, приятно видеть, что Groovy идет в ногу с новейшими возможностями, представленными в его родном языке.

record Point(int x, int y) {}

def p1 = new Point(0, 0)
def p2 = new Point(2, 4)
def p3 = new Point(0, 0)

assert p1.x() == 0
assert p1.y() == 0
assert p2.x() == 2
assert p2.y() == 4
assert p1.toString() == 'Point[x=0, y=0]'
assert p2.toString() == 'Point[x=2, y=4]'
assert p1 == p3
Вход в полноэкранный режим Выход из полноэкранного режима

Особенность №3: герметичные типы

Еще одна особенность, на которую повлияли последние изменения в языке программирования Java. Герметичные типы позволяют вам ограничить, какие классы (или интерфейсы) могут расширять конкретный герметичный тип. Это можно сделать как явно (используя ключевое слово «permits»), так и неявно (без ключевого слова), если все соответствующие классы хранятся в одном исходном файле. Подобно записям, Groovy также вводит аннотацию @Sealed, которую вы можете использовать как взаимозаменяемую, если это ваше предпочтение. Когда использовать герметичные типы? Возможно, вы не хотите позволять никому расширять ваш класс из соображений безопасности. Или, возможно, вы хотите добавить новые методы в интерфейс в будущем, и вы хотите иметь строгий контроль над подклассами, на которые они повлияют. Если это так — запечатанные типы могут быть тем, на что вам стоит обратить внимание.

import groovy.transform.ToString

sealed interface Tree<T> { }

@Singleton
final class Empty implements Tree {
    String toString() { "Empty" }
}

@ToString
final class Node<T> implements Tree<T> {
    final T value
    final Tree<T> left, right

    Node(T value, Tree<T> left, Tree<T> right) {
        this.value = value
        this.left = left
        this.right = right
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Особенность №4: Проверка типов

Несмотря на то, что Groovy в основном известен своими динамическими возможностями, он позволяет вам быть гораздо более строгим в проверке типов, чем Java. Недавно добавленный дополнительный модуль groovy-typecheckers представляет регекс-чекер, который поможет вам найти ошибки в ваших регулярных выражениях во время компиляции. Как в этом примере — в регулярном выражении отсутствует закрывающая скобка. Как правило, компилятор не может обнаружить такую проблему, поэтому мы находим ее либо в модульном тесте, либо во время выполнения. Здесь я запускаю этот скрипт в GroovyShell, чтобы поймать ожидаемое исключение MultipleCompilationErrorsException.

import groovy.transform.TypeChecked

@TypeChecked(extensions = 'groovy.typecheckers.RegexChecker')
def testRegexChecker() {
    def date = '2022-04-03'

    assert date ==~ /(d{4})-(d{1,2})-(d{1,2}/
}
Вход в полноэкранный режим Выход из полноэкранного режима

Особенность №5: Встроенные макрометоды

Макрометоды позволяют получать доступ к структурам данных AST компилятора и манипулировать ими. Вызов макрометода выглядит как обычный вызов метода, но это не так — во время компиляции он будет заменен сгенерированным кодом. Вот несколько примеров таких макрометодов. Например, метод SV создает строку с именами переменных и связанными с ними значениями. Метод SVI использует метод Groovy’s inspect, который выдает немного другой результат — например, он не разворачивает объект range, как показано в этом примере.

def num = 42
def list = [1 ,2, 3]
def range = 0..5
def string = 'foo'

assert SV(num, list, range, string) == 'num=42, list=[1, 2, 3], range=[0, 1, 2, 3, 4, 5], string=foo'

assert SVI(range) == 'range=0..5'

assert NV(range) instanceof NamedValue

assert NV(string).name == 'string' && NV(string).val == 'foo'
Вход в полноэкранный режим Выход из полноэкранного режима

Особенность №6: аннотация @Pojo

Если вы знакомы с Groovy, вы уже знаете, что каждый класс Groovy реализует интерфейс GroovyObject. В этом нет ничего страшного, если вы остаетесь со своим кодом только в экосистеме Groovy. Но иногда вы хотите использовать Groovy для написания библиотечного кода, который можно использовать и в проекте на чистой Java. Вы можете объединить эти два мира с помощью новой аннотации ‘ @POJO ‘. Любой класс, аннотированный аннотацией @POJO, может быть использован без добавления Groovy во время выполнения. Так же, как класс PojoPoint, показанный в этом примере. Давайте скомпилируем его и запустим как Java-программу.

import groovy.transform.CompileStatic
import groovy.transform.Immutable
import groovy.transform.stc.POJO

@POJO
@Immutable
@CompileStatic
class PojoPoint {
    int x, y

    static void main(String[] args) {
        PojoPoint point = new PojoPoint(1,1)
        System.out.println(point.toString())
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Особенность #7: Контракты Groovy

Контракты Groovy могут стать благословением, если вы устали писать защитный код. Аннотация класса @Invariant определяет утверждения, которые проверяются во время жизни объекта — после вызова конструктора, до и после вызова метода. Аннотация @Requires представляет предварительное условие метода — утверждение, выполняемое перед вызовом метода. А аннотация @Ensures работает как постусловие метода — утверждение, выполняемое после вызова метода. Кто-то может сказать, что эти аннотации можно легко заменить явными утверждениями в теле метода. И это правда. Но если вы хотите, чтобы контракт и бизнес-логика были хорошо разделены, контракты Groovy — хорошее место для начала.

import groovy.contracts.Ensures
import groovy.contracts.Invariant
import groovy.contracts.Requires

@Invariant({ speed >= 0 })
class Rocket {
    int speed = 0
    boolean started = false

    @Requires({ !started })
    Rocket startEngine() { tap {started = true }}

    @Requires({ started })
    Rocket stopEngine() { tap { started = false }}

    @Requires({ started })
    @Ensures({ old.speed < speed })
    Rocket accelerate(int value) { tap { speed += value }}
}
Вход в полноэкранный режим Выход из полноэкранного режима

Особенность № 8: GINQ

Groovy-Integrated Query language. Вам понравится эта функция, если вы поклонник SQL-подобных языков. GINQ позволяет выполнять запросы к коллекциям, используя SQL-подобный синтаксис. Как в этом примере. У нас есть документ JSON, содержащий поле people. Мы используем GINQ, чтобы найти всех людей в возрасте 18+ в порядке убывания, взяв первые три результата и изменив возвращаемые данные так, чтобы они были в верхнем регистре и ограничивались только первыми двумя буквами. Насколько я знаю, команда Groovy планирует расширить GINQ для поддержки баз данных SQL, чтобы вы могли писать SQL-запросы, генерируемые во время компиляции и проверяемые по типу.

import groovy.json.JsonSlurper

def json = new JsonSlurper().parseText '''
    {
        "people": [
            {"name": "Alan", "age": 11},
            {"name": "Mary", "age": 26},
            {"name": "Eric", "age": 34},
            {"name": "Elisabeth", "age": 14},
            {"name": "Marc", "age": 2},
            {"name": "Robert", "age": 52},
            {"name": "Veronica", "age": 32},
            {"name": "Alex", "age": 17}
        ]
    }
    '''

assert GQ {
    from f in json.people
    where f.age >= 18
    orderby f.age in desc
    limit 3
    select f.name.toUpperCase().take(2)

}.toList() == ['RO', 'ER', 'VE']
Вход в полноэкранный режим Выход из полноэкранного режима

Особенность №9: Поддержка TOML

Groovy 3 добавил поддержку формата YAML, а теперь Groovy 4 добавляет поддержку формата TOML. Полезно, если вы работаете с таким форматом в своей кодовой базе. Стоит отметить, что вывод, создаваемый классом TomlBuilder, содержит не заголовки таблиц, а имена полей, разделенные точками.

import groovy.toml.TomlBuilder
import groovy.toml.TomlSlurper

String input = '''
# This is a TOML document (taken from https://toml.io)

title = "TOML Example"

[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00

[database]
enabled = true
ports = [ 8000, 8001, 8002 ]
data = [ ["delta", "phi"], [3.14] ]
temp_targets = { cpu = 79.5, case = 72.0 }

[servers]

[servers.alpha]
ip = "10.0.0.1"
role = "frontend"

[servers.beta]
ip = "10.0.0.2"
role = "backend"
'''

def toml = new TomlSlurper().parseText(input)

assert toml.title == 'TOML Example'
assert toml.owner.name == 'Tom Preston-Werner'
assert toml.database.ports == [8000, 8001, 8002]
assert toml.servers.alpha.ip == '10.0.0.1'
assert toml.servers.beta.ip == '10.0.0.2'


TomlBuilder builder = new TomlBuilder()
builder {
    title 'This is TOML document'
    servers {
        alpha {
            ip '10.0.0.1'
        }
        beta {
            ip '10.0.0.2'
        }
    }
}
assert builder.toString() ==
'''title = 'This is TOML document'
servers.alpha.ip = '10.0.0.1'
servers.beta.ip = '10.0.0.2'
'''
Войти в полноэкранный режим Выход из полноэкранного режима

Особенность #10: Совместимость с JDK 8

Минимальная версия Java, необходимая для работы Groovy 4, — JDK 8. Вы можете спросить — «но как Groovy обрабатывает, например, записи»? Позвольте мне показать вам это. Здесь у меня есть Java 17 и Groovy 4.0.1. Я скомпилирую этот скрипт в файл класса, и когда мы откроем его в IntelliJ, мы увидим, что он создает эквивалент записи на Java, как и ожидалось. Теперь я собираюсь переключиться на Java 8, и давайте сделаем то же самое. Открыв файл класса в IntelliJ, мы видим, что теперь сгенерированный класс «эмулирует» поведение записи, но не использует родной синтаксис записи. И в этом прелесть переносимости кода Groovy — тот же код и совершенно новые возможности языка, которые работают даже на довольно старой версии Java.

Исходный код

wololock / groovy-4-examples

Примеры Groovy 4

Установите последнюю версию Groovy

sdk install groovy
Войдите в полноэкранный режим Выйти из полноэкранного режима

Получить SDKMAN — https://sdkman.io/

Запуск одного сценария с помощью команды groovy

cd examples/

groovy 01_basic_switch_expressions.groovy
Войти в полноэкранный режим Выйти из полноэкранного режима

Запуск одного сценария с помощью Gradle

./gradlew -q runScript -PmainClass=01_basic_switch_expressions
Войти в полноэкранный режим Выйти из полноэкранного режима

Запуск всех скриптов с помощью Gradle

chmod +x ./runall.sh

./runall.sh
Войти в полноэкранный режим Выйти из полноэкранного режима
Просмотр на GitHub

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *