Groovy 与 Java 的区别

本文详细对比了Groovy和Java之间的主要差异,包括默认导入、多方法(运行时分发)、数组初始化、包作用域可见性、ARM代码块、内部类、Lambda表达式、GString、字符串和字符字面量、原始类型和包装类、==操作符的行为以及额外的关键字。Groovy在设计上更注重开发者体验,提供了许多Java不具备的特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

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

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值