目录

1. 默认导入

2. 多方法(运行时分发)

3. 数组初始化

4. 包作用域可见性

5. ARM 代码块

6. 内部类

6.1 静态内部类

6.2 匿名内部类

6.3 创建非静态内部类的实例

7. Lambda 表达式

8. GString

9. 字符串和字符字面量

10. 原始类型和包装类

11. == 操作符的行为

12. 额外的关键字

Groovy 一直在尝试让 Java 开发者们在使用该语言时尽可能的自然。在设计 Groovy 时,我们尝试遵循了最小惊讶原理,尤其关注来自 Java 世界的开发者们在学习 Groovy 时的体验。

下面我们列出 Groovy 和 Java 之间存在的所有主要区别。

1. 默认导入

下面这些包和类在 Groovy 中都是默认导入的,所以不再需要使用显式的 import 语句来导入它们:

java.io.*

java.lang.*

java.math.BigDecimal

java.math.BigInteger

java.net.*

java.util.*

groovy.lang.*

groovy.util.*

2. 多方法(运行时分发)

在 Groovy 中,最终要调用哪个方法是在运行时决定的。这就是运行时分发或叫做多方法。这意味着,要调用的方法会在运行时根据方法参数的具体类型来选择。在 Java 却与之相反:Java 中的方法调用是在编译时确定的,基于方法中声明的类型。

下面这段 Java 代码在 Java 和 Groovy 中都是可以编译通过的,但是却具有不同的行为:

int method(String arg) {

return 1;

}

int method(Object arg) {

return 2;

}

Object o = "Object";

int result = method(o);

在 Java 中断言结果如下:

assertEquals(2, result);

在 Groovy 中却得到下面这个不同的结果:

assertEquals(1, result);

这是因为 Java 会使用静态类型信息,也就是说 o 被声明成了一个 Object;而 Groovy 会在运行时进行方法选择,根据参数的实际类型来决定,因为实际使用了一个字符串来调用方法,所以选择了 String 版本的方法。

3. 数组初始化

在 Groovy 中,{ ... } 代表的是闭包。这意味着你不可以像下面这样创建数组字面量:

int[] array = { 1, 2, 3}

正确的创建方法像下面这样:

int[] array = [1,2,3]

4. 包作用域可见性

在 Groovy 中,省略字段前面的修饰符,并不会像 Java 中那样导致该字段变成包私有字段:

class Person {

String name

}

相反,它实际上是创建了一个属性,就是说,创建了一个私有字段,和与该字段相关的存取器(getter 和 setter)。

我们可以使用 @PackageScope 注解来将一个字段声明为包私有字段:

class Person {

@PackageScope String name

}

5. ARM 代码块

Groovy 不支持来自 Java 7 的 ARM(自动资源管理)代码块。相反,Groovy 提供了许多基于闭包的方法来达到同样的效果,而且更加符号使用习惯。例如:

Path file = Paths.get("/path/to/file");

Charset charset = Charset.forName("UTF-8");

try (BufferedReader reader = Files.newBufferedReader(file, charset)) {

String line;

while ((line = reader.readLine()) != null) {

System.out.println(line);

}

} catch (IOException e) {

e.printStackTrace();

}

这段 Java 代码在 Groovy 中写法类似这样:

new File('/path/to/file').eachLine('UTF-8') {

println it

}

或者,可以写成下面这个更贴近 Java 的版本:

new File('/path/to/file').withReader('UTF-8') { reader ->

reader.eachLine {

println it

}

}

6. 内部类

Groovy 中匿名内部类和嵌套类的实现遵循 Java 的规范,但是读者不应该拿出 Java 语言规范来,在二者的差异点上对 Groovy 予以否定。Groovy 中对这部分的实现看起来就像在 groovy.lang.Closure 上 Groovy 做出的决定一样:有益但又有不同。例如,访问私有的字段和方法会出问题,但另一方面局部变量却不再需要是 final 的。

6.1 静态内部类

下面是一个静态内部类的例子:

class A {

static class B {}

}

new A.B()

静态内部类的使用是被支持的最好的一个。如果你确实需要一个内部类,请将它设置成静态的。

6.2 匿名内部类

import java.util.concurrent.CountDownLatch

import java.util.concurrent.TimeUnit

CountDownLatch called = new CountDownLatch(1)

Timer timer = new Timer()

timer.schedule(new TimerTask() {

void run() {

called.countDown()

}

}, 0)

