«1. Обзор

Groovy is a dynamic, scripting language for the JVM. It compiles to bytecode and blends seamlessly with Java code and libraries.

In this article, we’re going to take a look some of the essential features of Groovy, including basic syntax, control structures, and collections.

Then we will look at some of the main features that make it an attractive language, including null safety, implicit truth, operators, and strings.

2. Среда

Если мы хотим использовать Groovy в проектах Maven, нам нужно добавить в pom.xml следующее:

<build>
    <plugins>
        // ...
        <plugin>
            <groupId>org.codehaus.gmavenplus</groupId>
            <artifactId>gmavenplus-plugin</artifactId>
            <version>1.5</version>
       </plugin>
   </plugins>
</build>
<dependencies>
    // ...
    <dependency>
        <groupId>org.codehaus.groovy</groupId>
        <artifactId>groovy-all</artifactId>
        <version>2.4.10</version>
    </dependency>
</dependencies>

Самый последний подключаемый модуль Maven можно найти здесь и последняя версия groovy-все здесь.

3. Основные функции

В Groovy есть много полезных функций. Теперь давайте посмотрим на основные строительные блоки языка и на то, чем он отличается от Java.

Теперь давайте рассмотрим основные строительные блоки языка и его отличия от Java.

3.1. Динамическая типизация

Одной из наиболее важных особенностей Groovy является поддержка динамической типизации.

Определения типов являются необязательными, а фактические типы определяются во время выполнения. Давайте взглянем на эти два класса:

class Duck {
    String getName() {
        'Duck'
    }
}
class Cat {
    String getName() {
        'Cat'
    }
}

Эти два класса определяют один и тот же метод getName, но он не определен явно в контракте.

Теперь представьте, что у нас есть список объектов, содержащих уток и кошек, у которых есть метод getName. С Groovy мы можем сделать следующее:

Duck duck = new Duck()
Cat cat = new Cat()

def list = [duck, cat]
list.each { obj ->
    println obj.getName()
}

Код будет скомпилирован, и вывод приведенного выше кода будет следующим:

Duck
Cat

3.2. Неявное истинное преобразование

Как и в JavaScript, Groovy при необходимости оценивает каждый объект как логическое значение, например. при использовании его внутри оператора if или при отрицании значения:

if("hello") {...}
if(15) {...}
if(someObject) {...}

Есть несколько простых правил, которые следует помнить об этом преобразовании:

    Непустые коллекции, массивы, карты оцениваются как истинные Matcher хотя бы с одним match оценивается как true Итераторы и Enumerations с дополнительными элементами приводятся к true Непустые строки, GString и CharSequences приводятся к true Ненулевые числа оцениваются как true Ссылки на ненулевые объекты приводятся к true

Если мы хотим чтобы настроить неявное истинное преобразование, мы можем определить наш метод asBoolean().

3.3. Импорт

Некоторые пакеты импортируются по умолчанию, и нам не нужно импортировать их явно:

import java.lang.* 
import java.util.* 
import java.io.* 
import java.net.* 

import groovy.lang.* 
import groovy.util.* 

import java.math.BigInteger 
import java.math.BigDecimal

4. Преобразования AST

Преобразования AST (абстрактное синтаксическое дерево) позволяют нам подключаться к Groovy процесс компиляции и настроить его в соответствии с нашими потребностями. Это делается во время компиляции, поэтому при запуске приложения производительность не снижается. Мы можем создавать свои преобразования AST, но мы также можем использовать встроенные.

Мы можем создавать собственные преобразования или использовать встроенные.

Давайте взглянем на некоторые аннотации, которые стоит знать.

4.1. Annotation TypeChecked

Эта аннотация используется для того, чтобы заставить компилятор выполнять строгую проверку типов для аннотированных фрагментов кода. Механизм проверки типов является расширяемым, поэтому при желании мы можем обеспечить даже более строгую проверку типов, чем в Java.

