Groovy

Данный учебник представляет из себя руководство по языку программирования Groovy

Установка и настройка

править

На странице загрузки выбираем удобный способ установки (предварительно установив Java (JDK)). После установки могут быть доступны Groovy Console и Groovy Shell в которых можно набирать код и запускать его.

Также запустить код можно прямо из Groovy Web Console.

Перед запуском проверьте чтобы в пути Вашей ОС были прописаны переменная GROOVY_HOME и путь в переменной PATH к исполняему файлу groovy.bat (или groovy.sh)

Также файл с кодом можно выполнить через Java. Делается это так:

  1. В консоли набираем: groovyc helloworld.groovy и получаем скомпилированный в байт-код файл helloworld.class
  2. Запускаем его набирая в консоли: java -cp ".;%GROOVY_HOME%\lib\*" helloworld

Версию можно посмотреть набрав в консоли: groovy -v

Первая программа

править

Откройте редактор и напишите программу. По традиции, первая программа должна просто выводить приветствие миру:

println "Hello World!"

Сохраните её в файл helloworld.groovy и запустите.

groovy helloworld.groovy

На экране появится надпись:

Hello World!

На этом традиционную часть можно считать выполненной.

Комментарии и демонстрация результата

править

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

В Groovy знаком начала комментария служит //. Всё, что между ним и концом строки пропускается. Также комментарий можно расположить между /* и */. Пример:

println 2 + 2        // это комментарий
println "Привет!"    /* это тоже комментарий */
/* А это - многострочный 
комментарий */

Результат иллюстрируемого кода будет располагаться после последовательности //=>. Пример:

println 2 + 2       //=> 4
println "Привет"    //=> Привет

Переменные

править

Переменная, которая может иметь неопределенное значение на этапе компиляции или надо скрыть этот тип, в Groovy определяется ключевым словом def (аналогично var в Java):

def a = 1 // объявление "безтиповой" переменной, присвоение ей значения типа int
a = "String" /* так как мы не указали тип при объявлении этой переменной, 
                то можно присваивать этой переменной значение другого типа */

int b = 2 // объявление переменной типа int
b = "String for type int?" // так как тип переменной указан, 
                           // то когда мы пытаемся присвоить String, получаем ошибку (GroovyCastException)

Строки

править

В Groovy есть 2 типа строк:

  • Java Strings — строки в одинарных кавычках
  • Groovy Strings, известны как GStrings — в двойных кавычках; используя ${имя_переменной} можно "вставить" внутрь строки значение переменной
javaString = 'java' // Java String
groovyString = "Hello ${javaString}!" // GString

println javaString // => java
println groovyString // => Hello java!

bigGroovyString = """
  long
   long
    string
""" // Длинная строка с отступами

def a = "a"
println a
println a + "12" // конкатенация
println a * 3 // умножение

def aMultilineString = '''line one
line two
line three'''


someList[1,3,5..7,9]       // Получение подсписка

Также, Groovy предоставляет встроенный цикл, для перебора элементов списка, или элементов любого объекта, реализующего интерфейс java.lang.Iterable:

for ( e in someList ) {
	println e          // Распечатываем все элементы списка someList
}

Комплексный список:

def SomeGeterogeneousType = [22,'groovy',true] // комплексный список
print SomeGeterogeneousType[0] // 22

Map (Ассоциативный массив)

править

Аналогично обычным массивам (спискам), Groovy поддерживает прозрачный синтаксис для работы с maps (ассоциативными массивами). Объявление выглядит следующим образом:

def someMap = [ 'a' : 1, 'b' : 2 ]   // Объявление

Также, существует специальный синтаксис для объявления пустых map:

// Объявление пустого map
def emptyMap = [:]                   
def otherEmptyMap = [:] as HashMap

Доступ к элементам осуществляется по ключу, с использованием оператора [], или же с ключом как полем:

someMap['a']                         // Доступ к элементу
someMap.a                            // Доступ к элементу как к полю

Аналогично производится и изменение элементов:

someMap['a'] = 2                     // Изменение элемент
someMap.a = 2                        // Изменение элемента, как поля

Для хранения объектов в качестве ключа можно использовать скобки ():

def map = [(new String("username")):"james", nickname:"jcameron", (new Integer(22)):1234]
println map.get(new Integer(22))

Условное исполнение

править

Одной из наиболее важных особенностей любого языка программирования является возможность выполнять различные коды в разных условиях. Простейший способ сделать это состоит в использовании IF конструкции. Например:

def amPM = Calendar.getInstance().get(Calendar.AM_PM)
if (amPM == Calendar.AM){
	println("Good morning")
} else {
	println("Good evening")
}

Не беспокойтесь о длинной первой строке, это просто какой-то код, чтобы определить, сейчас утро или вечер. Остальная часть кода выполняется следующим образом: сначала оценивается выражение в круглых скобках и в зависимости от результата true (истинно) или false (ложно) выполняется первый или второй блок кода. Смотрите ниже раздел логические выражения.

Обратите внимание, что ​​блок else не требуется, в отличие от первого блока:

amPM = Calendar.getInstance().get(Calendar.AM_PM)
if (amPM == Calendar.AM){
	println("Have another cup of coffee.")
}

