JVMJVM06(编译器处理-java语法糖总结)
Posted 温文艾尔
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVMJVM06(编译器处理-java语法糖总结)相关的知识,希望对你有一定的参考价值。
⭐️写在前面
这里是温文艾尔の学习之路
- 👍如果对你有帮助,给博主一个免费的点赞以示鼓励把QAQ
- 👋博客主页🎉 温文艾尔の学习小屋
- ⭐️更多文章👨🎓请关注温文艾尔主页📝
- 🍅文章发布日期:2022.02.08
- 👋java学习之路!
- 欢迎各位🔎点赞👍评论收藏⭐️
- 🎄新年快乐朋友们🎄
- 👋jvm学习之路!
- ⭐️上一篇内容:【JVM】JVM05(从字节码角度分析i++和++i的执行流程))
文章目录
- ⭐️1编译期处理(语法糖)
- ⭐️1.1默认构造器
- ⭐️1.2自动拆装箱
- ⭐️1.3泛型集合取值
- ⭐️1.4可变参数
- ⭐️1.5 foreach循环
- ⭐️1.6switch字符串
- ⭐️1.7switch枚举
- ⭐️1.8枚举类
- ⭐️1.9 try-with-resources
- ⭐️1.10方法重写时的桥接方法
- ⭐️1.11匿名内部类
⭐️1编译期处理(语法糖)
所谓语法糖,其实就是指java编译器把*.java源码编译为*.class字节码的过程中,自动生成和转换的一些代码,主要是为了减轻程序员的负担,算是java编译器给我们的一个额外福利(给糖吃)
注意以下代码的分析,借助javap工具,idea的反编译功能,idea插件jclasslib等工具。另外,编译器转换的结果直接就是class字节码,只是为了便于阅读,给出了几乎等价的java源码方式,并不是编译器还会转换出中间的java源码,切记。
⭐️1.1默认构造器
public class Candy1
编译成class后的代码:
public class Candy1
//这个无参构造器是编译器帮助我们加上的
public Candy1()
super();//即调用父类Object的无参构造方法
⭐️1.2自动拆装箱
这个特性是JDK5开始加入的,代码片段1:
public class Demo01
public static void main(String[] args)
Integer x = 1;
int y = x;
上面代码在JDK5之前是无法编译通过的,必须改写为代码片段2:
public class Demo01
public static void main(String[] args)
Integer x = Integer.valueOf(1);
int y = x.intValue();
显然之前版本的代码太麻烦了,需要在基本类型和包装类型之间来回转换(尤其是集合类中操作的都是包装类型),因此这些转化的事情在JDK5以后都由编译器在编译阶段完成。即代码片段1都会在编译阶段被转换为代码片段2
⭐️1.3泛型集合取值
泛型也是在JDK5开始加入的特性,但java在编译泛型代码后会执行泛型擦除的动作,即泛型信息在编译为字节码之后就丢失了,实际的类型都当做了Object类型来处理
public class Demo02
public static void main(String[] args)
List<Integer> list = new ArrayList<>();
list.add(10);//实际调用的是List.add(Object e)
Integer x = list.get(0);//实际调用的是Object obj = list.get(int index);
所以在取值时,编译器真正生成的字节码中,还要额外做一个类型转换的操作:
//需要将Object转为Integer
Integer x = ( Integer)list.get(0);
如果前面的x变量类型修改为int基本类型那么最终生成的字节码是:
//需要将Object转为Integer
int x = ((Integer)list.get(0)).intValue();
这些事情现在都不用我们自己做
擦除的是字节码上的泛型信息,可以看到LocalVariableTypeTable仍然保留了方法参数泛型的信息
Compiled from "Demo02.java"
public class com.wql.jvm.Candy.Demo02
minor version: 0
major version: 52
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #8 // com/wql/jvm/Candy/Demo02
super_class: #9 // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
#1 = Methodref #9.#29 // java/lang/Object."<init>":()V
#2 = Class #30 // java/util/ArrayList
#3 = Methodref #2.#29 // java/util/ArrayList."<init>":()V
#4 = Methodref #7.#31 // java/lang/Integer.valueOf:(I)Ljava/lang/Int
eger;
#5 = InterfaceMethodref #32.#33 // java/util/List.add:(Ljava/lang/Object;)Z
#6 = InterfaceMethodref #32.#34 // java/util/List.get:(I)Ljava/lang/Object;
#7 = Class #35 // java/lang/Integer
#8 = Class #36 // com/wql/jvm/Candy/Demo02
#9 = Class #37 // java/lang/Object
#10 = Utf8 <init>
#11 = Utf8 ()V
#12 = Utf8 Code
#13 = Utf8 LineNumberTable
#14 = Utf8 LocalVariableTable
#15 = Utf8 this
#16 = Utf8 Lcom/wql/jvm/Candy/Demo02;
#17 = Utf8 main
#18 = Utf8 ([Ljava/lang/String;)V
#19 = Utf8 args
#20 = Utf8 [Ljava/lang/String;
#21 = Utf8 list
#22 = Utf8 Ljava/util/List;
#23 = Utf8 x
#24 = Utf8 Ljava/lang/Integer;
#25 = Utf8 LocalVariableTypeTable
#26 = Utf8 Ljava/util/List<Ljava/lang/Integer;>;
#27 = Utf8 SourceFile
#28 = Utf8 Demo02.java
#29 = NameAndType #10:#11 // "<init>":()V
#30 = Utf8 java/util/ArrayList
#31 = NameAndType #38:#39 // valueOf:(I)Ljava/lang/Integer;
#32 = Class #40 // java/util/List
#33 = NameAndType #41:#42 // add:(Ljava/lang/Object;)Z
#34 = NameAndType #43:#44 // get:(I)Ljava/lang/Object;
#35 = Utf8 java/lang/Integer
#36 = Utf8 com/wql/jvm/Candy/Demo02
#37 = Utf8 java/lang/Object
#38 = Utf8 valueOf
#39 = Utf8 (I)Ljava/lang/Integer;
#40 = Utf8 java/util/List
#41 = Utf8 add
#42 = Utf8 (Ljava/lang/Object;)Z
#43 = Utf8 get
#44 = Utf8 (I)Ljava/lang/Object;
public com.wql.jvm.Candy.Demo02();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 12: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/wql/jvm/Candy/Demo02;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: aload_1
9: bipush 10
11: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Lja
va/lang/Integer;
14: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Lja
va/lang/Object;)Z
19: pop
20: aload_1
21: iconst_0
22: invokeinterface #6, 2 // InterfaceMethod java/util/List.get:(I)L
java/lang/Object;
27: checkcast #7 // class java/lang/Integer
30: astore_2
31: return
LineNumberTable:
line 14: 0
line 15: 8
line 16: 20
line 17: 31
LocalVariableTable:
Start Length Slot Name Signature
0 32 0 args [Ljava/lang/String;
8 24 1 list Ljava/util/List;
31 1 2 x Ljava/lang/Integer;
//局部变量类型表 ,包含了我们方法参数的一些泛型信息 LocalVariableTypeTable:
Start Length Slot Name Signature
8 24 1 list Ljava/util/List<Ljava/lang/Integer;>;
SourceFile: "Demo02.java"
使用反射,仍然能够获得这些信息
public Set<Integer> test(List<String> list, Map<Integer, Object> map)
Method test = Candy3.class.getMethod("test", List.class, Map.class);
Type[] types = test.getGenericParameterTypes();
for (Type type : types)
if (type instanceof ParameterizedType)
ParameterizedType parameterizedType = (ParameterizedType) type;
System.out.println("原始类型 - " + parameterizedType.getRawType());
Type[] arguments = parameterizedType.getActualTypeArguments();
for (int i = 0; i < arguments.length; i++)
System.out.printf("泛型参数[%d] - %s\\n", i, arguments[i]);
输出
⭐️1.4可变参数
可变参数也是JDK5开始加入的新特性:
例如:
public class Demo03
public static void foo(String... args)
String[] array = args;
System.out.println(array);
public static void main(String[] args)
foo("hello","java");
可变参数String… args 实是-个string[] args, 从代码中的赋值语句中就可以看出来。同样java编译器会在编译期间将上述代码变换为:
public class Demo03
public static void foo(String[] args)
String[] array = args;
System.out.println(array);
public static void main(String[] args)
foo(new String[]"hello","java");
注意:
如果调用了foo(),则等价代码为foo(new String[]),创建了一个空的数组,而不会传递null进去
⭐️1.5 foreach循环
JDK5开始引入的语法糖,数组的循环:
public class Demo04
public static void main(String[] args)
int[] array = 1,2,3,4,5;//数组赋初值的简化写法也是语法糖
for (int e:array)
System.out.println(e);
会被编译器转换为:
public class Demo04
public static void main(String[] args)
int[] array = new int[]1,2,3,4,5;//数组赋初值的简化写法也是语法糖
for (int i = 0; i < array.length; ++i)
int e =array[i];
System.out.println(e);
而集合的循环:
public class Demo05
public static void main(String[] args)
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
for (Integer i : list)
System.out.println(i);
实际被编译器转换为对迭代器的调用:
public class Demo05
public static void main(String[] args)
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Iterator<Integer> iter = list.iterator();
while (iter.hasNext())
Integer e = iter.next();
System.out.println(e);
注意:
foreach循环写法,能够配合数组,以及所有实现了Iterable接口的集合类一起使用,其中Iterable用来获取集合的迭代器(Iterator)
⭐️1.6switch字符串
从JDK7开始,switch可以作用于字符串和枚举类,这个功能其实也是语法糖,例如:
public class Demo06
public static void choose(String str)
switch (str)
case "hello":
System.out.println("h");
break;
case "world":
System.out.println("w");
break;
注意:
switch配合String和枚举使用时,变量不能为null,原因分析完语法糖转换后的代码应当自然清楚会被编译器转换为:
public class Demo06
public static void choose(String str)
byte x = -1;
switch (str.hashCode())
case 99162322://hello的hashCode
if (str.equals("hello"))
x=0;
break;
case 113318802://world的hashCode
if (str.equals("world"))
x=1;
switch (x)
case 0:
System.out.println("h");
break;
case 1:
System.out.println("w");
可以看到,执行了两边switch,第一遍是根据字符串的hashCode和equals将字符串的转换为相应byte类型,第二遍才是利用byte执行进行比较
为什么第 以上是关于JVMJVM06(编译器处理-java语法糖总结)的主要内容,如果未能解决你的问题,请参考以下文章