Java与Groovy的不同点

Groovy与Java的区别

我主要参考这个链接:英文官方资料

在设计Groovy的时候我们按照”给Java开发者带来越小的迷惑越好”的原则来尽力让Java开发者使用感到自然.尤其是对于Java后端开发者.

下面我列出了所有的主要的Groovy和Java的不同点.

默认导入(Default imports)

下面的这些包和类会被默认的导入进来,例如,你没有必要额外的导入语句来使用下面的类或者包.

  • java.io.*
  • java.lang.*
  • java.math.BigDecimal
  • java.math.BigInteger
  • java.net.*
  • java.util.*
  • groovy.lang.*
  • groovy.util.*

因为这些都已经被默认导入了

多方法(Multi-methods)

在Groovy中,方法的调用是在运行时被被决定的.这个叫运行时调用或者多方法.这意味着在运行时选择哪个方法调用是被参数的类型决定的,在Java里面就完全相反,方法的调用是在编译期就依据类型的声明被决定的.

下面的代码,以Java代码的风格编写,可以同时被Java和Groovy编译,但是两者的行为是不一样的.

1
2
3
4
5
6
7
8
int method(String arg) {
return 1;
}
int method(Object arg) {
return 2;
}
Object o = "Object";
int result = method(o);

在Java中,代码的执行的方法结果为:2;在Groovy中,代码的执行的方法结果为:1

这是因为java是静态类型的语言,那么入参o是被声明为Object对象实例,那么方法调用在编译器决定时自然选择method(Object arg).而Groovy是动态语言,选择调用哪个方法是在运行期,运行时当方法method要被调用时,入参o实际是String对象实例,调用是就会选择method(String arg)

总结:当方法被重载的时候,Java和Groovy是不一样的.

数组初始化(Array initializers)

在Groovy里面,{…}块被作为闭包,这意味着你不能使用这个语法去声明任何数组字面量

在Java里面

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

在Groovy里面

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

包作用域可见性(Package scope visibility)

Java语言中包的可见性修饰符有public,default.protected,protected.而在Groovy里面默认是没有的.全部都是public

1
2
3
class Person {
String name
}

作为替代,如果你需要将一个类里面的属性声明为包私有,那么加注解@PackageScope将这个属性声明为包私有域就可以.这样不仅会将这个属性变成包私有的而且还会为这个属性生成getter和setter方法。

如下:

1
2
3
class Person {
@PackageScope String name
}

ARM 块(Automatic Resource Management)

自动资源管理(Automatic Resource Management)代码块从Java7开始就不在被Groovy支持,取代它的是Groovy依照闭包实现的一系列方法.

这个和下面的Java写法是一样的

1
2
3
4
5
6
7
8
9
10
11
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();
}

Groovy里面就会被这样写:

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

如果你希望风格更加接近Java风格,那么可以这样:

1
2
3
4
5
new File('/path/to/file').withReader('UTF-8') { reader ->
reader.eachLine {
println it
}
}

内部类(Inner classes)

Groovy匿名内部类和嵌套类的实现以Java的为参照,但是你不能拿出Java语言规完全照搬到Groovy上,并对Groovy和Java的不同点摇头拒绝接受.Groovy的内部类实现方式类似于闭包groovy.lang.Closure. groovy这样有一些优点也有一些不同,例如缺点是groovy获取内部类的私有属性和私有方法就是一个难题,但是优点是java内部类里面的局部变量不在一定要被声明为final

静态内部类(Static inner classes)

这是一个静态内部类例子

1
2
3
4
5
class A {
static class B {}
}

new A.B()

Java的静态内部类是被Groovy支持的最好的内部类,如果你的Groovy一定要写内部类,我建议你使用静态内部类

匿名内部类(Anonymous Inner Classes)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)

非静态内部类(Non-Static Inner Classes)

在Java里面我们可以这样