Скрипт не будет выдавать итоговый результат, так как нет истины, подходящий код

amPM = (Calendar.AM)
if (amPM == Calendar.AM){
	println("Have another cup of coffee.")
} 

Так как значение данные amPM ==(равны), истина выдаёт конечный итог.

Логические выражения

править

Существует специальный тип данных в большинстве языков программирования, который используется для представления значений истинности,true (истина) и false (ложь). Простейшие логические выражения - это просто слова. Логические значения могут быть сохранены в переменных, как и любой другой тип данных:

def myBooleanVariable = true

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

  • ==
  • !=
  • >
  • >=
  • <
  • <=

Большинство из них, довольно интуитивны. Оператор равенства ==, не путайте с оператором присваивания =. Оператор не равенство !=, то есть "не равно".

Некоторые примеры:

def titanicBoxOffice = 1234600000
def titanicDirector = "James Cameron"

def trueLiesBoxOffice = 219000000
def trueLiesDirector = "James Cameron"

def returnOfTheKingBoxOffice = 752200000
def returnOfTheKingDirector = "Peter Jackson"

def theTwoTowersBoxOffice = 581200000
def theTwoTowersDirector = "PeterJackson"

titanicBoxOffice > returnOfTheKingBoxOffice // вычисляется как истина
titanicBoxOffice >= returnOfTheKingBoxOffice // вычисляется как истина
titanicBoxOffice >= titanicBoxOffice // вычисляется как истина
titanicBoxOffice > titanicBoxOffice // оценивается как ложное
titanicBoxOffice + trueLiesBoxOffice < returnOfTheKingBoxOffice + theTwoTowersBoxOffice // оценивается как ложное

titanicDirector > returnOfTheKingDirector // оценивается как ложное, потому что "J" находится перед "Р"
titanicDirector < returnOfTheKingDirector // вычисляется как истина
titanicDirector >= "James Cameron" // вычисляется как истина
titanicDirector == "James Cameron" // вычисляется как истина

Логические выражения особенно полезны при использовании совместно с if-конструкциями. Например:

if (titanicBoxOffice + trueLiesBoxOffice > returnOfTheKingBoxOffice + theTwoTowersBoxOffice){
	println(titanicDirector + " is a better director than " + returnOfTheKingDirector)
}

Особенно полезна проверка на наличие значения. К примеру, определён ли данный ключ в карте:

def suvMap = ["Acura MDX":"\$36,700", "Ford Explorer":"\$26,845"]
if (suvMap["Hummer H3"] != null){
 	println("A Hummer H3 will set you back "+suvMap["Hummer H3"]);
}

Вообще null используется для указания на отсутствие значения выражения или переменной.

Функции

править

Функции и методы всегда возвращают, как результат, последнее выражение.

//класс пользователя
class Person{
	String first, last
}
//функции без типизированных параметров
def printInfo(first, second){
	println "first name: $first, second name: $second"
}
def printFirstName(user){
	println "user: $user.first"
}
//создаем объект класса Person с параметрами
def tempPerson = new Person(first: 'Adam', last: 'Smith')
// вызов функции разными способами
printInfo tempPerson.first, tempPerson.last
printFirstName(tempPerson)

printFirstName tempPerson

//типизированный параметр

def functionA(String str){
	println str
}
functionA 'String'// все ок
functionA 1 // вернётся исключение

int functionB(int argB) {
    argB + 2
}
String functionC() {
    "Hello World"
}

println functionB(1)
def hw = functionC()
println hw

Closures (Замыкания)

править

Closure - это замыкание и представляет собой некую анонимную функцию со свойствами объекта.

Синтаксис замыкания : { [closureArguments->] statements }

def closureFunction = {a, b ->
    println a
    println b
}
 
closureFunction(1, 2)

В замыканиях по умолчанию присутствует переменная it и ссылается на первый параметр в замыкании:

def closureA = { it }
assert closureA()  == null
assert closureA(1) == 1

Вызов замыкания:

def c = { it, arg-> println "${it} ${arg}" }
c.call("A", "B") // первый тип вызова
c("C", "D") // второй тип вызова

Определение замыкания в классе и его вызов:

public class ClassWithClosure {
    private int member = 20

    private String method()
    {
        return "hello"
    }

    def publicMethodWithClosure(String name_)
    {
        def localVar = member + 5
        def localVar2 = "Parameter: ${name_}"
        
        return {
            println "${member} ${name_} ${localVar} ${localVar2} ${method()}"
        }
    }
}

ClassWithClosure sample = new ClassWithClosure()
def closureVar = sample.publicMethodWithClosure("Xavier")
closureVar()

Если нужно передать замыкание в метод принимающий интерфейс(например функциональный), то делают так:

import java.util.function.Consumer
import java.util.function.Supplier

def supplierExample(Supplier<String> supplier) {
	println supplier.get()
}
def consumerExample(Consumer<Integer> consumer) {
	consumer.accept(12)
}

supplierExample({"xyz"} as Supplier<String>)
consumerExample({a-> print "int is:"+a} as Consumer<Integer>)