assert called.await(10, TimeUnit.SECONDS)

6.3 创建非静态内部类的实例

在 Java 中允许这样做:

public class Y {

public class X {}

public X foo() {

return new X();

}

public static X createX(Y y) {

return y.new X();

}

}

Groovy 不支持 y.new X() 语法。你需要像下面这段代码中一样,写成 new X(y):

public class Y {

public class X {}

public X foo() {

return new X()

}

public static X createX(Y y) {

return new X(y)

}

}

请注意,Groovy 支持在不传参数的情况下调用单参数方法。这时方法的参数会被设置成 null。这个规则也适用于调用构造函数。这就存在一个危险:你可能想写 new X(this) 但是却写成了 new X()。由于 new X() 和 new X(this) 都是很常规的用法,目前我们没有找到较好的方法来避免上面的问题。

7. Lambda 表达式

Java 8 支持 lambda 表达式和方法引用:

Runnable run = () -> System.out.println("Run");

list.forEach(System.out::println);

Java 8 的 lambda 表达式类似于匿名内部类。Groovy 不支持 lambda 表达式这种语法,但是 Groovy 有闭包可以替代它:

Runnable run = { println 'run' }

list.each { println it } // or list.each(this.&println)

8. GString

在 Groovy 中,由双引号括起来的字符串字面量被解释为 GString(插值字符串:如 "${name}")。如果一个类中有个包含 $ 的字符串字面量,在使用 Groovy 和 Java 编译器进行编译时,Groovy 可以会报编译错误,或者生成一段不易察觉的不同于 Java 的代码。

尽管一般来说,如果一个 API 中声明了参数类型,Groovy 会自动在 GString 和 String 之间做转换以满足 API 要求,但是请注意那些声明时接受 Object 类型参数,之后又对实际参数类型做检查的 Java API。

9. 字符串和字符字面量

单引号括起来的字面量在 Groovy 中被解释为字符串 String,双引号括起来的字符串可能是 String 也可能是 GString 这具体要看字符串字面量中是否有插值发生。

assert 'c'.getClass()==String

assert "c".getClass()==String

assert "c${1}".getClass() in GString

只有当向一个 char 类型的变量赋值时,Groovy 才会自动将一个单引号括起来的单字符 String 转换为 char。当调用接受 char 类型参数的方法时,我们要么进行显式的类型转换,要么确保参数值在调用前已经转换好了。

char a='a'

assert Character.digit(a, 16)==10 : 'But Groovy does boxing'

assert Character.digit((char) 'a', 16)==10

try {

assert Character.digit('a', 16)==10

assert false: 'Need explicit cast'

} catch(MissingMethodException e) {

}

Groovy 支持两种风格的类型转换,就转换为 char 而言,当对一个多字符的字符串进行转换时存在细微的不同:Groovy 风格的转换(使用 as)更加宽松,它会去字符串中的第一个字符;C 风格的类型转换将会抛出异常:

// 处理单字符的字符串时,它们效果相同

assert ((char) "c").class==Character

assert ("c" as char).class==Character

// 处理多字符字符串时,它们效果不同

try {

((char) 'cx') == 'c'

assert false: 'will fail - not castable'

} catch(GroovyCastException e) {

}

assert ('cx' as char) == 'c'

assert 'cx'.asType(char) == 'c'

10. 原始类型和包装类

因为在 Groovy 中一切皆对象,它会自动包装原始类型的引用。以此,他和 Java 的行为不一样,在 Java 中精度提升(widening)的优先级高于装箱。下面使用 int 做个例子:

int i

m(i)

void m(long l) { //1

println "in m(long)"

}

void m(Integer i) { //2

println "in m(Integer)"

}

//1: 在 Java 中会调用该方法,因为精度提升的优先级高于装箱

//2: 在 Groovy 中会调用该方法,因为所有原始类型的引用都会使用它们对应的包装类

11. == 操作符的行为

在 Java 中,== 表示原始类型的相等或对象身份的相同(引用的相等)。在 Groovy 中,如果对象 a,b 是 Comparable 的, == 被解释成 a.compareTo(b) == 0,否则将被解释成 a.equals(b)。如果想检查引用的相等性,需要使用 is 操作符,如:a.is(b)

12. 额外的关键字

Groovy 相比 Java 多了如下这些关键字,请不要把它们用作变量名:

as

def

in

trait

参考文献:

http://www.groovy-lang.org/differences.html