我写的代码vs编译后的代码

108 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第14天,点击查看活动详情

我写的代码vs编译后的代码

环境: java8

对比下我们写的代码在编译为class文件之后的一些变化

自动装箱和自动拆箱

Integer a = 1;
int b = a;

我们使用 javap -p -v Test.class 查看详细的执行的字节码指令, 可以看出这两行代码在编译之后多了Integer.valueOf()Integer.intValue() 也就是多了我们常说的自动装箱和自动拆箱, 写代码过程中应该尽量避免这种没必要产生的性能损耗

装箱拆箱.jpg

字符串拼接

String a = "a" + "b";

str.png 我们打开Test.class文件, 在idea反编译后的代码可以看出, 编译后的class文件中代码进行了常量折叠, 最终只在常量池产生了一个"ab"对象

这里关于常量池还有个有意思的, 之前在看《深入理解Java虚拟机》第二版的时候, 有这样一段测试代码, 当时看的也是非常疑惑

常量测试.jpg

终于由R大解释了这个问题, 并在《深入理解Java虚拟机》第三版中标注出来

解释.jpg

String a = "a"; 
String b = "b"; 
String c = a + b;

Strbuiler.png

String a = "";
for (int i = 0; i < 10; i++) {
    a = a + i;
}
System.out.println(a);

image.png

最终class文件中是使用StringBuilder进行拼接的, 我们知道String对象是不可变的, 如果没有编译器优化, 那么使用+符号连接会产生很多String对象

forEach

List<Integer> list = Arrays.asList(1, 2, 3);
for (Integer integer : list) {
    System.out.println(integer);
}

image.png forEach循环编译后的代码仍然是使用迭代的方式, 并且我们还可以看到var2.next()进行了强制转换, Java中的泛型看起来并不是真的泛型, 编译后泛型被擦除, 自动做强制转换, 这样做让我们在编写代码的过程中不必做恶心的强制转换(强制转换总是不那么让人放心),

lambda表达式

Consumer consumer = a -> System.out.println("hello");

lamdba.jpg 同样使用 javap -p -v Test.class 可以看出来lambda表达式最终是生成了一个lambda$main$0(java.lang.Object)的方法, 还是普通的方法调用, 但是lambda表达式写起来更简单, 我们知道在lambda表达式中不能给变量重新赋值, 为什么呢? 我们把上面的例子稍微改造一下

 String s = "";
 Map map = new HashMap();
 Consumer consumer = a -> {
     System.out.println(s);
     map.put("a","b");
     System.out.println("hello");
 };

lamdba2.jpg 再使用javap -p -v Test.class, 可以看到生成了lambda$main$0(java.lang.String, java.util.Map, java.lang.Object)方法, 将变量当做方法参数进行传递, 这也是为什么不能给变量重新赋值的原因了, 但是对象属性或者数组的值可以被操作, 那么我们可以用这样的方式变相的来给变量重新赋值

 String s = "s";
 String[] ss = new String[]{s};
 Consumer consumer = a -> ss[0] = "a";
 consumer.accept(1);
 s = ss[0];
 System.out.println(s);