Специальный класс Expando для создания динамических объектов и методов (как замыкания) которые можно вызывать:

def player = new Expando()
player.name = "Alec"
player.greeting = { "Hello, my name is $name" }

println player.greeting()
player.name = "Max"
println player.greeting()
//или с передачей типизированных аргументов в метод:
def user = new Expando(username: 'Adam')
user.printInfo = { String format ->
	println format+username
}
user.printInfo("format:")

Работа с файлами

править
def out= new File('File1.txt')
// если файл не существует, то создаем файл
if(!out.exists()) {
    out.createNewFile()
    out << 'aaa\nbbb\nccc' // пишем текст в файл
}

list= [] // создаем список для строк
out.eachLine { list<< it } // и заполняем его 
println list.size() // выводим размер списка строк
println out.text // выводим весь текст
out.write('\nnew string') // пишем текст в файл
out.eachLine { println it}
out.append('\nappend string') // добавляем текст в файл

//выводим информацию о файле
println out.name
println out.isAbsolute()
println out.path 
println out.parent
println out.absolutePath 
println out.absoluteFile.toString()
println out.canonicalPath
println out.canonicalFile.toString()
println out.toURI().toString()

// Создаем директории
def dir= new File('Directory1')
dir.mkdir() //make directory, if it doesn't already exist
def dir2= new File('Directory2/SubDir1')
dir2.mkdirs()

Работа со строками

править
lst = /This is my new string./
println lst

def b = "abcde" // также строка представляет собой и список символов
println b[2] // напечатает c
println b[1..3] // напечатает bcd

println "reverse me".reverse()
println "This is the end, my only friend!".tokenize(' ').reverse().join(' ')

Classes and Objects (Классы и объекты)

править

Описание и создание класса(по умолчанию класс имеет тип доступа public, а переменные класса имеют тип доступа private, но компилятор groovy сам сделает для этих полей геттеры и сеттеры с доступом public):

// интерфейс
interface Human{
    def HUMAN_NAME = 'Adam'//константа
    def HAIR_TYPES = ['Blondie','Brown']//массив
    int age()
    String fullInfo()
}
//классы его реализующие
class Man implements Human {
    String name=HUMAN_NAME
    int age
    String hair = HAIR_TYPES[0]
    @Override
    int age() {
        age
    }

    @Override
    String fullInfo() {
        "name:${name};age:${age};hair:${hair}"
    }
}
class Woman implements Human {
    String name
    int age
    String hair = HAIR_TYPES[1]
    @Override
    int age() {
        age
    }

    @Override
    String fullInfo() {
        Woman.class.getSimpleName()+";name:${name};age:${age};hair:${hair}"
    }
}
//использование
def adam = new Man(age: 22 ) as Human
println(adam.fullInfo())
def eva = new Woman()
eva.name = 'Eva'
eva.age = 18
println(eva.fullInfo())
//отложенная инициализация
def kain = new Man()
kain.with {
    name='Kain'
    age=5
}
println(kain.fullInfo())
// тоже самое, но тут происходит возврат этого объекта
def avel = new Man().with {
    name='Avel'
    age=2
    it
}
println(avel.fullInfo())
def irod = new Man().tap {
    name='Irod'
    age=32
    hair=HAIR_TYPES[1]
}
println(irod.fullInfo())
println("irod age:"+irod.age())

Классы:

// Классы могут не иметь конструктора
class ClassWithoutConstructor { }

class Bird {
    static startDate = new Date()
    private name
    def getName() { name }
    def setName(name) { this.name = name }
    static getStartDate() {
        startDate
    }
}

println Bird.startDate
def myBird = new Bird()
myBird.name = "Chack"
println myBird.name
// и иметь разные правила для генерации методов для полей
class Cat {

    def startDate = new Date() // getter и setter
    final String name = "Empty"
    final age      // только getter

    Cat(date, name) {
        startDate = date
        this.name = name
        this.age = 1
    }
}
Cat catty = new Cat(new Date(), "Pussy Cat")
println catty.name
println catty.age
println catty.startDate

Groovy добавляет к обычному классу конструктор принимающий один аргумент типа Map если не определен другой конструктор.

class Man {
    String name
    int age
}
//Именованные параметры в конструкторе станут типом Map
def adam = new Man(name:'Adam', age:30)
assert adam.name == 'Adam'

class Woman {
    String name
    int age = 18
    //Определяем свой собственный конструктор
    Woman(int age, String name) {
        this.name = name
        this.age = age
    }
}
//вызываем наш определенный конструктор, а с типом Map класс Woman уже не сможет работать
def eva = new Woman(19, 'Eva')
assert eva.age == 19

С помощью аннотации @MapConstrutor мы можем добавить к классу конструктор с параметром Map:

import groovy.transform.MapConstructor

@MapConstructor
class ManFinal {
    final String name // AST трансформация поддерживает финальные поля.
    final int age
}
def adamFinal = new ManFinal(name: 'Adam', age: 30)
assert adamFinal.age == 30

