概述
目录
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
最后
以上就是稳重保温杯为你收集整理的Groovy 与 Java 的区别的全部内容,希望文章能够帮你解决Groovy 与 Java 的区别所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复