Давайте посмотрим на пример ниже:

class Universe {
    @TypeChecked
    int answer() { "forty two" }
}

Если мы попытаемся скомпилировать этот код, мы увидим следующую ошибку:

[Static type checking] - Cannot return value of type java.lang.String on method returning type int

Аннотацию @TypeChecked можно применить к классам и методы.

4.2. Аннотация CompileStatic

Эта аннотация позволяет компилятору выполнять проверки во время компиляции, как это делается с кодом Java. После этого компилятор выполняет статическую компиляцию в обход протокола метаобъекта Groovy.

Когда класс аннотирован, все методы, свойства, файлы, внутренние классы и т. д. аннотированного класса будут проверяться на тип. Когда метод аннотирован, статическая компиляция применяется только к тем элементам (замыканиям и анонимным внутренним классам), которые включены в этот метод.

5. Свойства

В Groovy мы можем создавать POGO (обычные старые объекты Groovy), которые работают так же, как POJO в Java, хотя они более компактны, поскольку геттеры и сеттеры автоматически генерируются для общедоступных свойств во время компиляция. Важно помнить, что они будут созданы, только если они еще не определены.

Это дает нам гибкость определения атрибутов как открытых полей, сохраняя при этом возможность переопределять поведение при установке или получении значений.

Рассмотрим этот объект:

class Person {
    String name
    String lastName
}

Поскольку область действия по умолчанию для классов, полей и методов является общедоступной, это общедоступный класс, и два поля являются общедоступными.

«Компилятор преобразует их в закрытые поля и добавит методы getName(), setName(), getLastName() и setLasfName(). Если мы определим сеттер и геттер для определенного поля, компилятор не создаст публичный метод.

5.1. Сокращенные обозначения

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

resourceGroup.getResourcePrototype().getName() == SERVER_TYPE_NAME
resourceGroup.resourcePrototype.name == SERVER_TYPE_NAME

resourcePrototype.setName("something")
resourcePrototype.name = "something"

6. Операторы

Давайте теперь посмотрим на новые операторы, добавленные поверх тех, которые известны из Ява.

6.1. Null-Safe Dereference

Самый популярный из них — нулевой безопасный оператор разыменования «?», который позволяет нам избежать исключения NullPointerException при вызове метода или доступе к свойству нулевого объекта. Это особенно полезно в цепочках вызовов, где в какой-то точке цепочки может появиться нулевое значение.

Например, мы можем безопасно вызвать:

String name = person?.organization?.parent?.name

В приведенном выше примере, если человек, person.organization или Organization.parent имеют значение null, то возвращается значение null.

6.2. Оператор Элвиса

Оператор Элвиса «?:» позволяет нам сжимать тернарные выражения. Эти два эквивалентны:

String name = person.name ?: defaultName

и

String name = person.name ? person.name : defaultName

Они оба присваивают значение person.name переменной имени, если оно равно Groovy true (в данном случае не равно нулю и имеет ненулевую длину). ).

6.3. Оператор космического корабля

Оператор космического корабля «\u003c=\u003e» — это реляционный оператор, работающий аналогично JavacompareTo(), который сравнивает два объекта и возвращает -1, 0 или +1 в зависимости от значений обоих объектов. аргументы.

Если левый аргумент больше правого, оператор возвращает 1. Если левый аргумент меньше правого, оператор возвращает -1. Если аргументы равны, возвращается 0.

Самым большим преимуществом использования операторов сравнения является плавная обработка пустых значений, так что x \u003c=\u003e y ​​никогда не вызовет исключение NullPointerException:

println 5 <=> null

В приведенном выше примере в результате будет напечатано 1.

7. Строки

Существует несколько способов выражения строковых литералов. Подход, используемый в Java (строки в двойных кавычках), поддерживается, но при желании также разрешено использовать одинарные кавычки.