@MapConstructor
class WomanFinal {
    // не финальные поля!
    String name
    int age
    WomanFinal(int age, String name) {
        this.name = name
        this.age = age
    }
}
//теперь наш класс может принимать параметр типа Map
def evaFinal = new WomanFinal(name: 'Eva', age: 19)
assert evaFinal.age == 19
//также можем вызвать и наш собственный конструктор
def diana = new WomanFinal(20, 'Diana')
assert diana.age == 20

Интерфейсы:

// Объявление интерфейса
interface Voice{
    def DEFAILT_NAME='Sasha' //константа
    def SomeGeterogeneousType = [1,'small',true] // комплексный тип массива
    void voice();
    String getAnimalName();
}
// его реализация
class Dog implements Voice{

    @Override
    void voice() {
        println "Gav"
    }

    @Override
    String getAnimalName() {
        return Dog.class.getSimpleName()
    }

    void goMethod(){

    }
}
// и использование
def dog = new Dog() as Voice

// или можно привести к интерфейсу таким способом:
def dog = new Dog().asType(Voice.class)

dog.voice()
println "my name is: " + dog.getAnimalName()

// Реализация интерфейса как замыкания
def mainRunnable = {
    run:{
        try {
            int i = 5
            while (i>0) {
                sleep(1000)
                println "${i--}"
            }
        } catch (InterruptedException ex) {
            // error
        }
    }
} as Runnable

new Thread(mainRunnable).start()

Наследование:

class PersonA implements Comparable {
    def firstname, initial, surname
    PersonA(f,i,s) { firstname = f; initial = i; surname = s }
    int compareTo(other) { firstname <=> other.firstname }
}
def a = new PersonA('James', 'T', 'Kirk')
def b = new PersonA('Samuel', 'L', 'Jackson')
println a <=> b
// => -1

class PersonB extends PersonA {
    PersonB(f,i,s) { super(f,i,s) }
    int compareTo(other) { initial <=> other.initial }
}

a = new PersonB('James', 'T', 'Kirk')
b = new PersonB('Samuel', 'L', 'Jackson')
println a <=> b
// => 1

class Parent {
    private name // my child's name
    def setChildName(value) { name = value }
    def getChildName() { name }
}
class GrandParent extends Parent {
    private name // my grandchild's name
    def setgrandChildName(value) { name = value }
    def getGrandChildName() { name }
}
g = new GrandParent()
g.childName = 'Jason'
g.grandChildName = 'Rachel'
println g.childName       // => Jason
println g.grandChildName  // => Rachel

Абстрактные классы:

abstract class Shape {
    final name
    Shape(name) { this.name = name }
    abstract printName()
}

class Circle extends Shape {
    final radius
    Circle(radius) {
        super('circle')
        this.radius = radius
    }
    def area() { Math.PI * radius * radius }
    def printName() {
        println "I am a $name."
    }
}

class Rectangle extends Shape {
    final length, breadth
    def Rectangle(length, breadth) {
        super("rectangle")
        this.length = length
        this.breadth = breadth
    }
    def printName() {
        println "I am a $name."
    }
}
shapes = [new Circle(4.2), new Rectangle(5, 7)]
shapes.each { shape -> shape.printName() }

Статические внутренние классы:

class OuterClass {
    static class StaticInnerClass {
        public int getAge(){
            int a = 35
        }
    }
}

OuterClass.StaticInnerClass myInstance = new OuterClass.StaticInnerClass()

println myInstance.getAge()

Анонимные внутренние классы:

new Thread([run: {
    try {
        int i = 5
        while (i>0) {
            sleep(1000)
            println "${i--}"
        }
    } catch (InterruptedException ex) {
        // error
    }
}] as Runnable).start();

Enum:

enum Color{
	RED, GREEN, BLUE
}
 
def redColor = "RED" as Color
Color blueColor = "BLUE"
println blueColor

Импорт и переопределение

править

Импортируемые пакеты по умолчанию которые добавляются компилятором в каждый сценарий:

  • java.io.*
  • java.lang.*
  • java.math.BigDecimal
  • java.math.BigInteger
  • java.net.*
  • java.util.*
  • groovy.lang.*
  • groovy.util.*

Можно импортировать пакеты в статическом контексте, а также назначать алиасы/псевдонимы:

import static java.awt.Color.BLUE
import static Boolean.FALSE as F // назначаем алиас с именем F
import static Calendar.getInstance as now // назначаем алиас с именем now 
import static java.lang.Integer.*

println BLUE
// напечатает java.awt.Color[r=0,g=0,b=255]
println !F
// напечатает true

println now().time
// напечатает Fri Jun 08 06:37:20 EEST 2011
def a = parseInt("123")
println a
// напечатает 123
// представление списка как интерфейса Set
def contacts = ['a', 'b', 'c'] as Set
println contacts.size() // напечатает 3

// представление списка как интерфейса Map
def contacts = ['a':10, 'b':20, 'c':30] as Map
println contacts['a'] // напечатает 10

Работа с данными

править
// множественное присвоение
def (a, b, c) = [1,2,5]
println c

def (int myint, String mystring) = [5, "hello"]
println mystring

//диапазоны значений
def list = [3, 'Some string' , new Date()]

println list[0]
println list[1]

def letters = 'a'..'z'
def numbers = 0..<10

println numbers.size()

