Groovy 的隐藏特性?

看起来 Groovy 在这个线程中被遗忘了,所以我将对 Groovy 问同样的问题。

  • 尝试将答案限制在 Groovy 核心
  • 每个答案只有一个特征
  • 给出一个特性的例子和简短描述,而不仅仅是文档的链接
  • 使用粗体标题作为第一行标记特性

参见:

  1. Python 的隐藏特性
  2. Ruby 的隐藏特征
  3. Perl 的隐藏特性
  4. Java 的隐藏特性
12208 次浏览

使用散列作为伪对象。

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

与 Duck 类型相结合,这种方法可以大有帮助。甚至不需要拿出“作为”操作员。

使用扩展点运算符

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

这是 animals.collect { it.size() }的捷径。

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

这个代码:

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”——为了清楚起见,我将它们保留了下来。

有人知道 猫王吗?

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)

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

与 Java 不同,在 Groovy 中,任何东西都可以在 开关语句中使用,而不仅仅是基本类型。 在典型的 表演方法中

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

方法允许将其转换为:

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

变成这样

 myObj1.with {
value = 10
otherObj.title = name
mode = MODE_NORMAL
}
println
"""
Groovy has "multi-line" strings.
Hooray!
"""

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

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

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

查找对象上的方法很容易,只需要询问 metClass:

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

基于闭包的接口实现

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

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

要拦截丢失的静态方法,请使用以下方法

 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"

您可以使用 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']

使用宇宙飞船操作员

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

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 ]

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

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”

解体

在 Groovy 中它可以被称为别的东西; 在 clojure 中它被称为构造。你绝对想不到它有多方便。

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

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

产出:

福吧 10

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

这对于将代码转换为数据非常有帮助:

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

GDK 的 groovy.transform包中的转换所提供的特性,例如:

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

其他:

@ 授权

class Foo {
def footest() { return "footest"}
}


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


def bar = new Bar()


assert "footest" == bar.footest()

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

集合加/减运算符

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

从列表中删除 null

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

动态方法调用

可以使用具有名称的字符串调用方法

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

如何在 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"}}}

以字面值下划线

当写长的文字数字时,眼睛很难弄清楚一些数字是如何组合在一起的,例如成千上万的组合,成千上万的单词,等等。通过允许在数字字面值中放置下划线,可以更容易地发现这些组:

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

安全导航操作员

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

纪念

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

有一个无限的版本,将缓存任何对(输入参数,返回值) ,它将永远看到; 和一个有限的版本,将缓存最后 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)

Wikipedia 页面 有大量关于记忆在计算机科学中的应用的信息。我只会指出一个简单的实际用途。

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

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

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

import groovy.transform.Memoized


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

猫王接线员

“ Elvis 算符” 是三元算符的缩写。如果表达式解析为 false (如 Groovy truth) ,那么返回一个“合理的默认值”就是这种方便的实例之一。一个简单的例子如下:

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

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

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

displayCity = user.city ?: 'UnKnown City'

强制操作员

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

整数 x = 123
字符串 s = (字符串) x
整数不可赋值给 String,因此 它将在运行时产生 ClassCastException 可以通过强制手段解决这一问题:

整数 x = 123 字符串 s = < strong > x as String
整数不可赋值给 String,但是使用 as 将强制它赋值给 String

多变量减速

1)单行多变量声明

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

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

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