Многострочные строки, иногда называемые heredocs на других языках, также поддерживаются с использованием тройных кавычек (одинарных или двойных).

Многострочные строки, иногда называемые heredocs на других языках, также поддерживаются с использованием тройных кавычек (одинарных или двойных).

Строки, определенные в двойных кавычках, поддерживают интерполяцию с использованием синтаксиса ${}:

def name = "Bill Gates"
def greeting = "Hello, ${name}"

На самом деле любое выражение может быть помещено внутри ${}:

def name = "Bill Gates"
def greeting = "Hello, ${name.toUpperCase()}"

Строка с двойными кавычками называется GString, если он содержит выражение ${}, в противном случае это обычный объект String.

Приведенный ниже код будет выполняться без провала теста:

def a = "hello" 
assert a.class.name == 'java.lang.String'

def b = 'hello'
assert b.class.name == 'java.lang.String'

def c = "${b}"
assert c.class.name == 'org.codehaus.groovy.runtime.GStringImpl'

8. Коллекции и карты

Давайте посмотрим, как обрабатываются некоторые основные структуры данных.

8.1. Списки

Вот код для добавления нескольких элементов в новый экземпляр ArrayList в Java:

List<String> list = new ArrayList<>();
list.add("Hello");
list.add("World");

А вот та же операция в Groovy:

List list = ['Hello', 'World']

Списки default типа java.util.ArrayList, а также может быть объявлен явно путем вызова соответствующего конструктора.

Для Set нет отдельного синтаксиса, но для этого можно использовать приведение типов. Либо используйте:

Set greeting = ['Hello', 'World']

или:

def greeting = ['Hello', 'World'] as Set

8.2. Map

Синтаксис для Map аналогичен, хотя и немного более подробен, потому что нам нужно указать ключи и значения, разделенные двоеточиями:

def key = 'Key3'
def aMap = [
    'Key1': 'Value 1', 
    Key2: 'Value 2',
    (key): 'Another value'
]

После этой инициализации мы получим новый LinkedHashMap с записи: Ключ1 -\u003e Значение1, Ключ2 -\u003e Значение 2, Ключ3 -\u003e Другое значение.

Мы можем получить доступ к записям на карте разными способами:

println aMap['Key1']
println aMap[key]
println aMap.Key1

9. Структуры управления

9.1. Условия: if-else

Groovy поддерживает синтаксис условного if/else, как и ожидалось:

if (...) {
    // ...
} else if (...) {
    // ...
} else {
    // ...
}

9.2. Условные выражения: switch-case

The switch statement is backward compatible with Java code so that we can fall through cases sharing the same code for multiple matches.

The most important difference is that a switch can perform matching against multiple different value types:

def x = 1.23
def result = ""

switch ( x ) {
    case "foo":
        result = "found foo"
        break

    case "bar":
        result += "bar"
        break

    case [4, 5, 6, 'inList']:
        result = "list"
        break

    case 12..30:
        result = "range"
        break

    case Number:
        result = "number"
        break

    case ~/fo*/: 
        result = "foo regex"
        break

    case { it < 0 }: // or { x < 0 }
        result = "negative"
        break

    default:
        result = "default"
}

println(result)

В приведенном выше примере будет напечатано число.

9.3. Циклы: while

Groovy поддерживает обычные циклы while, такие как Java:

def x = 0
def y = 5

while ( y-- > 0 ) {
    x++
}

9.4. Циклы: for

Groovy поддерживает эту простоту и настоятельно рекомендует использовать циклы for следующей структуры:

for (variable in iterable) { body }

«

«Цикл for перебирает iterable. Часто используемые итерации — это диапазоны, коллекции, карты, массивы, итераторы и перечисления. На самом деле любой объект может быть итерируемым.

def x = 0
for ( i in 0..9 ) {
    x += i
}

x = 0
for ( i in [0, 1, 2, 3, 4] ) {
    x += i
}

