`

Groovy学习2

阅读更多

 

JAVA中要像闭包式的处理,匿名内部类有类似的功能,但代码实现很难看,而且如果要访问本地变量,本地变量还必须是final的。比较:
java:
inteface ResourceUser{
  void use(Resource resource);
}
resourceHandler.handler(new ResourceUser(){
   public void use(Resource resource){
      resource.doSomething();
   }
});
而groovy(闭包实现)则只要:
resourceHandler.handle{ resource -> resource.doSomething()}

闭包的使用,可以使用魔术变量it代替声明:
log = ''
(1..10).each{ counter -> log += counter}
assert log == '12345678910'
或者:
log = ''
(1..10).each{ log += it}
assert log == '12345678910'

上面这种是最简单的闭包声明方式,只有一个参数,第二种声明的方式是直接将它赋给一个变量:
def printer = {line -> println line}
通过方法的返回值也是一种声明闭包的方式:
def Closure getPrinter(){
  return { line -> println line}
}
第三种声明闭包的方式是重用已有的声明:一个方法
class MethodClosureSample{
   int limit
   MethodClosureSample(int limit){
      this.limit = limit
   }
   boolean validate(String value){
      return value.length() <= limit
   }
}
MethodClosureSample first = new MethodClosureSample(6)
MethodClosureSample second = new MethodClosureSample(5)
Closure firstClosure = first.&validate
def words = ['long string','medium','short','tiny']
assert 'medium' == words.find(firstClosure)
assert 'short' == words.find(second.&validate)

下面这段代码显示了创建了使用闭包的所有方式:使用简单声明,赋值给变量和方法闭包。
map = ['a':1, 'b':2]
map.each{ key, value -> map[key] = value * 2}
assert map == ['a':2, 'b':4]

doubler = {key, value -> map[key] = value * 2}
map.each(doubler)
assert map == ['a':4, 'b':8]

def doubleMethod(entry){
  map[entry.key] = entry.value * 2
}
doubler = this.&doubleMethod
map.each(doubler)
assert map == ['a':8, 'b':16]

闭包调用:
def adder = { x,y -> return x+y }
assert adder(4,3) == 7
assert adder.call(2,6) == 8

更多的闭包方法:
1.参数的数量
def caller(Closure closure){
  closure.getParameterTypes().size()
}
assert caller { one -> } == 1
assert caller { one, two -> } == 2
2.使用curry,curry的基本思想是一个多个参数的函数,传递较少的参数给函数来固定一些值,一个典型的例子是选择一些 数n并且传递给函数与后面传递的单个参数进行相加。
def adder = {x, y -> return x+y}
def addOne = adder.curry(1)
assert addOne(5) == 6
curry最强大的地方是当闭包的参数是闭包本身的时候,这通常在函数式编程时使用到。假设你需要实现一个日志记录器,它应该支持行数的过滤,日志的格式化,并且输出它们到一个设备上,每一个记录器都应该是可配置的,办法是提供一个闭包作为每一个记录器自定义版本,这仍旧允许你实现应用一个过滤器,格式化和最终的日志输出,下面的代码显示了curry怎样被用来注入到自定义的记录器中:
def configurator = { format, filter, line ->
   filter(line)? format(line) : null
}
def appender = { config, append, line ->
   def out = config(line)
   if (out) append(out)
}

def dateFormatter = {line -> "${new Date()}:$line"}
def debugFilter = {line -> line.contains('debug')}
def consoleAppender = {line -> println line}

def myConf = configurator.curry(dateFormatter, debugFilter)
def myLog = appender.curry(myConf, consoleAppender)

myLog('here is some debug message')
myLog('this will not be printed')


闭包的作用范围:
def x = 0
10.times{
  x++
}
assert x == 10
花括号显示了闭包声明的时间,并非执行时间。如何对x进行递增操作的呢?这个闭包是在Integer 10上的times方法上调用的,但其并不知道x这个变量,因此它不能传递x给闭包,不知道x的存在,对于x的操作是闭包自己在它的生命周期里自己维护的上下文中记住的。
对于上面这段代码,script创建了一个闭包,这个闭包有一个反向引用x,这个x是在闭包的声明范围之内的,script在Integer 10的对象上调用times方法,传递声明的闭包作为参数,换句话说,当times方法被执行的时候,一个引用指向堆栈中的闭包对象,times方法使用这个引用执行闭包的call方法,传递了本地变量10给闭包,这里,计数变量没有在闭包的call方法中被使用,在闭包的call凡伽梵中仅仅使用了在script中的x变量进行工作。

class Mother{
   int field = 1
   int foo(){
      return 2
   }
   Closure birth(param){//this method creates and return the closure
      def local = 3
      def closure = { caller ->
          [this, field, foo(), local, param, caller, this.owner]
      }
      return closure
   }
}
Motner julia = new Mother()
closure = julia.birth(4)
context = closure.call(this)
println context[0].call.name  // Script
assert context[1..4] == [1,2,3,4]
aseert context[5] instanceof Script
aseert context[6] instanceof Mother
firstClosure = julia.birth(4)
secondClosure = julia.birth(4)
assert false == firstClosure.is(secondClosure)

def foo(n){
  return {n += it}
}
def accumulator = foo(1)
assert accumulator(2) == 3
assert accumulator(1) == 4

在闭包内使用return关键字与闭包外使用是有区别的。在闭包外,任何出现return的地方都会导致离开当前方法,当在闭包体内出现return语句时,这仅仅结束闭包的当前计算,这是更加有限的,如,当使用list.each时,从闭包的提前返回不会引起从each方法的提前返回--闭包仍旧会在list的下一个元素出现时被调用。
[1,2,3].collect{ it * 2}
[1,2,3].collect{return it * 2}
这两句是相同的效果。

闭包是一个逻辑块,可以直接把闭包作为参数到处传递,从方法调用返回闭包,或者存储起来,在后面再使用。

def x = 1
if(x == 2){
  assert false
}
if(x = 2){  // 编译错误
  println x
}
if((x = 3)){//编译正常,先赋值3给x,再判断x,因为x!=0,所以true
  println x
}
assert x == 3
def store = []
while (x = x-1){
  store << x
}
assert store == [2,1]
while(x =1){
  println x
  break
}

switch(10){
  case 0 " assert false; break
  case 0..9 " assert false; break
  case [8,9,11] " assert false; break
  case Float " assert false; break
  case {it%3 == 0} " assert false; break
  case ~/../ " assert false; break
  default " assert false; break
}

def list = [1,2,3]
while(list){
  list.remove(0)
}
assert list == []
while(list.size < 3){
  list << list.size() + 1
}
assert list == [1,2,3]

定义变量的类型是可选的,不管怎样,标识符在声明的时候不必独一无二,当没有类型和修饰符时,必须使用def来作为替换,实际上用def来表明属性或者变量是没有类型的。(尽管在内部将被声明为Object类型,默认的访问范围是public)

groovy中属性的获取和赋值不仅可以通过.***实现,也可以通过下标实现:
class Counter{
  public count = 0
}
def counter = new Counter()
counter.count = 1
assert counter.count == 1
def fieldName = 'count'
count[fieldName] = 2
assert count['count'] == 2

groovy中方法的返回类型是可以忽略不写的,包括void或者其他正式的返回类型,而且显示的声明参数类型和忽略参数类型是可以混合在一起使用的。
class SomeClass{
   static void main(args){
      assert 'untyped' == method(1)
      assert 'typed' == method('whatever')
      assert 'two args' == method(1,2)
   }
   static method(arg){
      return 'untyped'
   }
   static method(string arg){
      return 'typed'
   }
   static method(arg1, Number arg2){
      return 'two args'
   }
}
一些参数使用:
class Summer{
  def sumWithDefaults(a,b,c=0){
     return a+b+c
  }
  def sumWithList(List args){
     return args.inject(0){sum, i -> sum += i}
  }
  def sumWithOptionals(a,b,Object[] optionals){
     return a+b+sumWithList(optionals.toList()))
  }
  def sumNamed(Map args){
     ['a','b','c'].each{args.get(it, 0)}
     return args.a + args.b + args.c
  }
}

def summer = new Summer()
assert 2 == summer.summer.sumWithDefaults(1,1)
assert 3 == summer.summer.sumWithDefaults(1,1,1)

assert 2 == summer.summer.sumWithList([1,1])
assert 3 == summer.summer.sumWithList([1,1,1])

assert 2 == summer.summer.sumWithOptionals(1,1)
assert 3 == summer.summer.sumWithOptionals(1,1,1)

assert 2 == summer.summer.sumNamed(a:1,b:1)
assert 1 == summer.summer.sumNamed(c:1)

JAVA的方法名里不能包括减号或者点号等,而在groovy中则可以使用,不过一般不直接使用这个特性。

如果一个引用没有指向任何对象,它为null,调用其的方法则会抛出NullPointerException.如果要判断一个对象是否null,要执行他的操作,可以使用if进行判断,或者try/catch进行捕捉,groovy里也提供一种?.操作符来进行安全的引用。
def map = [a:[b:[c:1]]]

assert map.a.b.c == 1

if(map && map.a && map.a.x){
  assert map.a.x.c == null
}

try{
    assert map.a.x.c == null
}catch(NullPointException npe){
}

assert map?.a?.x?.c == null

构造函数:
class VendorWithCtor{
  String name, product
  VendorWithCtor(name, product){
     this.name = name
     this.product = product
  }
}

def first = new VendorWithCtor('Canoo', 'ULC')

def second = ['Canoo', 'ULC'] as VendorWithCtor

VendorWithCtor third = ['Canoo', 'ULC']
或者
VendorWithCtor third
third = ['Canoo', 'ULC']

通过命名参数构造:
class Vendor{
   String name, product
}
new Vendor()
new Vendor(name : 'Canoo')
new Vendor(product: 'ULC')
new Vendor(name : 'Canoo', product: 'ULC')

def vendor = new Vendor(name : 'Canoo')
assert 'Canoo' == vendor.name

groovy类能够继承groovy和java的类和接口,java类也可以继承groovy类和接口。
如果你决定使用接口,groovy提供了使他们更动态的途径,如果你有一个接口MyInterface(该接口只有一个方法)和一个闭包myClosure,你能使用as关键字强制将闭包造型为MyInterface,相似的,如果你有一个接口有几个方法,你能创建一个map(方法名称为key,对应的闭包为value)并且强制造型map为你的接口类型。总之,如果你是从java转过来的,你也许使用强接口类型的风格,当使用groovy的时候,你不需要强制使用任何一种风格,在许多情况下,通过使用动态类型你可以最小化类型的数量,并且如果你真的需要,也可以完全使用接口。

bean的支持上groovy更加简练:
java:

String property1;
public String getProperty1{
   return property1;
}
public void setProperty1(String property1){
  this.property1 = property1;
}

groovy:

String property1 即可

groovy使用上,set只要用object.property1 = 'test'
get也是直接调用object.property1即可。因为groovy会默认给这个属性加上setter,getter方法,当前前提是自己没有加的,如果已经有了相应的setter或者getter方法的,就不会自动添加的。

有时,自定义的getter会对原属性进行些改动,这时,可以通过.@来直接获得属性的原始值:
class DoubleBean{
  public value
  void setValue(value){
    this.value = value;
  }
  def getValue(){
    value * 2
  }
}
def bean = new DoubleBean(value : 100)
assert 200 == bean.value
assert 100 == bean.@value
如果是在类内部使用属性的话,都是解释为直接属性访问,而不是Bean风格的属性访问(通过访问方法进行),在类的外部,就是通过上面的两种方式分别访问直接属性值和Bean风格的属性。

GDK的bean properties使用:
class SomeClass{
  def someProperty
  public someField
  private somePrivateField
}
def obj = new SomeClass()
def store = []
obj.properties.each{ property ->
  store += property.key
  store += property.value
}
assert store.contains('someProperty')
assert store.contains('someField')
assert store.contains('somePrivateField')
assert store.contains('class')
assert store.contains('metaClass')
assert obj.properties.size() == 3

在groovy的代码中,你经常会发现如object.name这样的表达式,当groovy解释这样的引用的时候到底发生了什么:
1.如果object引用到一个map,object.name引用到存储在map中的,Key值为name对应的value值;
2.否则,如果name是object的一个属性,那么这个属性被引用(有限采用访问方法)
3.每一个groovy对应有机会实现自己的getProperty(name)和setProperty(name,value)方法,当这样做之后,这些实现用来控制属性的访问。例如,在map中使用这个机制来暴露key列表为属性列表。
4.属性的访问能够通过提供object.get(name)方法进行拦截,这是groovy运行时被困扰的最后的对策:它仅仅用在没有适当的javaBean属性可用和getProperty方法没有实现的情况。

当属性包含特殊字符,而这些特殊字符是无效标识符的时候,这可以通过字符串来应用,如object.'my-name',你也可以使用Gstring:def name = 'my-name'; object."$name".

groovy可以通过类似赋值的方式来扩展属性:
def boxer = new Expando()
assert null == boxer.takeThis
boxer.takeThis = 'ouch!'
assert 'ouch!' == boxer.takeThis
box.fightBack = {times -> return this.takeThis * times}
assert 'ouch!ouch!ouch!' == boxer.fightBack(3)

关于元编程:
在groovy中,所有的对象都实现了GroovyObject接口,它就像我们提到过的其他类一样,声明为groovy.lang包中,GroovyObject看起来有如下的格式:
public interface GroovyObject{
  public Object invokeMethod(String name, Object args);
  public Object getProperty(String property);
  public void setProperty(String property, Object newValue);
  public MetaClass getMetaClass();
  public void setMetaClass(MetaClass metaClass);
}
在groovy中你所有的类都是通过GroovyClassGenerator来构建的,因此他们都实现了GroovyObject这个接口并且有接口中的方法的默认实现--除了你自己选择实现它之外。如果你希望一个一般的java类也被作为一个groovy类来组织,他必须实现GroovyObject接口,为了便利性,你可以扩展抽象类GroovyObjectSupport,这个类提供了默认的实现。
GroovyObject与MetaClass协作,MetaClass是groovy元概念的核心,它提供了一个groovy类的所有的元数据,如可用的方法,属性列表,MetaClass也实现了下列接口:
Object invokeMethod(Object obj, String methodName, Object args)
Object invokeMethod(Object obj, String methodName, Object[] args)
Object invokeStaticMethod(Object obj, String methodName, Object[] args)
Object invokeConstructor(Object[] args)
这些方法进行真正的方法调用工作,使用JAVA反射API(默认,并且性能更佳)或者通过透明创建一个反射类,GroovyObject的invokeMethod方法默认实现总是转到相应的MetaClass.
MetaClass被存储在一个名称为MetaClassRegistry的中心存储器中,同事groovy也从MetaClassRegistry中获取MetaClass.

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics