Приховані особливості Groovy?


78

Здається, про Groovy забули в цій темі, тому я просто задам те саме питання для Groovy.

  • Спробуйте обмежити відповіді на Groovy core
  • Одна функція на відповідь
  • Наведіть приклад і короткий опис функції, а не просто посилання на документацію
  • Позначте об’єкт, використовуючи жирний заголовок як перший рядок

Дивитися також:

  1. Приховані можливості Python
  2. Приховані особливості Ruby
  3. Приховані можливості Perl
  4. Приховані можливості Java

Відповіді:


56

Використання оператора розширеної точки

def animals = ['ant', 'buffalo', 'canary', 'dog']
assert animals.size() == 4
assert animals*.size() == [3, 7, 6, 3]

Це ярлик для animals.collect { it.size() }.


Що означає третій рядок?
— ripper234

7
З контексту це означає виклик методу size для кожного елемента масиву і повернення масиву результатів. Насправді досить круто :-)
— Майкл Рутерфурд

39

Метод with дозволяє перетворити це:

 myObj1.setValue(10)
 otherObj.setTitle(myObj1.getName())
 myObj1.setMode(Obj1.MODE_NORMAL)

в це

 myObj1.with {
    value = 10
    otherObj.title = name
    mode = MODE_NORMAL
 }

3
це приносить мені старі спогади про об'єкт pascal :-)
— fortran

1
Враховуючи, що це Groovy, чи не буде більш типовим порівнянням між myObj1.value = 10(і т. Д.) Та останнім, оскільки вам не потрібно викликати метод встановлення?
— Філіп

37

Використання хешів як псевдооб’єктів.

def x = [foo:1, bar:{-> println "Hello, world!"}]
x.foo
x.bar()

У поєднанні з набором качок ви можете пройти довгий шлях із таким підходом. Навіть не потрібно видавати оператора "як".


2
нове для Groovy - це справді дуже приємно.
— Стів Б.

37

Хтось знає про Елвіса ?

def d = "hello";
def obj = null;

def obj2 = obj ?: d;   // sets obj2 to default
obj = "world"

def obj3 = obj ?: d;  // sets obj3 to obj (since it's non-null)

1
це те саме, що нульовий оператор злиття (??) із C #?
— Олексій Бараноський,

Здавалося б, так, хоча мені довелося шукати C # op.
— billjamesdev

Не зовсім так, це скорочений потрійний оператор. Я написав про це: colinharrington.net/blog/2008/10/groovy-elvis-operator Ви також можете робити там повний вираз :-)
— Колін Гаррінгтон,

Код, розміщений у відповіді, не компілюється, оскільки ключове слово "за замовчуванням" використовується як змінна. Натомість використовуйте "d", щоб зробити компіляцію коду.
— Vorg van Geir

2
Немає жодної важливої ​​причини, лише дотримуючись конвенції, запропонованої ОП. На той час я не брав до уваги освіжаючий ефект, який спричинить моя дія.
— gotomanners

35

З’ясувати, які методи існують на об’єкті, так само просто, як запитати metaClass:

"foo".metaClass.methods.name.sort().unique()

відбитки:

["charAt", "codePointAt", "codePointBefore", "codePointCount", "compareTo",
 "compareToIgnoreCase", "concat", "contains", "contentEquals", "copyValueOf", 
 "endsWith", "equals", "equalsIgnoreCase", "format", "getBytes", "getChars", 
 "getClass", "hashCode", "indexOf", "intern", "lastIndexOf", "length", "matches", 
 "notify", "notifyAll", "offsetByCodePoints", "regionMatches", "replace", 
 "replaceAll", "replaceFirst", "split", "startsWith", "subSequence", "substring", 
 "toCharArray", "toLowerCase", "toString", "toUpperCase", "trim", "valueOf", "wait"]

1
Спочатку це здається безглуздо. Але це неймовірно корисно. У python у вас є вбудована функція dir: dir ("foo") надає всі методи для рядка.
— santiagobasulto

28

