Groovy与Java的异同(翻译自官网)

824 阅读5分钟

Groovy尽可能做到让Java开发人员很自然地使用Groovy。在设计Groovy时,对于来自Java背景的Groovy的开发人员,我们试图遵循最不让人惊讶的原则,使Java开发人员更快更容易上手!

这里我们列举了Java和Groovy之间的所有主要差异。

1、默认imports

所有这些包和类都是默认导入的,即您不必使用明确的import句来导入它们:

  • java.io.*(io包!)

  • java.lang.*(基本包!包括String等类)

  • java.math.BigDecimal(数学包!)

  • java.math.BigInteger

  • java.net.*(网络包!)

  • java.util.*(工具包!)

  • groovy.lang.*(这是啥!groovy的基本包?maybe)

  • groovy.util.*(groovy的工具包!)

2、动态方法(Multi-methods)

在Groovy中,方法的调用是在运行时决定,而不是在编译时决定的。这种调用方式我们称之为'运行时分发'或'动态方法'。这意味着该方法将根据运行时参数的类型进行选择。在Java中,情况恰恰相反:在编译时根据声明的类型选择方法。

以Java代码编写的代码可以在JavaGroovy中编译,但其行为(结果)会有所不同,我举个栗子:

int method(String arg) {//这里方法参数是String类型
    return 1;
}
int method(Object arg) {//这里参数是Object类型
    return 2;
}
Object o = "Object";//这里声明时是Object,实际是String类型
int result = method(o);

在Java中,您将拥有:

assertEquals(2, result);

而在Groovy中:

assertEquals(1, result);

是因为Java将使用静态信息类型,它被o声明为an Object,而Groovy将在运行时选择该方法实际调用时的类型。由于它调用的是String,所以调用该String版本。

3、数组初始化器

在Groovy,{…}已经被用作闭包,也就是说你不能使用下面的语法创建数组:

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

你实际上必须使用:

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

4、包范围可见性

在Groovy里,省略字段的修饰符不会像Java一样使其成为包私有属性(还记得Java的四种可见性范围吗?默认不写的话就是default,同一包下可见)

class Person {
    String name //相当于default 
}

相反,它用于创建一个属性,也就是说一个私有字段,一个关联的getter和一个关联的setter

可以使用@PackageScope以下注释来创建一个包专用字段:

class Person {
    @PackageScope String name
}

5、ARM块

Groovy不支持来自Java 7ARM(自动资源管理)块。相反,Groovy提供了依赖闭包的各种方法,这些方法具有相同的效果,而且更具惯用性。例如:

Path file = Paths.get("/path/to/file");
Charset charset = Charset.forName("UTF-8");
try (BufferedReader reader = Files.newBufferedReader(file, charset)) {//这是Java 7的特性,资源自动关闭
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }

} catch (IOException e) {
    e.printStackTrace();
}

可以这样写:

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

或者,如果你想要一个更接近Java的版本:

new File('/path/to/file').withReader('UTF-8') { reader ->
   reader.eachLine {//这里咱先不管它eachLine是什么东东!
       println it
   }
}

6、内部类

Groovy遵循了Java的匿名内部类以及嵌套内的特点。但是它并没有完全依照Java语言规范,因此在使用前应该记住它们是有区别的。Groovy的实现和groovy.lang.Clouser类的风格有些类似,但也有不同点。比如在访问私有字段和方法以及局部变量没有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(),这是不合法的。虽然我们还没有找到办法避免用户这样写。

7、Lambdas表达式

Java 8支持lambdas和方法引用:

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

Java 8 lambda可以或多或少地被视为匿名内部类。Groovy不支持这种语法,但是有闭包:

Runnable run = { println 'run' }
list.each { println it } // or list.each(this.&println)

8、GStrings

使用双引号修饰的字符串被解释为GString值。如果一个字符串里含有美元符号$GroovyJava的编译器里将会产生编译错误。

当然,Groovy会自动在GStringString之间进行类型转换,就像Java可以接受一个Object参数然后检查其实际类型一样。

9、字符串和字符文字

在Groovy里,使用单引号修饰的被当成String类型,使用双引号修饰的可以当成GString类型或String类型。取决于字面常量。

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

如果声明是char类型,Groovy会自动将单个字符从String类型转换为char类型。如果被调用的方法声明的参数类型是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类型的时候,单个字符和多个字符转换有些不一样。对于多个字符转换成char类型,Groovy会选择第一个字符,这一点不像C语言,会直接失败。

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

// for multi char strings they are not
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那样遵循相应的自动拆箱优先级的特性,以下是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.compareTo(b)==0,如果他们是Comparable,就是使用a.equals(b),否则检查基本类型,也就是is,比如a.is(b)

12、额外的关键字

Groovy中的关键字比Java中的还要多。不要将它们用于变量名称等。

  • as

  • def

  • in

  • trait