Groovy记录
官方文档
- The Apache Groovy programming language
- Groovy Language Documentation
- Domain-Specific Languages
- IBM: 精通Groovy
相关记录
-
keywords
as
、def
、trait
、in
-
String
- Single quoted string:
'a single quoted string'
- Triple single quoted string:
'''a triple single quoted string'''
- Double quoted string:
"a double quoted string"
def name = 'Guillaume' // a plain string def greeting = "Hello ${name}"
- Triple double quoted string
def name = 'Groovy' def template = """ Dear Mr ${name}, You're the winner of the lottery! Yours sincerly, Dave """ assert template.toString().contains('Groovy')
- Single quoted string:
-
Numbers Underscore in literals
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
-
Lists
def letters = ['a', 'b', 'c', 'd'] assert letters[0] == 'a' assert letters[1] == 'b' assert letters[-1] == 'd' //神奇吧!从后往前取,用负数! assert letters[-2] == 'c'
-
Arrays
String[] arrStr = ['Ananas', 'Banana', 'Kiwi'] def numArr = [1, 2, 3] as int[]
-
Maps
def colors = [red: '#FF0000', green: '#00FF00', blue: '#0000FF'] assert colors['red'] == '#FF0000' assert colors.green == '#00FF00' colors['pink'] = '#FF00FF' colors.yellow = '#FFFF00' assert colors.pink == '#FF00FF' assert colors['yellow'] == '#FFFF00' assert colors instanceof java.util.LinkedHashMap
-
Method pointer operator
def str = 'example of method reference' def fun = str.&toUpperCase def upper = fun() assert upper == str.toUpperCase()
它可以作为
Closure
参数:def transform(List elements, Closure action) { def result = [] elements.each { result << action(it) } result } String describe(Person p) { "$p.name is $p.age" } def action = this.&describe def list = [ new Person(name: 'Bob', age: 42), new Person(name: 'Julia', age: 35)] assert transform(list, action) == ['Bob is 42', 'Julia is 35']
-
Spread operator
class Car { String make String model } def cars = [ new Car(make: 'Peugeot', model: '508'), new Car(make: 'Renault', model: 'Clio')] def makes = cars*.make assert makes == ['Peugeot', 'Renault']
-
Range operator
def range = 0..5 assert (0..5).collect() == [0, 1, 2, 3, 4, 5] assert (0..<5).collect() == [0, 1, 2, 3, 4] assert (0..5) instanceof List assert (0..5).size() == 6
-
Spaceship operator
assert (1 <=> 1) == 0 assert (1 <=> 2) == -1 assert (2 <=> 1) == 1 assert ('a' <=> 'z') == -1
-
Membership operator
def list = ['Grace','Rob','Emmy'] assert ('Emmy' in list)
-
Identity operator
In Groovy, using == to test equality is different from using the same operator in Java. In Groovy, it is calling equals. If you want to compare reference equality, you should use is like in the following example:def list1 = ['Groovy 1.8','Groovy 2.0','Groovy 2.3'] def list2 = ['Groovy 1.8','Groovy 2.0','Groovy 2.3'] assert list1 == list2 assert !list1.is(list2)
-
Coercion operator
Integer x = 123 String s = x as String
-
Call operator
class MyCallable { int call(int x) { 2*x } } def mc = new MyCallable() assert mc.call(2) == 4 assert mc(2) == 4
-
public static void main vs script
Groovy supports both scripts and classes. Take the following code for example:
Main.groovy
class Main { static void main(String... args) { println 'Groovy world!' } }
This is typical code that you would find coming from Java, where code has to be embedded into a class to be executable. Groovy makes it easier, the following code is equivalent:
println 'Groovy world!'
-
Script class
A script is always compiled into a class. The Groovy compiler will compile the class for you, with the body of the script copied into arun
method. The previous example is therefore compiled as if it was the following:
Main.groovy
import org.codehaus.groovy.runtime.InvokerHelper class Main extends Script { def run() { println 'Groovy world!' } static void main(String[] args) { InvokerHelper.runScript(Main, args) } }
You can also mix methods and code. The generated script class will carry all methods into the script class, and assemble all script bodies into the run method:
println 'Hello' int power(int n) { 2**n } println "2^6==${power(6)}"
This code is internally converted into:
import org.codehaus.groovy.runtime.InvokerHelper class Main extends Script { int power(int n) { 2** n} def run() { println 'Hello' println "2^6==${power(6)}" } static void main(String[] args) { InvokerHelper.runScript(Main, args) } }
-
Class
Groovy classes are very similar to Java classes, and are compatible with Java ones at JVM level. They may have methods, fields and properties (think JavaBean properties but with less boilerplate). Classes and class members can have the same modifiers (public, protected, private, static, etc) as in Java with some minor differences at the source level which are explained shortly.The key differences between Groovy classes and their Java counterparts are:
-
Classes or methods with no visibility modifier are automatically public (a special annotation can be used to achieve package private visibility).
-
Fields with no visibility modifier are turned into properties automatically, which results in less verbose code, since explicit getter and setter methods aren’t needed. More on this aspect will be covered in the fields and properties section.
-
Classes do not need to have the same base name as their source file definitions but it is highly recommended in most scenarios (see also the next point about scripts).
-
One source file may contain one or more classes (but if a file contains any code not in a class, it is considered a script). Scripts are just classes with some special conventions and will have the same name as their source file (so don’t include a class definition within a script having the same name as the script source file).
-
-
Constructors
class PersonConstructor { String name Integer age PersonConstructor(name, age) { this.name = name this.age = age } } def person1 = new PersonConstructor('Marie', 1) def person2 = ['Marie', 2] as PersonConstructor PersonConstructor person3 = ['Marie', 3]
class PersonWOConstructor { String name Integer age } def person4 = new PersonWOConstructor() def person5 = new PersonWOConstructor(name: 'Marie') def person6 = new PersonWOConstructor(age: 1) def person7 = new PersonWOConstructor(name: 'Marie', age: 2)
-
Method
1.Named arguments
Like constructors, normal methods can also be called with named arguments. They need to receive the parameters as a map. In the method body, the values can be accessed as in normal maps (map.key).def foo(Map args) { "${args.name}: ${args.age}" } foo(name: 'Marie', age: 1)
2.Default arguments
def foo(String par1, Integer par2 = 1) { [name: par1, age: par2] } assert foo('Marie').age == 1
-
Exception declaration
Groovy automatically allows you to treat checked exceptions like unchecked exceptions. This means that you don’t need to declare any checked exceptions that a method may throw as shown in the following example which can throw a FileNotFoundException if the file isn’t found:def badRead() { new File('doesNotExist.txt').text } shouldFail(FileNotFoundException) { badRead() }
-
Fields and properties
A field is a member of a class or a trait which has:
- a mandatory access modifier (
public
,protected
, orprivate
) - one or more optional modifiers (
static
,final
,synchronized
) - an optional type
- a mandatory name
A property is an externally visible feature of a class. Rather than just using a public field to represent such features (which provides a more limited abstraction and would restrict refactoring possibilities), the typical convention in Java is to follow JavaBean conventions, i.e. represent the property using a combination of a private backing field and getters/setters. Groovy follows these same conventions but provides a simpler approach to defining the property. You can define a property with:
- an absent access modifier (no
public
,protected
orprivate
) - one or more optional modifiers (
static
,final
,synchronized
) - an optional type
- a mandatory name
- a mandatory access modifier (
-
Annotation member values
However it is possible to omit value= in the declaration of the value of an annotation if the member value is the only one being set:@interface Page { String value() int statusCode() default 200 } @Page(value='/home') void home() { // ... } @Page('/users') void userList() { // ... } @Page(value='error',statusCode=404) void notFound() { // ... }
-
Closure annotation parameters
An interesting feature of annotations in Groovy is that you can use a closure as an annotation value. Therefore annotations may be used with a wide variety of expressions and still have IDE support. For example, imagine a framework where you want to execute some methods based on environmental constraints like the JDK version or the OS. One could write the following code:class Tasks { Set result = [] void alwaysExecuted() { result << 1 } @OnlyIf({ jdk>=6 }) void supportedOnlyInJDK6() { result << 'JDK 6' } @OnlyIf({ jdk>=7 && windows }) void requiresJDK7AndWindows() { result << 'JDK 7 Windows' } }
For the
@OnlyIf
annotation to accept a Closure as an argument, you only have to declare the value as a Class:@Retention(RetentionPolicy.RUNTIME) @interface OnlyIf { Class value() }
class Runner { static <T> T run(Class<T> taskClass) { def tasks = taskClass.newInstance() def params = [jdk:6, windows: false] tasks.class.declaredMethods.each { m -> if (Modifier.isPublic(m.modifiers) && m.parameterTypes.length == 0) { def onlyIf = m.getAnnotation(OnlyIf) if (onlyIf) { Closure cl = onlyIf.value().newInstance(tasks,tasks) cl.delegate = params if (cl()) { m.invoke(tasks) } } else { m.invoke(tasks) } } } tasks } }
-
Traits
Traits are a structural construct of the language which allows:- composition of behaviors
- runtime implementation of interfaces
- behavior overriding
- compatibility with static type checking/compilation
trait FlyingAbility { String fly() { "I'm flying!" } }
class Bird implements FlyingAbility {} def b = new Bird() assert b.fly() == "I'm flying!"
Closures
Closure太重要了,以至于,我要把它单独拎出来。
先看看官方对它的定义:
A closure in Groovy is an open, anonymous, block of code that can take arguments, return a value and be assigned to a variable. A closure may reference variables declared in its surrounding scope. In opposition to the formal definition of a closure, Closure in the Groovy language can also contain free variables which are defined outside of its surrounding scope. While breaking the formal concept of a closure, it offers a variety of advantages which are described in this chapter.
-
Defining a closure
{ [closureParameters -> ] statements }
Where
[closureParameters->]
is an optional comma-delimited list of parameters, and statements are 0 or more Groovy statements. The parameters look similar to a method parameter list, and these parameters may be typed or untyped.When a parameter list is specified, the
->
character is required and serves to separate the arguments from the closure body. The statements portion consists of 0, 1, or many Groovy statements.{ item++ } { -> item++ } { println it } { it -> println it } { name -> println name } { String x, int y -> println "hey ${x} the value is ${y}" } { reader -> def line = reader.readLine() line.trim() }
-
Closures as an object
A closure is an instance of the groovy.lang.Closure class, making it assignable to a variable or a field as any other variable, despite being a block of code:def listener = { e -> println "Clicked on $e.source" } assert listener instanceof Closure Closure callback = { println 'Done!' } Closure<Boolean> isTextFile = { File it -> it.name.endsWith('.txt') }
-
Calling a closure
def code = { 123 } assert code() == 123 assert code.call() == 123
Unlike a method, a closure always returns a value when called.
-
Normal parameters
Parameters of closures follow the same principle as parameters of regular methods:- an optional type
- a name
- an optional default value
Parameters are separated with commas:
def closureWithOneArg = { str -> str.toUpperCase() } assert closureWithOneArg('groovy') == 'GROOVY' def closureWithOneArgAndExplicitType = { String str -> str.toUpperCase() } assert closureWithOneArgAndExplicitType('groovy') == 'GROOVY' def closureWithTwoArgs = { a,b -> a+b } assert closureWithTwoArgs(1,2) == 3 def closureWithTwoArgsAndExplicitTypes = { int a, int b -> a+b } assert closureWithTwoArgsAndExplicitTypes(1,2) == 3 def closureWithTwoArgsAndOptionalTypes = { a, int b -> a+b } assert closureWithTwoArgsAndOptionalTypes(1,2) == 3 def closureWithTwoArgAndDefaultValue = { int a, int b=2 -> a+b } assert closureWithTwoArgAndDefaultValue(1) == 3
-
Implicit parameter
When a closure does not explicitly define a parameter list (using->
), a closure always defines an implicit parameter, namedit
.If you want to declare a closure which accepts no argument and must be restricted to calls without arguments, then you must declare it with an explicit empty argument list:
def magicNumber = { -> 42 } // this call will fail because the closure doesn't accept any argument magicNumber(11)
-
Delegation strategy
A closure actually defines 3 distinct things:-
this
corresponds to the enclosing class where the closure is defined -
owner
corresponds to the enclosing object where the closure is defined, which may be either a class or a closure -
delegate
corresponds to a third party object where methods calls or properties are resolved whenever the receiver of the message is not defined
-
-
this
Closure里的this就是Closure没有this!它只是指向定义了这个Closure的外部的第一层的Class对象 -
owner
就是拥有(定义)这个Closure的对象,可能是Class也可能是Closure -
delegage
class Person { String name } def p = new Person(name:'Igor') def cl = { name.toUpperCase() } cl.delegate = p assert cl() == 'IGOR'
但是,
class Person { String name def pretty = { "My name is $name" } String toString() { pretty() } } class Thing { String name } def p = new Person(name: 'Sarah') def t = new Thing(name: 'Teapot') // Using the default strategy, the name property is resolved on the owner first assert p.toString() == 'My name is Sarah' // so if we change the delegate to t which is an instance of Thing p.pretty.delegate = t // there is no change in the result: name is first resolved on the owner of the closure assert p.toString() == 'My name is Sarah'
最后一行,为什么不变?
因为,
Closure.OWNER_FIRST is the default strategy
首先从owner
中去找,找到了name
属性,ok,那就不管后面又给delegate赋了什么鬼值了再来一个栗子:
class Person { String name int age def fetchAge = { age } } class Thing { String name } def p = new Person(name:'Jessica', age:42) def t = new Thing(name:'Printer') def cl = p.fetchAge cl.delegate = p assert cl() == 42 cl.delegate = t assert cl() == 42 cl.resolveStrategy = Closure.DELEGATE_ONLY cl.delegate = p assert cl() == 42 cl.delegate = t try { cl() assert false } catch (MissingPropertyException ex) { // "age" is not defined on the delegate }
-
在GString中使用Closure
class Person { String name String toString() { name } } def sam = new Person(name:'Sam') def lucy = new Person(name:'Lucy') def p = sam // Create a GString with lazy evaluation of "p" def gs = "Name: ${-> p}" assert gs == 'Name: Sam' p = lucy assert gs == 'Name: Lucy'