Для перехоплення відсутніх статичних методів використовуйте наступне

 Foo {
    static A() { println "I'm A"}

     static $static_methodMissing(String name, args) {
        println "Missing static $name"
     }
 }

Foo.A()  //prints "I'm A"
Foo.B()  //prints "Missing static B"

- Кен


Новинка в Groovy, у неї виникають труднощі при аналізі цього.
— ripper234

3
Object Foo не має визначеного статичного методу з назвою B. Тим не менш, ви можете реалізувати його на льоту, додавши метод, який називається "$ static_methodMissing (String, Object)" і реалізуючи там все, що завгодно. Цей магічний метод викликається щоразу, коли викликається статичний метод, а об’єкт не має такого статичного методу.
— Jen S.

24

Деструктурування

Це могло б називатися по-іншому в Groovy; це називається деструктуризацією в кложурі. Ви ніколи не повірите, наскільки це зручно.

def list = [1, 'bla', false]
def (num, str, bool) = list
assert num == 1
assert str == 'bla'
assert !bool

Це називається багаторазовим призначенням у Groovy. Вікіпедія називає це паралельним призначенням .
— Frank Kusters

Деструктуризація в Clojure набагато потужніша, ніж багаторазове призначення. clojure.org/…
— Джейсон,

21

Для тестування Java-коду за допомогою groovy приголомшливий конструктор графіків об’єктів:

def company = builder.company( name: 'ACME' ) {
   address( id: 'a1', line1: '123 Groovy Rd', zip: 12345, state: 'JV' )
   employee(  name: 'Duke', employeeId: 1 ){
      address( refId: 'a1' )
   }
}

Стандартна функція, але все ж дуже приємна.

ObjectGraphBuilder

(Вам потрібно вказати будь-які властивості вашого POJO, які є Listзначенням порожнього списку за замовчуванням, а не nullдля того, щоб працювати підтримка конструктора .)


19
println 
"""
Groovy has "multi-line" strings.
Hooray!
"""

Ах, краса багаторядкових струн. Кожна мова повинна їх прийняти.
— ripper234

2
Не впевнений, чому багаторядковий рядок потребує "" "як роздільник, коли" міг бути розширений, щоб дозволити багаторядкові, а також однорядкові рядки.
— Vorg van Geir

2
@VorgvanGeir за допомогою "" "означає, що вам не доведеться рятуватися".
— undefined

1
@Brian Правда, але "" "a \ bc" de "f \ g" "" не компілюється, оскільки вам потрібно уникнути \ або \ g, і \ b діятиме як зворотний простір, якщо ви його не уникнете. Який сенс не потребувати втечі ", коли вам все одно потрібно врятуватися КОЖНОЮ спеціальною послідовністю всередині рядка?
— Vorg van Geir

Тому що деякі з нас хочуть мати можливість писати "foo \ tbar". Але у Groovy також є: println (/ a \ bc "de" f \ g /) // -> a \ bc "de" f \ g
— DarkStar

15

У groovy 1.6 регулярні вирази працюють з усіма ітераторами закриття (як кожен, збирати, вводити тощо) і дозволяють легко працювати з групами захоплення:

def filePaths = """
/tmp/file.txt
/usr/bin/dummy.txt
"""

assert (filePaths =~ /(.*)\/(.*)/).collect { full, path, file -> 
        "$file -> $path"
    } ==  ["file.txt -> /tmp", "dummy.txt -> /usr/bin"]

15

На відміну від Java, у Groovy в операторі switch можна використовувати що завгодно , а не лише примітивні типи. У типовій подіїВиконаний метод

switch(event.source) {
   case object1:
        // do something
        break
   case object2:
        // do something
        break
}

15

Використання оператора космічного корабля

Мені подобається оператор Spaceship , корисний для всіляких сценаріїв нестандартного сортування. Деякі приклади використання наведено тут . Однією з ситуацій, коли це особливо корисно, є створення компаратора на льоту об’єкта за допомогою декількох полів. напр

