Groovy的隐藏功能?

Hidden features of Groovy?

似乎groovy在这个线程中被遗忘了,所以我将向groovy提出同样的问题。

  • 尝试限制对groovy核心的回答
  • 每个答案一个功能
  • 给出功能的示例和简短描述,而不仅仅是指向文档的链接
  • 用粗体标题作为第一行标记功能

参见:

  • Python的隐藏特征
  • 红宝石的隐藏特征
  • Perl的隐藏特性
  • Java的隐藏特性

  • 使用扩散点运算符

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

    这是animals.collect { it.size() }的快捷方式。


    WITH方法允许转换:

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

    进入这个

    1
    2
    3
    4
    5
     myObj1.with {
        value = 10
        otherObj.title = name
        mode = MODE_NORMAL
     }


    有人知道埃尔维斯吗?

    1
    2
    3
    4
    5
    6
    7
    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
    2
    3
    def x = [foo:1, bar:{-> println"Hello, world!"}]
    x.foo
    x.bar()

    结合duck输入,您可以使用这种方法进行很长的路。甚至不需要删掉"as"操作符。


    找到对象上的方法与询问元类一样简单:

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

    印刷品:

    1
    2
    3
    4
    5
    6
    7
    ["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"]


    To intercept missing static methods use the following

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     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中被称为其他东西;在clojure中被称为破坏。你永远不会相信它有多有用。

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


    为了用Groovy测试Java代码,对象图形生成器是令人惊异的:

    1
    2
    3
    4
    5
    6
    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' )
       }
    }

    标准功能,但仍然很好。

    对象图形生成器

    (您需要为您的POJO的任何属性指定一个空列表的默认值,而不是null,以便构建器支持工作。)


    1
    2
    3
    4
    5
    println
    """
    Groovy has"multi-line" strings.
    Hooray!
    """


    不像Java,在Groovy中,任何东西都可以用在转换语句中,而不仅仅是基元类型。在典型的EventPerformed方法中

    1
    2
    3
    4
    5
    6
    7
    8
    switch(event.source) {
       case object1:
            // do something
            break
       case object2:
            // do something
            break
    }

    在groovy 1.6中,正则表达式与所有闭包迭代器(如每个闭包迭代器、collect、inject等)一起工作,并允许您轻松地使用捕获组:

    1
    2
    3
    4
    5
    6
    7
    8
    def filePaths ="""
    /tmp/file.txt
    /usr/bin/dummy.txt
    """


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


    使用宇宙飞船操作员

    我喜欢宇宙飞船操作员,对各种自定义排序场景都很有用。这里有一些用法示例。其中一个特别有用的情况是使用多个字段在对象的运行中创建一个比较器。例如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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 ]

    闭包可以使所有旧的资源管理的尝试最终消失。文件流在块末尾自动关闭:

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


    由gdk的groovy.transform包内的转换提供的特性,例如:

    • @Immutable:@immutable注释指示编译器执行ast转换,该转换添加必要的getter、构造函数、equals、hashcode和其他助手方法,这些方法通常是在创建具有定义属性的不可变类时编写的。
    • @CompileStatic:这将允许Groovy编译器使用Java风格的编译时间检查,然后执行静态编译,从而绕过Groovy元对象协议。
    • @Canonical:@canonical注释指示编译器执行ast转换,该转换将位置构造函数、equals、hashcode和漂亮的print to字符串添加到类中。

    其他:

    • @Slf4j这个本地转换使用日志记录向程序添加了日志记录功能。对名为log的未绑定变量的每个方法调用都将映射到对记录器的调用。
    • groovy的xml slurper:易于解析XML。杀手锏!


    基于闭包的接口实现

    如果您有键入的引用,例如:

    1
    MyInterface foo

    您可以使用以下方法实现整个接口:

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

    或者,如果要单独实现每个方法,可以使用:

    1
    2
    3
    4
    5
    6
    7
    foo = [bar: {-> println"bar invoked
    <div class="
    suo-content">[collapse title=""]<ul><li>缺少右大括号?</li><li>@Edstaub谢谢,修好了</li></ul>[/collapse]</div><hr><P>您可以使用tospreadmap()将列表转换为映射,这在列表中的顺序足以确定键及其关联值时非常方便。见下面的例子。</P>[cc lang="groovy"]def list = ['key', 'value', 'foo', 'bar'] as Object[]
    def map = list.toSpreadMap()

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


    从列表中删除null

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

    @代表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Foo {
        def footest() { return"footest"}  
    }

    class Bar {
        @Delegate Foo foo = new Foo()    
    }

    def bar = new Bar()

    assert"footest" == bar.footest()

    我知道我有点晚了,但我认为这里缺少一些好的功能:

    集合加减运算符

    1
    2
    3
    4
    5
    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语句

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

    范围和索引

    1
    2
    3
    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变量名

    1
    2
    3
    4
    def α = 123
    def β = 456
    def Ω = α * β
    assert Ω == 56088


    文字中的下划线

    在编写长的文字数字时,很难弄清楚一些数字是如何组合在一起的,例如使用数千个组、单词等。通过允许在数字文字中放置下划线,更容易发现这些组:

    1
    2
    3
    4
    5
    6
    7
    8
    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

    用隐式参数重新排序的参数是另一个不错的参数。

    此代码:

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

    创建所有这些不同的方法:

    1
    2
    3
    4
    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 msg"和"int val"中的"string"和"int",我只是为了清晰起见而把它们放在里面。


    在方法参数中使用扩散算子

    在将代码转换为数据时,这是一个很好的帮助:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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)
    }

    这一用法也很有用:

    1
    2
    3
    4
    String locale="en_GB"

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


    我认为它是闭包作为参数和参数默认值的组合:

    1
    2
    3
    4
    5
    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"


    记忆化

    memoization是一种优化技术,它包括存储昂贵函数调用的结果,并在用相同参数再次调用函数时返回缓存的结果。

    有一个不受限制的版本,它将缓存它将看到的任何一对(输入参数,返回值);以及一个有限的版本,它将使用一个LRU缓存缓存看到的最后n个输入参数及其结果。

    方法的记忆化:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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
    }

    关闭的记忆:

    1
    2
    3
    4
    5
    6
    7
    def factorial = {Number n ->
        n == 0 ? 1 : factorial(n - 1)
    }.memoize()

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

    维基百科的网页上有大量关于记忆在计算机科学中的应用的信息。我将指出一个简单的实际用途。

    将常量的初始化推迟到最后可能的时刻

    有时您有一个常量值,它在类定义或创建时无法初始化。例如,常量表达式可以使用另一个常量或来自不同类的方法,这些常量或方法在类初始化后将由其他东西(spring或类似的东西)插入。

    在这种情况下,您可以将常量转换为getter,并用@Memoized对其进行修饰。它将只计算一次(第一次访问时),然后缓存并重用值:

    1
    2
    3
    4
    5
    6
    import groovy.transform.Memoized

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


    groovy可以像javascript一样工作。您可以通过闭包来拥有私有变量和函数。您还可以使用闭包编写函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    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)
    }
    }

    输出:

    起泡棒十


    如何在groovy中用几行代码构建JSON树?

    1)用自引用withDefault闭包定义树

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

    2)创建自己的JSON树

    1
    2
    3
    4
    5
    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"}}}


    安全导航操作员

    安全导航运算符用于避免NullPointerException。通常,当您引用一个对象时,您可能需要在访问该对象的方法或属性之前验证它不是空的。为了避免这种情况,安全导航操作员只返回空值,而不是抛出异常,如:

    1
    2
    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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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()'

    多变量死亡(P)(1)单线多种变量声明(p)字母名称(P)(2)采用不同类型的声明。(p)字母名称


    埃尔维斯算子

    "ELVIS运算符"是三元运算符的缩写。如果表达式解析为false(如在groovy truth中),则返回"合理的默认"值很方便。一个简单的例子如下:

    使用三元运算符时,必须重复要分配的值

    1
    displayCity = user.city ? user.city: 'UnKnown City'

    对于ELVIS运算符,如果不是假值,则使用测试值

    1
    displayCity = user.city ?: 'UnKnown City'

    胁迫操作员(P)强迫经营者是一种不同的惩罚形式。Coercion converts object from one type to another without them being comparational for assignment.让我们做一个例子:(p)(P)Integer X=123STRING S=(STRING)xInteger is not assignable to a string,so it will produce a classscatexception at runtime8 This can be fixed by using胁迫:(p)(P)Integer X=123String S=x as stringInteger is not assignable to a string,but use of as will coerce it to a string(p)