groovy静态编译容易引发的问题
groovy为了提高性能,有一个静态编译的注解@CompileStatic
,在类上添加该注解后,编译出来的class文件会更加静态化,更像java文件编译出来的class字节码。
但是因此会导致一些问题,今天我就遇到一个,下面将其记录下来。
众所周知,groovy是动态类型的,如下面重载的函数,使用java调用和groovy调用结果是不同的。
1. 测试groovy非静态编译
在groovy中,定义变量时使用的是Object,是在调用时决定调用哪一个方法的,下面两次调用test方法,均能正确识别出对应的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Test { static void main(String[] args) { Object param = "a" test(param)
Object param2 = 1 test(param2) }
static void test(Object b) { println "Object" }
static void test(String a) { println "String" }
static void test(Integer b) { println "Integer" } }
|
2.测试java
上面同样的代码,使用java写就会变得不一样,因为java是在编译期间就决定应该调用哪个方法的,下面三个方法中,只会调用参数为Object的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class Test2 {
public static void main(String[] args) { Object param = "a"; test(param);
Object param2 = 1; test(param2);
}
static void test(Object a) { System.out.println("Object"); }
static void test(String b) { System.out.println("String"); }
static void test(Integer b) { System.out.println("Integer"); } }
|
3.测试groovy静态编译
现在已经明白groovy和java调用的区别了,那如果将groovy进行静态编译后,结果是什么样子呢?
使用下面代码测试后,发现,groovy启用静态编译后仍然能够识别到对应的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @CompileStatic class Test {
static void main(String[] args) { Object param = "a" test(param)
Object param2 = 1 test(param2) }
static void test(Object b) { println "Object" }
static void test(String a) { println "String" }
static void test(Integer b) { println "Integer" } }
|
4.加大难度测试groovy静态编译
既然添加静态编译注解后,为啥编译出来的代码的运行行为仍然和非静态编译的groovy效果一样呢?我怀疑是代码太简单了,groovy能识别出类型,我将情况弄得再复杂一些。
为了给groovy编译器增加难度,我将参数先存储到list里,再取出,看groovy能否识别。
经过下面的测试结果,完全出乎我的预期,groovy将Integer强转成String了,他做了转换。即使参数是Date对象,他仍然将其转成了字符串,这种转换在大部分情况下都是不符合正常人预期的。
大部分情况下会导致问题。而移除@CompileStatic
注解后,因为是在运行时判断类型的,所以能够识别出正确的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @CompileStatic class Test {
static void main(String[] args) { List list = Arrays.asList("a", 1,new Date()) test(list.get(0))
test(list.get(1)) test(list.get(2)) }
static void test(Object b) { println "Object" }
static void test(String a) { println "String" }
static void test(Integer b) { println "Integer" } }
|
上面这种不符合预期的方法查找就是产生bug的根源,并且idea 点击ctrl左键跳转的方法,与实际选择的方法不一致,这也增加了查找bug的难度。所以我建议减少使用静态编译注解的使用,虽然其能够提高点性能,但可能结果是无法预期的。
5.测试java中正确,groovy中不正确的例子。内层闭包使用外层闭包变量,无法正确识别变量类型
下面的代码中,我们使用了静态编译,其中map操作符中的pop参数是String类型的,然而真实调用时,触发testFunction的却是参数为Object的。
这是因为我们在使用pop参数时,没有在当前闭包中使用,而是再创建了一个闭包,我们在内层闭包中使用pop时,没有将pop识别成String类型,而在java中因为是强类型,是可以识别的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @CompileStatic class Test {
static void main(String[] args) { List<String> list = Arrays.asList("a", "b", "c")
Object response = list.stream() .map({ pop -> return { testFunction(pop) return "sss" }.call() }) .collect(Collectors.toList());
System.out.println(response) }
static void testFunction(Object a) { System.out.println("object"); }
static void testFunction(String a) { System.out.println("String"); } }
|
将上面的代码中添加一行,在第一个闭包中使用一下pop变量,那么就能正确识别到变量pop是String类型,从而调用参数为String的testFunction方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @CompileStatic class Test {
static void main(String[] args) { List<String> list = Arrays.asList("a", "b", "c")
Object response = list.stream() .map({ pop -> def a = pop return { testFunction(pop) return "sss" }.call() }) .collect(Collectors.toList());
System.out.println(response) }
static void testFunction(Object a) { System.out.println("object"); }
static void testFunction(String a) { System.out.println("String"); } }
|
第五条描述的现象(静态编译下,内层闭包直接使用外层闭包参数,导致参数类型无法识别)也是我今天第一次发现。我还没有查找资料,我猜应该是bug吧,毕竟这样与java差距太大,不符合常人预期,后面有发现我再继续更新。