def list = [
    [ id:0, first: 'Michael', last: 'Smith', age: 23 ],
    [ id:1, first: 'John', last: 'Smith', age: 30 ],
    [ id:2, first: 'Michael', last: 'Smith', age: 15 ],    
    [ id:3, first: 'Michael', last: 'Jones', age: 15 ],   
]

// sort list by last name, then first name, then by descending age
assert (list.sort { a,b -> a.last <=> b.last ?: a.first <=> b.first ?: b.age <=> a.age })*.id == [ 3,1,0,2 ]

14

Закриття може змусити всі старі пробні ігри управління ресурсами зникнути. Потік файлів автоматично закривається в кінці блоку:

new File("/etc/profile").withReader { r ->
    System.out << r
}

1
А також файловий маніпулятор належним чином закритий на випадок, коли під час закриття виникає виняток, мені це подобається навіть краще, ніж try-with-resources.
— DarkStar

13

Функції, що надаються перетвореннями всередині groovy.transformпакета GDK , такі як:

  • @Immutable: Анотація @Immutable вказує компілятору виконати перетворення AST, яке додає необхідні геттери, конструктори, дорівнює, hashCode та інші допоміжні методи, які зазвичай пишуться при створенні незмінних класів із визначеними властивостями.
  • @CompileStatic: Це дозволить компілятору Groovy використовувати перевірку часу компіляції у стилі Java, а потім виконувати статичну компіляцію, тим самим минаючи протокол мета-об'єкта Groovy.
  • @Canonical: Анотація @Canonical вказує компілятору виконати перетворення AST, яке додає позиційні конструктори, дорівнює, hashCode та гарний принт toString до вашого класу.

Інші:

  • @Slf4jЦе локальне перетворення додає можливість реєстрації у вашій програмі за допомогою ведення журналу LogBack. Кожен виклик методу для незв’язаної змінної з іменем log буде відображено у виклику реєстратора.
  • XML Slurper Groovy : легкий синтаксичний аналіз XML. Вбивча особливість!

12

Ви можете перетворити список на карту за допомогою toSpreadMap (), зручного часом, коли порядку в списку достатньо для визначення ключів та значень, пов’язаних з ними. Див. Приклад нижче.

def list = ['key', 'value', 'foo', 'bar'] as Object[]
def map = list.toSpreadMap()

assert 2 == map.size()
assert 'value' == map.key
assert 'bar' == map['foo']

Це потрібно as Object []в першому рядку?
— Каміль

12

Впровадження інтерфейсу на основі закриття

Якщо у вас є набране посилання, наприклад:

MyInterface foo

Ви можете реалізувати весь інтерфейс, використовуючи:

foo = {Object[] args -> println "This closure will be called by ALL methods"} as MyInterface

Як варіант, якщо ви хочете реалізувати кожен метод окремо, ви можете використовувати:

foo = [bar: {-> println "bar invoked"}, 
    baz: {param1 -> println "baz invoked with param $param1"}] as MyInterface

8

Видалити nullзначення зі списку

def list = [obj1, obj2, null, obj4, null, obj6]
list -= null
assert list == [obj1, obj2, obj4, obj6]

7

Я знаю, що я трохи запізнився, але, думаю, тут відсутні деякі приємні функції:

Оператори збору плюс / мінус

def l = [1, 2, 3] + [4, 5, 6] - [2, 5] - 3 + (7..9)
assert l == [1, 4, 6, 7, 8, 9]

def m = [a: 1, b: 2] + [c: 3] - [a: 1]
assert m == [b: 2, c: 3]

Оператор перемикання

switch (42) {
  case 0: .. break
  case 1..9: .. break
  case Float: .. break
  case { it % 4 == 0 }: .. break
  case ~/\d+/: .. break
}

Діапазони та індексація

assert (1..10).step(2) == [1, 3, 5, 7, 9]
assert (1..10)[1, 4..8] == [2, 5, 6, 7, 8, 9]
assert ('a'..'g')[-4..-2] == ['d', 'e', 'f']

