Groovy 与 Java 的区别

目录

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 中,如果对象 abComparable 的, == 被解释成 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

 

 

 

 

 

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值