Groovy
Данный учебник представляет из себя руководство по языку программирования Groovy
Установка и настройка
правитьНа странице загрузки выбираем удобный способ установки (предварительно установив Java (JDK)). После установки могут быть доступны Groovy Console и Groovy Shell в которых можно набирать код и запускать его.
Также запустить код можно прямо из Groovy Web Console.
Перед запуском проверьте чтобы в пути Вашей ОС были прописаны переменная GROOVY_HOME и путь в переменной PATH к исполняему файлу groovy.bat (или groovy.sh)
Также файл с кодом можно выполнить через Java. Делается это так:
- В консоли набираем:
groovyc helloworld.groovy
и получаем скомпилированный в байт-код файл helloworld.class - Запускаем его набирая в консоли:
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*/
- использование строк в одинарных кавычках
- использование строк в двойных кавычках
- использование строки обрамлённой доллар-слешем, позволяет использовать слеш и доллар без их экранирования
- использование 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!")
}
- =~ creates a matcher against the text variable, using the pattern on the right hand side
- the return type of =~ is a Matcher
- 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!")
}
- ==~ matches the subject with the regular expression, but match must be strict
- the return type of ==~ is therefore a boolean
- equivalent to calling if (text ==~ /match/)
Traits
правитьэто интерфейс и его стандартная реализация и состояние в одном месте.
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 репозитариях.
Groovy SQL
правитьПодключение к базе данных и запрос 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'
}