Імена змінних Unicode

def α = 123
def β = 456
def Ω = α * β
assert Ω == 56088


6

Підкреслення в літералах

Під час написання довгих буквальних чисел оку важче зрозуміти, як деякі числа згруповані, наприклад, з групами тисяч, слів і т. Д. Якщо дозволити розміщувати підкреслення в літералах чисел, легше виявити ці групи:

long creditCardNumber = 1234_5678_9012_3456L
long socialSecurityNumbers = 999_99_9999L
double monetaryAmount = 12_345_132.12
long hexBytes = 0xFF_EC_DE_5E
long hexWords = 0xFFEC_DE5E
long maxLong = 0x7fff_ffff_ffff_ffffL
long alsoMaxLong = 9_223_372_036_854_775_807L
long bytes = 0b11010010_01101001_10010100_10010010

5

Ще один приємний аргумент - упорядкування за допомогою неявних аргументів.

Цей код:

def foo(Map m=[:], String msg, int val, Closure c={}) {
  [...]
}

Створює всі ці різні методи:

foo("msg", 2, x:1, y:2)
foo(x:1, y:2, "blah", 2)
foo("blah", x:1, 2, y:2) { [...] }
foo("blah", 2) { [...] }

І більше. Неможливо зіпсувати, ставлячи іменовані та порядкові аргументи в неправильному порядку / положенні.

Звичайно, у визначенні "foo" ви можете залишити "String" та "int" у "String msg" та "int val" - я залишив їх просто для ясності.


Я хотів би, щоб це було так, але наразі Groovy (1.6) підтримує лише іменовані аргументи для конструкторів об'єктів. Ви можете використовувати цей синтаксис для викликів методів, але він пакує будь-які іменовані аргументи в Map, а потім шукає foo (Map).
— Коді Кастерлайн,

Я збентежений щодо того, що, на вашу думку, я сказав, що означає інше.
— Роберт Фішер,

4

Я думаю, що це комбінація замикань як параметрів та значень параметра за замовчуванням:

public void buyItems(Collection list, Closure except={it > 0}){
  list.findAll(){except(it)}.each(){print it}
}
buyItems([1,2,3]){it > 2}
buyItems([0,1,2])

друкує: "312"


4

Використання оператора поширення в параметрах методу

Це чудова допомога при перетворенні коду в дані:

def exec(operand1,operand2,Closure op) {
    op.call(operand1,operand2)
}

def addition = {a,b->a+b}
def multiplication = {a,b->a*b}

def instructions = [
     [1,2,addition],
     [2,2,multiplication]
]

instructions.each{instr->
    println exec(*instr)
}

Також корисно таке використання:

String locale="en_GB"

//this invokes new Locale('en','GB')
def enGB=new Locale(*locale.split('_'))

Ні, я маю на увазі перетворення коду в дані, створення масиву, який є даними, еквівалентний списку аргументів, який зазвичай є статичним. Але я розумію вашу думку, це залежить від вашої точки зору. Я розглядаю це з точки зору рефакторингу існуючого статичного коду до більш динамічного! C коду.
— Luis Muñiz

Можливо, "при використанні дизайну, керованого даними"?
— DarkStar

3

Мемоізація

Запам'ятовування - це техніка оптимізації, яка полягає у збереженні результатів дорогих викликів функцій і поверненні кешованого результату при кожному повторному виклику функції з тими ж аргументами.

Існує необмежена версія, яка буде кешувати коли-небудь пару (вхідні аргументи, повернене значення), яку вона коли-небудь побачить; та обмежена версія, яка буде кешувати останні побачені N вхідних аргументів та їх результати, використовуючи кеш LRU.

Запам'ятовування методів:

import groovy.transform.Memoized

@Memoized
Number factorial(Number n) {
    n == 0 ? 1 : factorial(n - 1)
}

@Memoized(maxCacheSize=1000)
Map fooDetails(Foo foo) {
    // call expensive service here
}

Запам'ятовування закриття:

