
编写可靠的 Apache Groovy 代码:了解你的真值
在 Groovy 中,“真或假”的整个问题值得讨论。理解它能帮助你避免意外行为,编写清晰简洁的代码,有效地调试并有效地利用 Groovy 的特性。(如果你还没有安装 Groovy,请阅读这篇介绍,这是系列文章的其中一篇。)
Java 程序员应该意识到,对于像 int
或 double
这样的原始类型,相等性的工作方式与类实例有所不同。
考虑以下 Java 程序
1 import java.lang.*;
2 public class Groovy06 {
3 static public void main(String[] args) {
4 String s1 = "Hi there";
5 String s2 = "Hi " + "there";
6 System.out.println("s1.equals(s2) " + s1.equals(s2));
7 System.out.println("s1 == s2 " + (s1 == s2));
8 int i1 = 2;
9 int i2 = 1 + 1;
10 System.out.println("i1.equals(i2) " + i1.equals(i2));
11 System.out.println("i1 == i2 " + (i1 == i2));
12 }
13 }
当你尝试编译这段代码时,你会得到这个错误
$ javac Groovy06.java
Groovy06.java:15: error: int cannot be dereferenced
System.out.println("i1.equals(i2) " + i1.equals(i2));
^
1 error
$
这个错误告诉你,由于 i1
是一个原始类型(在本例中是 int
),你无法获得对它的引用来访问它的 equals()
方法——这就是原始类型的本质。如果你将 i1
和 i2
的类型更改为 Integer
1 import java.lang.*;
2 public class Groovy06 {
3 static public void main(String[] args) {
4 String s1 = "Hi there";
5 String s2 = "Hi " + "there";
6 System.out.println("s1.equals(s2) " + s1.equals(s2));
7 System.out.println("s1 == s2 " + (s1 == s2));
8 Integer i1 = 2;
9 Integer i2 = 1 + 1;
10 System.out.println("i1.equals(i2) " + i1.equals(i2));
11 System.out.println("i1 == i2 " + (i1 == i2));
12 }
13 }
现在你可以正常编译这个程序了。运行它会得到
$ java Groovy06
s1.equals(s2) true
s1 == s2 true
i1.equals(i2) true
i1 == i2 true
$
那么 .equals()
和 ==
之间有什么区别呢?再做一个更改来看看结果
1 import java.lang.*;
2 public class Groovy06 {
3 static public void main(String[] args) {
4 String s1 = new String("Hi there");
5 String s2 = new String("Hi " + "there");
6 System.out.println("s1.equals(s2) " + s1.equals(s2));
7 System.out.println("s1 == s2 " + (s1 == s2));
8 Integer i1 = new Integer(2);
9 Integer i2 = new Integer(1 + 1);
10 System.out.println("i1.equals(i2) " + i1.equals(i2));
11 System.out.println("i1 == i2 " + (i1 == i2));
12 }
13 }
现在运行它
$ java Groovy06
s1.equals(s2) true
s1 == s2 false
i1.equals(i2) true
i1 == i2 false
$
在这里你可以看到,创建新实例意味着每个实例的值是相等的,但实例本身并不相等。更准确地说,s1
和 s2
指的是不同的对象,i1
和 i2
也是如此。碰巧的是,s1
和 s2
包含相同的值,i1
和 i2
也是如此。所以 .equals()
比较的是值,而 ==
比较的是引用,在本例中,引用指向内存的不同部分。
我听说在 Java 程序的野外,存在一些细微的 bug,是由于程序员使用了 ==
而不是 .equals()
造成的。我相信这一点。
Groovy 的设计者采取了不同的方法。在 Groovy 的世界里,==
符号完全等同于 .equals()
方法。在需要比较引用的情况下,Groovy 有符号 ===
(以及用于否定引用相等的符号 !==
)。
你可以编写一个小脚本来测试这一点,基于之前的 Java 示例
1 String s1 = new String("Hi there")
2 String s2 = new String("Hi " + "there")
3 println "s1.equals(s2) ${s1.equals(s2)}"
4 println "s1 == s2 ${s1 == s2}"
5 println "s1 === s2 ${s1 === s2}"
6 int i1 = new Integer(2)
7 int i2 = new Integer(1 + 1)
8 println "i1.equals(i2) ${i1.equals(i2)}"
9 println "i1 == i2 ${i1 == i2}"
10 println "i1 === i2 ${i1 === i2}"
现在运行它
$ groovy Groovy06.groovy
s1.equals(s2) true
s1 == s2 true
s1 === s2 false
i1.equals(i2) true
i1 == i2 true
i1 === i2 true
$
我非常喜欢 Groovy 使用 ===
运算符来测试引用的相等性,以及 ==
被评估为 .equals()
的方式。这对我来说感觉更安全。值得在此强调的是,Groovy 中没有任何原始类型。因此,如果 ==
和 .equals()
不同,那就意味着你必须记住在看起来像原始类型的东西上使用 .equals()
。
还需要强调的是,使用 Groovy 编译和运行 Java 程序会得到与 Java 不同的答案。对于将 Java 代码迁移到 Groovy 的人(或反之亦然!),记住这一点非常重要。
到目前为止,除了 Java 和 Groovy 中 ==
的区别之外,true
和 false
看起来非常相似。但是 Groovy 引入了一个被称为 “Groovy 真值” 的概念,这个概念在 Java 中不存在,但与 C 语言有些相似之处。
在 Groovy 中,null
、空白、zero
或空值会被评估为 false
1 def n = null
2 def b = ""
3 def z = 0
4 def el = []
5 def em = [:]
6 if (n == null)
7 println 'n == null'
8 if (!n)
9 println '!n'
10 if (b == "")
11 println 'b == ""'
12 if (!b)
13 println '!b'
14 if (z == 0)
15 println 'z == 0'
16 if (!z)
17 println '!z'
18 if (el == [])
19 println 'el == []'
20 if (!el)
21 println '!el'
22 if (em == [:])
23 println 'em == [:]'
24 if (!em)
25 println '!em'
运行它
$ groovy Groovy06b.groovy
n == null
!n
b == “”
!b
z == 0
!z
el == []
!el
em == [:]
!em
$
请注意,Groovy 真值可以出现在 if
语句之外的其他地方。例如
1 boolean x = ""
2 println x
现在运行
$groovy Groovy06c.groovy
false
$
需要明确的是,将 “” 赋值给 x
并不是将 “” 转换为 boolean
!
结论
这里有三个关键要点:第一,==
和 .equals()
的等价性;第二,新的 ===
引用相等性检查;第三,Groovy 真值。