// работа с замыканиями
3.times { println 'Hi'}
[1,2,3].each {it -> println it}
(10..1).each {println it}
[ 'a' : 1, 'b' : 2 ].each {key, value -> println key}

Дата и время

править
import java.util.GregorianCalendar as D
import static java.util.Calendar.getInstance as now

println new D(2011, 11, 25).time
println now().time
println new Date() + 1

dateStr = "2011-06-03"
date = Date.parse("yyyy-MM-dd", dateStr)
println 'Date was '+date.format("MM/dd/yyyy")

Аннотации

править
import groovy.transform.Immutable
// Эта аннотация которая генерирует из данного класса синглетон
@Singleton(lazy=true)
class MySingleton {
    def getHello(){
        "hello world"
    }
}

println MySingleton.instance.hello

// Эта аннотация позволяет внедрить в класс Manager поведение метода из класса Employee 
class Employee {
    def doWork() { 'my work' }
}

class Manager {
    @Delegate
    Employee slave = new Employee()
}

def worker = new Manager()
println worker.doWork()

// либо можно сделать то же самое с помощью mixin
class Employee2 { def doWork() { 'my work' } }
class Manager2 {}

Manager2.mixin Employee2

println new Manager2().doWork()

// Аннотация @Immutable делает объект этого класса неизменяемым
// свойства объекта становятся readonly
@Immutable
class Person{
	String first, last
}

Regular Expressions (Регулярные выражения)

править

Pattern оператор

править

Оператор pattern (~) обеспечивает простой способ создать java.util.regex.Pattern.
Пример:

def p = ~/foo/
assert p instanceof Pattern

В основном оператор pattern используется со слеш-строками (строки обрамлённых слешами), тем не менее этот оператор может использоваться с любыми видами строк Groovy:

p = ~'foo'                             /*1*/                           
p = ~"foo"                             /*2*/                           
p = ~$/dollar/slashy $ string/$        /*3*/                           
p = ~"${pattern}"                      /*4*/
  1. использование строк в одинарных кавычках
  2. использование строк в двойных кавычках
  3. использование строки обрамлённой доллар-слешем, позволяет использовать слеш и доллар без их экранирования
  4. использование GString

Find оператор

править

Alternatively to building a pattern, you can directly use the find operator =~ to build a java.util.regex.Matcher instance:

def text = "some text to match"
def m = text =~ /match/                /*1*/                                    
assert m instanceof Matcher            /*2*/                                  
if (!m) {                              /*3*/                                  
    throw new RuntimeException("Oops, text not found!")
}
  1. =~ creates a matcher against the text variable, using the pattern on the right hand side
  2. the return type of =~ is a Matcher
  3. equivalent to calling if (!m.find())

Since a Matcher coerces to a boolean by calling its find method, the =~ operator is consistent with the simple use of Perl’s =~ operator, when it appears as a predicate (in if, while, etc.).

Match оператор

править

The match operator (==~) is a slight variation of the find operator, that does not return a Matcher but a boolean and requires a strict match of the input string:

m = text ==~ /match/                   /*1*/                                         
assert m instanceof Boolean            /*2*/                                     
if (!m) {                              /*3*/                                       
    throw new RuntimeException("Should not reach that point!")
}
  1. ==~ matches the subject with the regular expression, but match must be strict
  2. the return type of ==~ is therefore a boolean
  3. equivalent to calling if (text ==~ /match/)

это интерфейс и его стандартная реализация и состояние в одном месте.

trait Marks {
   void DisplayMarks() {
      println("Display Marks")
   } 
}
trait UserTrait {
    //должна быть обязательная реализация этого метода в наследниках
    abstract String name()
    //стандартная реализация метода
    String showName() {
        return "Hello, ${name()}!"
    }
}
class Employee implements UserTrait {
    String name() {
        return 'Bob'
    }
}
def emp = new Employee() as UserTrait
println emp.name()
println emp.showName()

Множественное наследование:

trait Marks {
   void DisplayMarks() {
      println("Marks1")
   } 
} 

trait Total {
   void DisplayTotal() { 
      println("Total")
   } 
}  
//множественное наследование
class Student implements Marks,Total {
   int StudentID 
}
Student st = new Student()
st.StudentID = 1
		
println(st.DisplayMarks())
println(st.DisplayTotal())

Расширение:

class Example {
   static void main(String[] args) {
      Student st = new Student()
      st.StudentID = 1
      println(st.DisplayMarks())
   } 
} 

trait Marks {
   void DisplayMarks() {
      println("Marks1")
   } 
} 

trait Total extends Marks {
   void DisplayMarks() {
      println("Total")
   } 
}  

class Student implements Total {
   int StudentID 
}

Переопределение методов:

trait SpeakingTrait {
    String speak() {
        return "Speaking!!"
    }
}

class Dog implements SpeakingTrait {
    String speak() {
        return "Bow Bow!!"
    }
}

This в Trait

trait UserTrait {
    def self() {
        return this 
    }
}

Свойства:

trait UserTrait implements Human { 
    String email
    String address
}

Tuples (Кортежи)