1
2
3
4
5
6
7
8
9
public class Y {
public class X {}
public X foo() {
return new X();
}
public static X createX(Y y) {
return y.new X();
}
}

Groovy doesn’t support the y.new X() syntax. Instead, you have to write new X(y), like in the code below:

Groovy不支持y.new X()语法,作为替代,Groovy里面你可以new X(y)这样的语法来实现

1
2
3
4
5
6
7
8
9
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()而不是是new X(y),遗憾的是暂时groovy还没有有效的方式去组织你犯这样的错误,只能靠开发者自己注意.所以大家要注意new X()是在外部类里面调用内部类X构造方法创建X实例,而new X(y)是调用X的构造方法并且指定X的外部类Y.

函数式编程(Lambda)

Java8 支持的Lambda和方法引用

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

Java8 lambda表达式或多或少的被认为是匿名内部类,Groovy不支持这样的实现方式而是使用闭包实现.

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

Groovy String(GStrings)

双引号的字符串字面量被声明为String,但是当双引号里面的字符串是带上了$符号实现的赋值字符串,那么使用Groovy和Java的编译器就会出现一些细节的不同,Groovy会填入具体的值产生实际的字符串而Java可能会要么当成普通字符串要么编译可能错误.

例如

1
2
int x= 1
String s = "GString ${x}"

在Groovy里面是GString 1 ${x}会被值替代;而在Java里面就是GString ${x}被当成普通字符串

所以,在Groovy里面可以这样记忆:不带$填值写法的双引号普通字符串,groovy/java是一样的都是普通字符串.而带了$填值操作的双引号字符串,在groovy里面就是GString,在Java里面依然是普通字符串或者编译可能保错.

字符串和字符(String and Character literals)

单引号字面量在Groovy里面被当作String,双引号字符串在Groovy被当成String或者GString(取决于双引号里面是否有$插值操作)

例如这几种写法都是字符串:

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

当把一个单引号字符串赋值给char时,Groovy会将变量自动类型转换为字符,当调用一个参数为字符的方式时你也可以用强制类型转换或者提前将符串转换成字符

下面时使用例子

1
2
3
4
5
6
7
8
9
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支持两种方式实现字符串类型转换为字符.第一是Groovy风格的字符串转换,这种转换将会取字符串的第一个字符,第二是C语言风格的表达式但是可能会导致异常

1
2
3
4
5
// 第一种方式
char c1='ab' // groovy风格,结果是a
// 第二种方式
def c2 = (char)'a' // c语言风格,结果是a
def c3 = (char)'ab' // c语言风格会导致异常

下面是使用事例:

1
2
3
4
5
6
7
8
9
10
11
12
// for single char strings, both are the same
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'

基本类型和包装类型

Groovy是万物皆对象的语言他会自动将基本类型包装为引用类型.因此,groovy和Java在对待基本类型是否先装箱拆箱是不一样的,下面是一个例子

例如:

1
2
3
4
5
6
7
8
9
10
int i
m(i)

void m(long l) {
println "in m(long)"
}

void m(Integer i) {
println "in m(Integer)"
}

java会调用m(long l),因为java基本类型值类转换中优先于装箱拆箱,而Groovy里面就会调用m(Integer i)因为groovy的所以基本类型取值都是包装类型

== 操作符

在java里面,==对于基本数据类型就是值的比较而对引用类型就是引用地址的比较.但是在Groovy里面==会被转化为a.compareTo(b)==0操作,如果没有compareTo那么a.equals(b)会被调用.如果你真的是要进行引用地址比较,那么使用is操作就可以,例如a.is(b)

转换

在处理基本类型值的转换上,groovy上比Java更加复杂.具体可以参考官网的图,我这边markdown无法完全表现出来,请见谅,好在这个也不是太重要^^

扩展键字

groovy相比java多了几个关键字,不要在Groovy里面使用这几个关键字做变量名-

  • as
  • def
  • in
  • trait