def factorial = {Number n ->
    n == 0 ? 1 : factorial(n - 1)
}.memoize()

fooDetails = {Foo foo ->
    // call expensive service here
}.memoizeAtMost(1000)

Сторінка Вікіпедії містить велику інформацію про використання мемоїзації в інформатиці. Я лише зазначу одне просте практичне використання.

Відклавши ініціалізацію константи до останнього можливого моменту

Іноді у вас є постійне значення, яке неможливо ініціалізувати під час визначення класу або часу створення. Наприклад, вираз константи може використовувати іншу константу або метод з іншого класу, який буде підключено чимось іншим (Spring або подібним) після ініціалізації вашого класу.

У цьому випадку ви можете перетворити свою константу в геттер і прикрасити її @Memoized. Він буде обчислений лише один раз, перший раз до нього здійснено доступ, а потім значення, кешоване та використане повторно:

import groovy.transform.Memoized

@Memoized
def getMY_CONSTANT() {
    // compute the constant value using any external services needed
}

2

Groovy може працювати багато, як Javascript. Ви можете мати приватні вари та функції через закриття. Ви також можете каррі функції за допомогою закриття.

class FunctionTests {

def privateAccessWithClosure = {

    def privVar = 'foo'

    def privateFunc = { x -> println "${privVar} ${x}"}

    return {x -> privateFunc(x) } 
}


def addTogether = { x, y ->
    return x + y
}

def curryAdd = { x ->
    return { y-> addTogether(x,y)}
}

public static void main(String[] args) {
    def test = new FunctionTests()

    test.privateAccessWithClosure()('bar')

    def curried = test.curryAdd(5)

    println curried(5)
}
}

вихід:

foo bar 10


2

Виклик динамічного методу

Ви можете викликати метод, використовуючи рядок із його назвою

class Dynamic {
    def one() { println "method one()" }
    def two() { println "method two()" }
}

def callMethod( obj, methodName ) {
    obj."$methodName"()
}

def dyn = new Dynamic()

callMethod( dyn, "one" )               //prints 'method one()'
callMethod( dyn, "two" )               //prints 'method two()'
dyn."one"()                            //prints 'method one()'

2

Як побудувати дерево JSON за кілька рядків у groovy?

1) визначте своє дерево із самореференційним withDefaultзакриттям

def tree // declare  first before using a self reference
tree = { ->  [:].withDefault{ tree() } }

2) Створіть власне дерево JSON

frameworks = tree()
frameworks.grails.language.name = 'groovy'
frameworks.node.language.name = 'js'

def result =  new groovy.json.JsonBuilder(frameworks)

Що дає: {"grails":{"language":{"name":"groovy"}},"node":{"language":{"name":"js"}}}


2

Оператор безпечної навігації

Оператор безпечної навігації використовується, щоб уникнути NullPointerException. Зазвичай, коли у вас є посилання на об'єкт, вам може знадобитися переконатися, що він не є нульовим, перш ніж отримувати доступ до методів або властивостей об'єкта. Щоб уникнути цього, оператор безпечної навігації просто поверне null замість викиду винятку, наприклад:

def person = Person.find { it.id == 123 }        // find will return a null instance    
def name = person?.name                          // use of the null-safe operator prevents from a NullPointerException, result is null

1

Кілька змінних уповільнення

1) Декларації кількох змінних в одному рядку

def (a,b,c) = [1,2,3]

2) Використання різних оголошень типу.

def (String a, int b) = ['Groovy', 1]

0

Оператор примусу

Оператор примусу (as) - це варіант кастингу. Примус перетворює об'єкт з одного типу на інший, не будучи сумісними для призначення. Візьмемо приклад:

Ціле число x = 123
Рядок s = (Рядок) x
Ціле число не можна присвоїти рядку , тому воно створюватиме ClassCastException під час виконання Це можна виправити, використовуючи замість цього примус:

Integer x = 123 String s = x as String
Integer не може бути присвоєно рядку, але використання as примусить його до рядка

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.