править
def tuple2 = new Tuple2<String, Boolean>('tuple 2', true)
// Получаем значения из кортежа и сравниваем
assert tuple2.v1 == 'tuple 2'
assert tuple2.v2 == true
def tuple3 = new Tuple3<String, Integer, BigDecimal>('add', 2, 40.0)
assert tuple3.v1 == 'add'
assert tuple3.v2 == 2

Композиция

править
class Human {
    int age
    String name
    String format(){
        name+"@"+age
    }
}
interface IHuman {
    String fullInfo()
}
//Композиция в одном варианте
class Man implements IHuman {
    Human comp
    @Override
    String fullInfo() {
        return comp.format()
    }
}
trait HumanTrait implements IHuman{
    String name
    int age
    def String formatValues(){
        name+"!!!"+age
    }
}
//Композиция через Trait во втором варианте
class Woman implements HumanTrait {

    @Override
    String fullInfo() {
        return formatValues()
    }
}
def adam = new Man(comp: new Human(age: 32, name: 'adam'))
println(adam.fullInfo())
def eva = new Woman(name: 'Eva', age: 18)
println(eva.fullInfo())

DSL (Domain Specific Language)

править

Простой пример реализации DSL в файле HelloWorldDSL.groovy:

class HelloWorldService {
    // этот метод станет ключевым словом в нашем DSL
    void printHelloWorld() {
        println("MyGradleImpl World")
    }
}
//"Делегат" который примет замыкание, инициализирует настройки поиска кому передать выполнение замыкания
class HelloWorldScript {
    static void execute(@DelegatesTo(strategy = Closure.DELEGATE_FIRST, value = HelloWorldService) Closure script) {
        script.resolveStrategy = Closure.DELEGATE_FIRST
        script.delegate = new HelloWorldService()
        script()
    }
}
//Передаем в метод execute() замыкание которое выполнит метод printHelloWorld()
HelloWorldScript.execute({
    printHelloWorld()
})

Перепишем последний вызов на другой вид вызова метода:

HelloWorldScript.execute {
    printHelloWorld()
}

Теперь реализуем простейший DSL для создания коллекции книг из библиотеки в файле LibraryDSL.groovy:

class Books {
    final String title
    private final List<Book> books = []
    Books(String title){
        this.title = title
    }
    // ключевое слово для нашего DSL синтаксиса
    void book(String title, String author) {
        books << new Book(title: title, author: author)
    }
    void printAll(){
        books.each {println it}
    }
    int bookSize(){
        return books.size()
    }
    public void printInfo(){
        println("Title:"+title)
        printAll()
        println("size:"+bookSize())
    }
}
@ToString
class Book {
    String title
    String author
}
class Library {
    static Books createCollection(String collectionTitle, @DelegatesTo(strategy = Closure.DELEGATE_FIRST, value = Books) Closure script) {
        def books = new Books(collectionTitle)
        script.resolveStrategy = Closure.DELEGATE_FIRST
        script.delegate = books
        script()
        return books
    }
}
//Запускаем создание коллекции книг из библиотеки
def books = Library.createCollection "First Collection", {
    book "Book 1", "A. Author"
    book "Book 2", "B. Author"
    book "Book 3", "C. Author"
}
//и выводим всю информацию про коллекцию
books.printInfo()

Grape - менеджер зависимостей

править

Grape - Встроенный менеджер зависимостей для загрузки из репозиториев Maven необходимых библиотек. Этот скрипт, при первом запуске загружает из удаленного репозитария артефакты Web-клиента Vert.x, добавляет их в classpath скрипта, создает экземпляры нужных классов, стартует Web-клиент, ждет 4 секунды и останавливает его. При следующем запуске зависимости Vert.x уже лежат в локальной файловой системе и не надо качать их из сети повторно. Скрипт для загрузки веб-страницы через Web-клиента Vert.x:

@Grapes([
@Grab(group='io.vertx', module='vertx-lang-groovy', version='4.1.0.Beta1'),
@Grab(group='io.vertx', module='vertx-core', version='4.1.0.Beta1'),
@Grab(group='io.vertx', module='vertx-web-client', version='4.1.0.Beta1')
//@GrabExclude("org.codehaus.groovy:groovy-swing") //исключить эту зависимость
])

import io.vertx.ext.web.client.WebClientOptions
import io.vertx.ext.web.client.WebClient
import io.vertx.core.Vertx

def vertx = Vertx.vertx()
def httpClient = WebClient.create(vertx)
def alarm = new Timer()
alarm.runAfter(2000) {
	httpClient.get(80, 'google.com', '/').send { resp ->
		if (resp.succeeded()) {
			def httpVersion = resp.result().version()
			println "HTTP version: ${httpVersion}"
			if(resp.result().statusCode() == 200){
			    println "OK:"+resp.result().statusCode()
			}else{
			    println "Something went wrong " + resp.result().statusCode()
			}
		} else {
			println "error!!!"
		}
	}
}
sleep(4000)
//System.exit(0)

Или более простой способ получения страницы:

def data = 'http://www.google.com'.toURL().text
println(data)

Скачанные зависимости хранятся в ${user.home}/.groovy/grapes. Настроить поиск зависимостей можно создав файл ${user.home}/.groovy/grapeConfig.xml:

<ivysettings>
  <settings defaultResolver="downloadGrapes"/>
  <resolvers>
    <chain name="downloadGrapes" returnFirst="true">
      <filesystem name="cachedGrapes">
        <ivy pattern="${user.home}/.groovy/grapes/[organisation]/[module]/ivy-[revision].xml"/>
        <artifact pattern="${user.home}/.groovy/grapes/[organisation]/[module]/[type]s/[artifact]-[revision](-[classifier]).[ext]"/>
      </filesystem>
      <ibiblio name="localm2" root="${user.home.url}/.m2/repository/" checkmodified="true" changingPattern=".*" changingMatcher="regexp" m2compatible="true"/>
     <ibiblio name="ibiblio" m2compatible="true"/>
    </chain>
  </resolvers>
</ivysettings>

Эта конфигурация говорит что зависимости нужно сначала искать в локальной файловой системе ${user.home}/.groovy/grapes, потом в ${user.home}/.m2/repository/, затем в jcenter, в ibiblio, и в java.net2 репозитариях.

Подключение к базе данных и запрос SELECT(необходимо подключить драйвер JDBC для MySQL или другой базы):

import groovy.sql.Sql

Sql sql = Sql.newInstance("jdbc:mysql://localhost:3306/testdatabase", "user", "password", "com.mysql.jdbc.Driver")
// создаем таблицу
sql.execute('''create table users (
    id int(11) not null primary key auto_increment,
    username varchar(255), 
    age int(11)
)''')

Пример скрипта, запускаемого из командной строки с динамической установкой зависимостей, для доступа к базе SQLite с именем test_sqlite.groovy:

@Grapes([
 @Grab(group='org.xerial',module='sqlite-jdbc',version='3.34.0'),
 @GrabConfig(systemClassLoader=true)
])
import groovy.sql.Sql
def sql = Sql.newInstance("jdbc:sqlite:sample.db", "org.sqlite.JDBC")
sql.execute("drop table if exists person")
sql.execute("create table person (id integer, name string)")
def people = sql.dataSet("person")
people.add(id:1, name:"leo")
people.add(id:2,name:'yui')

sql.eachRow("select * from person") {
  println("id=${it.id}, name= ${it.name}")
}

Выполнение простого sql-запроса

править

Вставка новой записи:

def age = 25
def name = "Adam"
sql.execute("insert into users (username, age) values (${name}, ${age})")
// or
sql.executeInsert("insert into users (username, age) values (${name}, ${age})")

Выборка первой записи из результата запроса:

def rowFirst = sql.firstRow('select username, age from users')
println "Row: Name = ${rowFirst.username} and Age = ${rowFirst.age}"

Выборка всех записей:

sql.eachRow("select * from users"){ row -> println row.username }

Удаление записи:

int id = 2
sql.execute('delete from users where id = ?' , [id])

Обновление записи:

def newUsername = 'New Name'
int rowsAffected = sql.executeUpdate('update users set username = ? where id=2', [newUsername])
println "updated: ${rowsAffected}";

Выполнение более сложных запросов

править
import groovy.sql.Sql
import groovy.sql.DataSet

DataSet users
Sql sql = Sql.newInstance("jdbc:mysql://localhost:3306/testdatabase", "user", "password", "com.mysql.jdbc.Driver")
// таблица users должна быть создана
users = sql.dataSet("USERS")
users.add(username: "James", age: 55) //вставка новой записи в таблицу

DataSet findedUsers = users.findAll() // получение всех записей и их вывод
findedUsers.each{ println it.username}

Добавление записей в транзакции:

import groovy.sql.Sql

DataSet users
Sql sql = Sql.newInstance("jdbc:mysql://localhost:3306/testdatabase", "user", "password", "com.mysql.jdbc.Driver")

// таблица users должна быть создана
users = sql.dataSet("USERS")

sql.withTransaction {
    users.add(username: "Alec", age: 25)
    users.add(username: "Alec 2", age: 25)
}

Блочное добавление записей:

import groovy.sql.Sql

Sql sql = Sql.newInstance("jdbc:mysql://localhost:3306/testdatabase", "user", "password", "com.mysql.jdbc.Driver")
def updateCounts = sql.withBatch('insert into users(username, age) values (?, ?)') { ps ->
    ps.addBatch("New Name", 22)      // varargs style
    ps.addBatch(["New Name", 18])     // list
    ps.addBatch(["New Name", 31])    
}

Вызов SQL процедуры

править

Вызов процедур:

import groovy.sql.Sql

Sql sql = Sql.newInstance("jdbc:mysql://localhost:3306/testdatabase", "user", "password", "com.mysql.jdbc.Driver")
sql.call("{? = call MyProcedure(?)}", [Sql.VARCHAR, 'Sam']) { name ->
    assert name == 'Adam'
}
// or with GString
def first = 'Sam'
sql.call("{$Sql.VARCHAR = call MyProcedure($first)}") { name ->
    assert name == 'Adam'
}

Работа с JSON

править
import groovy.json.JsonBuilder
import groovy.json.JsonSlurper
import groovy.json.JsonOutput
// Создание JSON объекта
def json = new JsonBuilder()
json.person {
    username "Guillaume"
    age 33
    pets "Hector", "Felix"
}