def array = (0..4).toArray()
x = 0
for ( i in array ) {
    x += i
}

def map = ['abc':1, 'def':2, 'xyz':3]
x = 0
for ( e in map ) {
    x += e.value
}

x = 0
for ( v in map.values() ) {
    x += v
}

def text = "abc"
def list = []
for (c in text) {
    list.add(c)
}

Скобки вокруг тела необязательны, если оно состоит только из одного оператора. Ниже приведены примеры итерации по диапазону, списку, массиву, карте и строкам:

Итерация объекта делает цикл Groovy сложной управляющей структурой. Это верный аналог использованию методов, перебирающих объект с замыканиями, например методу each из Collection.

for (x in 0..9) { println x }

Основное отличие состоит в том, что тело цикла for не является замыканием, это означает, что это тело является блоком:

(0..9).each { println it }

тогда как это тело является замыканием:

Несмотря на то, что они внешне похожи, по строению очень разные.

Замыкание является отдельным объектом и имеет разные функции. Его можно создать в другом месте и передать методу each. Однако тело цикла for создается непосредственно в виде байт-кода в момент его появления. Никаких специальных правил определения области применения не применяется.

10. Обработка исключений

Большая разница в том, что обработка проверенных исключений не применяется принудительно.

try {
    someActionThatWillThrowAnException()
} catch (e)
    // log the error message, and/or handle in some way
}

Для обработки общих исключений мы можем поместить потенциально вызывающий исключение код в блок try/catch:

Не объявляя тип перехватываемого исключения, здесь будет перехвачено любое исключение.

11. Замыкания

Проще говоря, замыкание — это анонимный блок исполняемого кода, который может быть передан в переменные и имеет доступ к данным в том контексте, в котором он был определен.

Они также похожи на анонимные внутренние классы, хотя не реализуют интерфейс и не расширяют базовый класс. Они похожи на лямбды в Java.

Интересно, что Groovy может использовать все преимущества дополнений JDK, которые были введены для поддержки лямбда-выражений, особенно потокового API. Мы всегда можем использовать замыкания там, где ожидаются лямбда-выражения.

def helloWorld = {
    println "Hello World"
}

Давайте рассмотрим следующий пример:

helloWorld.call()

Переменная helloWorld теперь содержит ссылку на замыкание, и мы можем выполнить его, вызвав его метод вызова:

helloWorld()

Groovy позволяет нам использовать более естественный синтаксис вызова метода — он вызывает для нас метод вызова:

11.1. Параметры

Как и методы, замыкания могут иметь параметры. Есть три варианта.

def printTheParam = { println it }

В последнем примере, из-за отсутствия declpersistence_startared, имеется только один параметр с именем по умолчанию it. Модифицированное замыкание, которое печатает то, что отправлено, будет выглядеть так:

printTheParam('hello')
printTheParam 'hello'

Мы могли бы назвать его так:

def power = { int x, int y ->
    return Math.pow(x, y)
}
println power(2, 3)

Мы также можем ожидать параметры в замыканиях и передавать их при вызове:

def say = { what ->
    println what
}
say "Hello World"

Определение типа параметров такое же, как и у переменных. Если мы определяем тип, мы можем использовать только этот тип, но также можем передавать его и передавать что угодно:

11.2. Необязательный возврат

def square = { it * it }
println square(4)

Последний оператор замыкания может быть неявно возвращен без необходимости писать оператор возврата. Это может быть использовано для сокращения стандартного кода до минимума. Таким образом, замыкание, которое вычисляет квадрат числа, может быть сокращено следующим образом:

Это замыкание использует неявный параметр it и необязательный оператор return.

12. Заключение

В этой статье представлено краткое введение в язык Groovy и его основные возможности. Мы начали с введения простых понятий, таких как базовый синтаксис, условные операторы и операторы. Мы также продемонстрировали некоторые более продвинутые функции, такие как операторы и замыкания.