【问题标题】:Hidden features of Groovy?Groovy 的隐藏功能?
【发布时间】:2010-09-23 03:25:19
【问题描述】:

似乎 Groovy 在这个帖子中被遗忘了,所以我只会问 Groovy 相同的问题。

  • 尝试限制对 Groovy 核心的回答
  • 每个答案一个功能
  • 提供功能示例和简短说明,而不仅仅是文档链接
  • 使用粗体标题作为第一行标记功能

另见:

  1. Hidden features of Python
  2. Hidden features of Ruby
  3. Hidden features of Perl
  4. Hidden features of Java

【问题讨论】:

    标签: groovy


    【解决方案1】:

    多变量减速

    1)单行多变量声明

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

    2)使用不同的类型声明。

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

    【讨论】:

      【解决方案2】:

      猫王操作员

      “猫王算子”是三元算子的缩写。这很方便的一个例子是,如果表达式解析为 false(如 Groovy 的真相),则返回一个“合理的默认”值。一个简单的示例可能如下所示:

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

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

      使用 Elvis 运算符,如果测试的值不为假,则使用该值

      displayCity = user.city ?: 'UnKnown City'
      

      【讨论】:

        【解决方案3】:

        强制运算符

        强制运算符 (as) 是强制转换的一种变体。强制将对象从一种类型转换为另一种类型,但它们不兼容赋值。举个例子:

        整数 x = 123
        字符串 s = (字符串) x
        Integer 不能分配给 String,因此 它会在运行时产生 ClassCastException 这可以通过使用强制来解决:

        整数 x = 123 字符串 s = x 作为字符串
        整数不能分配给字符串,但使用 as 会将其强制为字符串

        【讨论】:

          【解决方案4】:

          有人知道猫王吗?

          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)
          

          【讨论】:

          • 这是否与 C# 中的空合并运算符 (??) 相同?
          • 看起来是这样,是的,尽管我必须查找 C# 操作。
          • 不完全是,它是一个缩短的三元运算符。我在上面写了一篇文章:colinharrington.net/blog/2008/10/groovy-elvis-operator 你也可以在那里做完整的表达:-)
          • 答案中发布的代码无法编译,因为关键字“default”被用作变量。使用“d”来编译代码。
          • 根本没有重要的原因,只是遵守了 OP 建议的约定。当时我并没有考虑到我的行为会带来的提神效果。
          【解决方案5】:

          记忆

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

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

          方法的记忆:

          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)
          

          The Wikipedia page 提供有关在计算机科学中使用记忆的大量信息。我只指出一个简单的实际用途。

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

          有时您的常量值无法在类定义或创建时进行初始化。例如,常量表达式可能会使用来自不同类的另一个常量或方法,在你的类初始化之后,它们将被其他东西(Spring 等)插入。

          在这种情况下,您可以将常量转换为 getter 并用@Memoized 装饰它。它只会计算一次,第一次被访问,然后缓存和重用的值:

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

          【讨论】:

            【解决方案6】:

            安全导航操作员

            安全导航运算符用于避免 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
            

            【讨论】:

              【解决方案7】:

              文字中的下划线

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

              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
              

              【讨论】:

                【解决方案8】:

                @Delegate

                class Foo {
                    def footest() { return "footest"}   
                }
                
                class Bar {
                    @Delegate Foo foo = new Foo()     
                }
                
                def bar = new Bar()
                
                assert "footest" == bar.footest()
                

                【讨论】:

                  【解决方案9】:

                  对于使用 groovy 测试 java 代码,对象图生成器非常棒:

                  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 的任何属性 Lists 提供一个空列表的默认值,而不是 null 以使构建器支持工作。)

                  【讨论】:

                    【解决方案10】:

                    使用点号运算符

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

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

                    【讨论】:

                    • 第三行是什么意思?
                    • 从上下文中,这意味着在每个数组元素上调用 size 方法并返回结果数组。实际上很酷:-)
                    【解决方案11】:

                    with 方法可以转这个:

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

                    进入这个

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

                    【讨论】:

                    • 这让我想起了关于 object pascal 的旧回忆 :-)
                    • 鉴于这是 Groovy,在 myObj1.value = 10 (等)和后者之间不是更典型的比较,因为您不需要调用 setter 方法吗?
                    【解决方案12】:

                    要拦截缺少的静态方法,请使用以下

                     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 新手,解析这个有点困难。
                    • Object Foo 没有定义名为 B 的静态方法。然而,您可以通过添加一个名为“$static_methodMissing(String, Object)”的方法并在那里实现您想要的任何东西来即时实现一个。每当调用静态方法并且对象没有定义该静态方法时,都会调用此魔术方法。
                    【解决方案13】:

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

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

                    【讨论】:

                      【解决方案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()'
                      

                      【讨论】:

                        【解决方案15】:

                        从列表中删除 null 值

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

                        【讨论】:

                          【解决方案16】:

                          我知道我来晚了,但我认为这里缺少一些不错的功能:

                          集合加/减运算符

                          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
                          

                          【讨论】:

                            【解决方案17】:

                            GDK 的groovy.transform 包内的转换提供的功能,例如:

                            • @Immutable:@Immutable 注释指示编译器执行 AST 转换,该转换添加必要的 getter、构造函数、equals、hashCode 和其他通常在创建具有已定义属性的不可变类时编写的辅助方法。
                            • @CompileStatic:这将使 Groovy 编译器使用 Java 风格的编译时检查,然后执行静态编译,从而绕过 Groovy 元对象协议。
                            • @Canonical:@Canonical 注释指示编译器执行 AST 转换,将位置构造函数、equals、hashCode 和漂亮的打印 toString 添加到您的类中。

                            其他:

                            • @Slf4j 此本地转换使用 LogBack 日志记录为您的程序添加了日志记录功能。对名为 log 的未绑定变量的每个方法调用都将映射到对记录器的调用。
                            • Groovy's XML Slurper:轻松解析 XML。杀手级功能!

                            【讨论】:

                              【解决方案18】:

                              在方法参数中使用扩展运算符

                              这对将代码转换为数据有很大帮助:

                              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('_'))
                              

                              【讨论】:

                              • 不,我的意思是将代码转换为数据,创建一个数据数组,相当于参数列表,通常是静态的。但我明白你的意思,这取决于你的观点。我是从将现有静态代码重构为更动态的 vue 代码的角度来看待它的。
                              • 也许是“使用数据驱动设计时”?
                              【解决方案19】:

                              基于闭包的接口实现

                              如果您有一个类型化的引用,例如:

                              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
                              

                              【讨论】:

                                【解决方案20】:

                                Groovy 可以像 Javascript 一样工作。您可以通过闭包拥有私有变量和函数。 您还可以使用闭包对函数进行 curry。

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

                                输出:

                                富吧 10

                                【讨论】:

                                  【解决方案21】:

                                  解构

                                  在 Groovy 中它可能被称为别的东西;它在clojure中被称为解构。你永远不会相信它可以派上用场。

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

                                  【讨论】:

                                  【解决方案22】:
                                  println 
                                  """
                                  Groovy has "multi-line" strings.
                                  Hooray!
                                  """
                                  

                                  【讨论】:

                                  • 啊,多行字符串的美。每种语言都应该采用这些。
                                  • 不确定为什么多行字符串需要“”作为分隔符,而“可以扩展为允许多行和单行字符串。
                                  • @VorgvanGeir 使用 """ 意味着你不必逃避 "。
                                  • @Brian True,但 """a\bc"de"f\g""" 无法编译,因为您必须转义 \ 或 \g,而 \b 的行为就像一个退格键,除非你逃脱它。当您仍然需要转义字符串中的所有其他特殊序列时,不需要转义 " 有什么意义?
                                  • 因为我们中的一些人希望能够写“foo\tbar”。但是 Groovy 也有: println (/a\bc"de"f\g/) // -> a\bc"de"f\g
                                  【解决方案23】:

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

                                  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”

                                  【讨论】:

                                    【解决方案24】:

                                    使用宇宙飞船操作符

                                    我喜欢Spaceship operator,它适用于各种自定义排序方案。一些使用示例是here。它特别有用的一种情况是使用多个字段在对象的运行中创建比较器。例如

                                    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 ]
                                    

                                    【讨论】:

                                      【解决方案25】:

                                      您可以使用 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 []吗?
                                      【解决方案26】:

                                      找出对象上有哪些方法就像询问元类一样简单:

                                      "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"]
                                      

                                      【讨论】:

                                      • 这看起来很傻。但它非常有用。在 python 中,你有 dir 内置函数: dir("foo") 给出了字符串的所有方法。
                                      【解决方案27】:

                                      与 Java 不同,在 Groovy 中,任何东西都可以在 switch 语句中使用,而不仅仅是原始类型。 在一个典型的 eventPerformed 方法中

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

                                      【讨论】:

                                        【解决方案28】:

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

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

                                        【讨论】:

                                          【解决方案29】:

                                          闭包可以让所有旧的资源管理的try-finally游戏消失。文件流在块结束时自动关闭:

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

                                          【讨论】:

                                          • 而且如果在闭包中引发异常,文件句柄也会正确关闭,我比 try-with-resources 更喜欢这个。
                                          【解决方案30】:

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

                                          这段代码:

                                          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 msg" 和 "int val" 中去掉 "String" 和 "int" -- 我只是为了清楚起见而把它们留在里面。

                                          【讨论】:

                                          • 我希望是这样,但目前 Groovy (1.6) 仅支持对象构造函数的命名参数。您可以将此语法用于方法调用,但它将任何命名参数打包到 Map 中,然后查找 foo(Map)。
                                          • 我很困惑你认为我所说的暗示不同。
                                          猜你喜欢
                                          • 2010-09-17
                                          • 2010-09-13
                                          • 2020-06-01
                                          • 2011-07-21
                                          • 1970-01-01
                                          • 2012-09-23
                                          • 2010-09-08
                                          • 2011-02-03
                                          • 2010-09-08
                                          相关资源
                                          最近更新 更多