println json.toString() // вывод в строку полученного объекта

// Парсинг строки в JSON формате
def someJSONString = '{"person":{"username":"Guillaume","age":33,"pets":["Hector","Felix"]}}'
println JsonOutput.prettyPrint(someJSONString) // форматированный вывод объекта

def slurper = new JsonSlurper()
def doc = slurper.parseText(someJSONString)

println doc.person.username // вывод имени
doc.person.pets.each {println it} // вывод животных

Работа с XML

править

Создание XML документа из объекта:

import groovy.xml.MarkupBuilder

writer = new StringWriter()
builder = new MarkupBuilder(writer)
petsList = [ "Hector", "Felix"]
builder.person() {
        username("Guillaume")
        age("33")
        gender("female")
        pets(){
            for (e in petsList){pet(e)}
        }

}
println writer.toString()

Работа с HTML:

import groovy.xml.MarkupBuilder
def writer = new StringWriter()
def builder = new MarkupBuilder(writer)
builder.html() {
    head() {
        title("This is the title")
    }

    body() {
        div("class" : "main") {
            p("this is the body")
        }
    }
}

println writer.toString()

Парсинг XML документа

def xmlString = """
  <person>
  <username>Guillaume</username>
  <age>33</age>
  <gender>female</gender>
  <pets>
    <pet>Hector</pet>
    <pet>Felix</pet>
  </pets>
</person>
"""

def person = new XmlSlurper().parseText(xmlString)
println person.username
person.pets.pet.each {println "pet's name:"+it}


Работа с YAML

править

Создание:

import groovy.yaml.YamlBuilder

def config = new YamlBuilder()
config {
    application 'Sample App'
    version '1.0.1'
    // Вложенный элемент YAML.
    database {
        url 'jdbc:db//localhost'   
    }
    // Здесь будет массив строк.
    services 'ws1', 'ws2'    
}
println(config.toString())

Парсинг:

import groovy.yaml.YamlSlurper

def configYaml = '''\
application: "Sample App"
version: "1.0.1"
services:
- "WS1"
- "WS2"
'''
def config = new YamlSlurper().parseText(configYaml)
assert config.application == 'Sample App'

Потоки и асинхронная работа

править

Обычный запуск в новом потоке:

Thread.start {
  println Thread.currentThread().getId()
}

С помощью фреймворка для параллельного выполнения задач GPars:

//эта аннотация скачивает и устанавливает библиотеку в classpath
@Grab(group='org.codehaus.gpars', module='gpars', version='1.2.1')
import static groovyx.gpars.GParsExecutorsPool.withPool
//С помощью GPars 5 раз сделать асинхронную загрузку страницы(и распечатать ответ) 
//по url в отдельных потоках
int count = 5
withPool(count) {
	count.times {
		Closure callUrl = {"http://google.com".toURL().withReader {
			reader -> println reader.readLines()
			}}
		callUrl.callAsync()
		
	}
}

GUI. SWT (Standard Widget Toolkit)

править

Desktop программу можно написать на SWT - кросс-платформенную оболочку для графических библиотек конкретных платформ. Файл-скрипт SWTExample.groovy

@Grapes(@Grab(group='org.eclipse.swt', module='org.eclipse.swt.win32.win32.x86_64', version='4.3'))
import org.eclipse.swt.widgets.*
import org.eclipse.swt.layout.GridLayout
import org.eclipse.swt.SWT

def display = new Display ()
def shell = new Shell(display)
shell.setLayout(new GridLayout())
def button = new Button(shell, SWT.NONE)
button.setText("Click and check the console")
button.addListener(SWT.Selection, new Listener() {
      public void handleEvent(Event e) {
        switch (e.type) {
        case SWT.Selection:
          println("button pressed")
          break
        }
      }
    });
//shell.setText("Snippet 1")
shell.open()



while (!shell.isDisposed ()) {
	if (!display.readAndDispatch ()) display.sleep ()
}
display.dispose()

Прочие советы

править

Парсинг строки, и выполнение ее в рантайме:

int sum = (Integer) Eval.me("1+1");
print sum

Добавление аннотации @CompileStatic к классам приводит к проверке типов во время компиляции:

@CompileStatic
class Factorial {}

Лямбда в Java стиле:

def inc = n -> n + 1 
assert inc(1) == 2
//со стандартным параметром factor
def multiplyBy = (n, factor = 2) -> n * factor 
assert multiplyBy(1) == 2

Неизменяемые коллекции:

import static groovy.test.GroovyAssert.shouldFail
 
// Создаем неизменяемый список
def list = ['Groovy', 'Gradle', 'Micronaut'].asUnmodifiable()
 
shouldFail(UnsupportedOperationException) {
    // Не можем добавить новый элемент.
    list << 'Java'
}
//Создаем неизменяемый ассоциативный массив
def data = [name: 'Language', subject: 'Groovy'].asUnmodifiable()
shouldFail(UnsupportedOperationException) {
    // Не можем добавить новый ключ
    data.key = 'Java'
}

Ссылки

править