Перейти к основному содержимому

Введение в язык Groovy

· 13 мин. чтения

Задача: Наибольшая подстрока палиндром

Для заданной строки s, верните наибольшую подстроку палиндром входящую в s. Подстрока — это непрерывная непустая последовательность символов внутри строки. Стока является палиндромом, если она читается одинаково в обоих направлениях...

ANDROMEDA 42

1. Обзор

Groovy — это динамический язык сценариев для JVM . Он компилируется в байт-код и легко смешивается с кодом и библиотеками Java.

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

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

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-all здесь .

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

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

  • Непустые коллекции, массивы, карты оцениваются как истинные
  • Сопоставитель хотя бы с одним совпадением оценивается как true
  • Итераторы и перечисления с дополнительными элементами приводятся к истине
  • Непустые Strings , GStrings и CharSequences приводятся к 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 ( абстрактное синтаксическое дерево ) позволяют нам подключиться к процессу компиляции Groovy и настроить его в соответствии с нашими потребностями. Это делается во время компиляции, поэтому при запуске приложения производительность не снижается. Мы можем создавать свои преобразования AST, но мы также можем использовать встроенные.

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

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

4.1. Тип аннотации Checked

Эта аннотация используется для того, чтобы заставить компилятор выполнять строгую проверку типов для аннотированных фрагментов кода. Механизм проверки типов является расширяемым, поэтому при желании мы можем обеспечить даже более строгую проверку типов, чем в 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. Аннотация _

Эта аннотация позволяет компилятору выполнять проверки во время компиляции, как это делается с кодом 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. Операторы

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

6.1. Нулевое разыменование

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

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

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

В приведенном выше примере, если person , 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. Оператор космического корабля

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

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

Самым большим преимуществом использования операторов сравнения является плавная обработка нулей , так что x <=> 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']

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

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

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

или же:

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

8.2. карта

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

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

После этой инициализации мы получим новый LinkedHashMap с записями: Key1 -> Value1, Key2 -> Value 2, Key3 -> Another Value .

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

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

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

9.1. Условные выражения: если-иначе

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

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

9.2. Условные обозначения: switch-case

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

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

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. Циклы: пока

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

def x = 0
def y = 5

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

9.4. Петли: для

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

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

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

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

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

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

Несмотря на то, что они похожи, они очень разные по конструкции.

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

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

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

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

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

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

11. Закрытие

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

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

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

Рассмотрим пример ниже:

def helloWorld = {
println "Hello World"
}

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

helloWorld.call()

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

helloWorld()

11.1. Параметры

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

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

def printTheParam = { println 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 и его основные функции. Мы начали с введения простых понятий, таких как базовый синтаксис, условные операторы и операторы. Мы также продемонстрировали некоторые более продвинутые функции, такие как операторы и замыкания.

Если вы хотите найти больше информации о языке и его семантике, вы можете перейти прямо на официальный сайт .