春节假期学习簿之Groovy学习笔记之语法浅析
这个春节过得很充实,因为学习了Groovy。“了”字用得不对,因为还在学习中。趁翔还是热得,稍做些总结。古人有云,学而时习之,不亦乐乎嘛。
Groovy与Java
如果你想要学好Groovy,你首先得是一位有经验的Java程序员。Groovy是Java程序员的出路(之一)。单独来看的话,太小众。
Groovy比Java更动态,更偏向于函数化。它支持不可变对象和闭包,但同时又没有抛弃指令式编程。它不强制你使用何种风格,所以选择权在你手上。因此,个人觉得,它更像Java程序员的出路,而不是一门独立的函数化编程语言。
动态类型与多路分发
与Java不同,Groovy是动态的。说它是动态的,是因为它是运行期多路分发/多宗绑定/多分派的(Multiple Dispatch/Multimethods 下文称多宗绑定)。
所谓多宗绑定既:注1
- 方法的接收者与方法的参数统称为宗量。
- 根据绑定基于多少种宗量,将绑定划分为单宗绑定和多宗绑定两种。
- 多宗绑定既基于多种宗量对目标方法进行选择。
- Java是,编译期多分派,运行期单宗绑定的。
我知道你看不懂,来来来,我们来看几段代码:
def var1 = 'Some String'
等同于Java中的
Object var1 = "Some String"
但是,如果你在Java中
class Test1 {
public void someMethod(String value) {
// process with String value...
System.out.println("someMethod with string param");
}
public void someMethod(Object value) {
// process with Object value ...
System.out.println("someMethod with object param");
}
public static void main(String[] args) {
Object var1 = "Hello World!";
new Test1().someMethod(var1);
String var2 = "Hello World2!";
new Test1().someMethod(var2);
}
}
资深Java程序员会这样告诉你
someMethod with object param
someMethod with string param
但如果你在Groovy中做同样的事情的话
def someMethod(String value) {
// process ...
println("someMethod with string param")
}
def someMethod(Object value) {
// process ...
println("someMethod with object param")
}
def var1 = "Hello World"
someMethod(var1)
String var2 = "hello world2"
someMethod(var2)
你会得到这样的结果
someMethod with string param
someMethod with string param
我知道你在想Groovy是怎么实现的,让我来告诉你。
首先,Groovy有类型猜测系统(Type inference),编译期会根据类型猜测系统的提示进行方法选择。 其次,所有Groovy的对象都有元类信息(metaClass),运行期通过把所有方法的调用分发至MOP(Meta Object Protocol,元对象协议)上来进行动态地方法选择。简单来说,有点像运行期做了个AOP,然后反射去拿各种对象信息,再去调用正确的方法。(不负责非正确比喻,请无视。当然Groovy肯定不是用AOP和反射来实现的,并且它的做法比反射理论上来说更高效)
为什么Groovy能做到这些?因为它和Java是异源同宗的,虽然两者最终编译结果都是能够在JVM上运行的class字节码,但是groovy并非先编译成Java再编译成class字节码的,所以它可以绕过Java的编译器,做一些Java做不到但是JVM能做到的事情。
譬如说像下面这种会让Java程序员抓狂的代码
def something = new Object()
something.metaClass.helloWorld = { println "Hello World from an Object!" }
something.helloWorld()
输出
Hello World from an Object!
你看!一个方法凭空就加到Object对象上惹!
函数化编程
作为一门现代化编程语言,不支持函数化编程是不行的。Groovy支持函数化编程的两大特性:
- 方法(闭包)可以作为头等公民来传递。
- 支持不可变对象(immutable)和等值(equality)而非等同(identity)比较
可传递的方法(闭包)
我们还是来看代码吧
def someClosure = { println "This is a closure and the parameter is $it" } // 闭包,只有一个参数默认it,字符串中$it将会被替换成它的值。
def callClosureMethod(Closure callback, int it) { // 这个方法调用闭包
callback(it) // 还可以使用callback.call(it)来调用
}
callClosureMethod(someClosure, 47)
这还不是最赞的地方,还有更赞的
// 停留3秒钟后打印bye!
Thread.start { sleep 3000; println "bye!" } // 还记得Java中的Thread吗?这个就是它,后面的花括号是闭包。
你看这个闭包没有继承Runnable也没有实现run方法!如果将这样的闭包用在像addListener这样的方法上将会有多爽快!是的,你的确可以这样用!
当然,除了闭包,方法也可以传递,但是由于Java的限制,你得用&
创建一个方法闭包:
def tellMe(int v) {
println "The value of v is $v"
}
def runClosure(def c) {
c(47)
}
def methodClosure = this.&tellMe
runClosure(methodClosure)
说到闭包就不得不谈到闭包的作用域scope,在所有函数式编程语言总,这是一个很高级的话题,一旦谈到这个问题,不外乎难倒面试/教做人/欺负新人/装逼的。为去歧义,明确说一下,这里的“闭包的作用域”指的不是闭包本身的作用域,而是指闭包代码块内各个变量的作用域。
在Groovy中,闭包的作用域是可以通过更改delegate来改变的。闭包代码块内,所有变量的引用,都是通过变量前加前缀delegate.
来解析的,而delegate本身所指向的对象是可以改变的,默认指向的是闭包的所有者(owner),即定义闭包的对象。
并且,“变量前加前缀delegate.
”这个操作,即闭包内变量的解析,也是可以通过为闭包设置resolveStrategy
属性来改变的。可用的resolveStrategy
有OWNER_ONLY
, OWNER_FIRST
(默认), DELEGATE_ONLY
, DELEGATE_FIRST
, SELF_ONLY
。这样Groovy闭包内作用域就变得非常灵活可变了。
不可变对象和等值比较
Groovy语法本身没有对这个特性提供特别的支持,它是通过提供注解和覆盖Java的比较方法来实现的。
考虑下面这段Java代码
// java bean
class Player {
private String name;
public Player() { } // 空构造器
public Player(String name) { this.name = name; }
public String getName() { return this.name; } // 只读属性
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof Player)) {
return false;
}
Player other = (Player) obj;
if (name == null) {
if (other.name != null) {
return false;
}
}
else if (!name.equals(other.name)) {
return false;
}
return true;
}
@Override
public int hashCode() {
// 老程序员会告诉你,覆盖了equals也必须覆盖hashCode……
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
}
public class PlayGame {
public static void main(String[] args) {
Player player1 = new Player("player1");
Player player2 = new Player("player2");
Player anotherPlayer1 = new Player("player1");
System.out.println(player1 == player2);
System.out.println(player1 == anotherPlayer1);
System.out.println(player1.equals(player2));
System.out.println(player1.equals(anotherPlayer1));
}
}
结果如下
false
false
false
true
Java中双等比较的是两个变量是否是同一个引用,而想要做值比较的话,就得用equals,如果是自己写得bean的话,就得像上面这样写许多代码。
再考虑下面这段java代码
int[] a = {1, 2, 3};
int[] b = {1, 2, 3};
List<Integer> listA = new ArrayList(Arrays.asList(a));
List<Integer> listB = new ArrayList(Arrays.asList(b));
System.out.println(a == b);
System.out.println(listA == listB);
System.out.println(a.equals(b));
System.out.println(listA.equals(listB));
结果如下
false
false
false
true
像上面这样的代码,一般是用来为难面试的和实习生同学的,因为这样的不一致导致很多刚入门的同学头昏眼花的。 Groovy中的比较就很直接了
def a = [1, 2, 3]
def b = [1, 2, 3]
println a == b
println a.equals(b)
println a.is(b) // 等同于java的==
结果如下
true
true
false
回到前面一个例子中来,在Groovy中,想要自己写一个bean并且做等值比较,那是相当的爽快
@groovy.transform.Immutable
class Player {
String name
}
def player1 = new Player(name: "player1")
def player2 = new Player(name: "player2")
def player3 = new Player(name: "player1")
println player1 == player2
println player1 == player3
println player1.equals(player2)
println player1.equals(player3)
结果如下
false
true
false
true
重点就在于@groovy.transform.Immutable
这个注解上,你可以尝试一下删除这个注解,然后看看运行的结果会有什么变化。
好奇的同学肯定会问这是怎么实现的?答案就是AST Transformation(语义树变换)。眼熟的同学肯定会说,我看到了lombok。
是的,神奇之处就在于此,其实Java也有同样的能力(lombok)。Groovy自带了很多AST变换的功能,不仅如此,还提供了可以一些附加工具,让程序员可以很方便地访问AST节点来做编译期检查甚至改变语法树的强大功能。
这些都是很高级的功能了,就不在此展开了。(其实是我还没掌握)
总结
这里挑选了Groovy的几个特性做了简介,但这只是强大Groovy的一部分功能,而且都是很抽象的理论上的特性,下次再记录一些比较实用的功能会比较好一点。
注1: 参考《深入理解Java虚拟机-JVM高级特性与最佳实践》,周志明著,2011年9月版,8.3.2.分派,209页。
- 完 -
![W微信打赏 Thanks for donation 感谢打赏](/img/Wechatpay.jpeg)