JVM -- 编译器处理;语法糖
Posted MinggeQingchun
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM -- 编译器处理;语法糖相关的知识,希望对你有一定的参考价值。
语法糖(Syntactic sugar),其实就是指 java 编译器把 *.java 源码编译为 *.class 字节码的过程中,自动生成 和转换的一些代码
语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。——百度百科
一、默认构造器
//java源码
public class Candy
//编译成class后的代码
public class Candy
// 这个无参构造是编译器帮助我们加上的
public Candy()
super(); // 即调用父类 Object 的无参构造方法,即调用 java/lang/Object."
//<init>":()V
二、自动拆装箱
JDK1.5之后,新增了自动拆、装箱功能
简化基本类型和对象转换的写法
装箱:基本类型的值被封装为一个包装类对象
拆箱:一个包装类对象被拆开并获取相应的值
这是编译器的工作,在class中已经添加,虚拟机没有自动装箱和拆箱的语句
==:基本类型是内容相同,对象时指针是否相同(内存同一个区域)
基本类型没有空值,对象有null
当一个基础数据类型与封装类进行==、+、-、*、/运算时,会将封装类进行拆箱,然后再运算
如以前创建一个Integer对象,需要 使用 “new”关键字
而现在Java中可以直接赋值如下:
Integer不是new出Integer对象,而是直接赋值,就是自动装箱过程
Integer a = new Integer("100");
//JDK1.5之后
Integer b = 100;
int x = b;
三、泛型集合取值
泛型也是在 JDK 5 开始加入的特性,但 java 在编译泛型代码后会执行 泛型擦除 的动作,即泛型信息 在编译为字节码之后就丢失了,实际的类型都当做了 Object 类型来处理
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 仍然保留了方法参数泛型的信息
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: 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)Ljava/lang/Integer;
14: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
19: pop
20: aload_1
21: iconst_0
22: invokeinterface #6, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
27: checkcast #7 // class java/lang/Integer
30: astore_2
31: return
LineNumberTable:
line 8: 0
line 9: 8
line 10: 20
line 11: 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;>;
使用反射,仍然能够获得这些信息
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]);
输出:
原始类型 - interface java.util.List
泛型参数[0] - class java.lang.String
原始类型 - interface java.util.Map
泛型参数[0] - class java.lang.Integer
泛型参数[1] - class java.lang.Object
四、可变参数
可变参数也是 JDK 5 开始加入的新特性
public class Candy4
public static void foo(String... args)
String[] array = args; // 直接赋值
System.out.println(array);
public static void main(String[] args)
foo("hello", "world");
可变参数 String... args 其实是一个 String[] args ,从代码中的赋值语句中就可以看出来。 同样 java 编译器会在编译期间将上述代码变换为
public class Candy4
public static void foo(String[] args)
String[] array = args; // 直接赋值
System.out.println(array);
public static void main(String[] args)
foo(new String[]"hello", "world");
注:
调用了 foo() 则等价代码为 foo(new String[]) ,创建了一个空的数组,而不会传递 null 进去
五、foreach 循环
foreach是 JDK 5 开始引入的语法糖,数组的循环
public static void main(String[] args)
int[] array = 1, 2, 3, 4, 5; // 数组赋初值的简化写法也是语法糖
for (int e : array)
System.out.println(e);
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 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 static void main(String[] args)
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Iterator iter = list.iterator();
while(iter.hasNext())
Integer e = (Integer)iter.next();
System.out.println(e);
注:
foreach 循环写法,能够配合数组,以及所有实现了 Iterable 接口的集合类一起使用,其中 Iterable 用来获取集合的迭代器( Iterator )
六、switch 字符串
JDK 7 开始,switch 可以作用于字符串和枚举类
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 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 执行进行比较。
第一遍时必须既比较 hashCode,又利用 equals 比较,是因为hashCode 是为了提高效率,减少遍历次数;而 equals 是为了防止 hashCode 冲突,例如 BM 和 C. 这两个字符串的hashCode值都是 2123
如下代码
public static void choose(String str)
switch (str)
case "BM":
System.out.println("h");
break;
case "C.":
System.out.println("w");
break;
会被编译器转换为
public static void choose(String str)
byte x = -1;
switch(str.hashCode())
case 2123: // hashCode 值可能相同,需要进一步用 equals 比较
if (str.equals("C."))
x = 1;
else if (str.equals("BM"))
x = 0;
default:
switch(x)
case 0:
System.out.println("h");
break;
case 1:
System.out.println("w");
七、switch 枚举
enum Sex
MALE, FEMALE
public static void foo(Sex sex)
switch (sex)
case MALE:
System.out.println("男");
break;
case FEMALE:
System.out.println("女");
break;
转换后代码
/**
* 定义一个合成类(仅 jvm 使用,对我们不可见)
* 用来映射枚举的 ordinal 与数组元素的关系
* 枚举的 ordinal 表示枚举对象的序号,从 0 开始
* 即 MALE 的 ordinal()=0,FEMALE 的 ordinal()=1
*/
static class $MAP
// 数组大小即为枚举元素个数,里面存储case用来对比的数字
static int[] map = new int[2];
static
map[Sex.MALE.ordinal()] = 1;
map[Sex.FEMALE.ordinal()] = 2;
public static void foo(Sex sex)
int x = $MAP.map[sex.ordinal()];
switch (x)
case 1:
System.out.println("男");
break;
case 2:
System.out.println("女");
break;
八、枚举类
JDK 7 新增了枚举类
转换后代码
public final class Sex extends Enum<Sex>
public static final Sex MALE;
public static final Sex FEMALE;
private static final Sex[] $VALUES;
static
MALE = new Sex("MALE", 0);
FEMALE = new Sex("FEMALE", 1);
$VALUES = new Sex[]MALE, FEMALE;
/**
* Sole constructor. Programmers cannot invoke this constructor.
* It is for use by code emitted by the compiler in response to
* enum type declarations.
*
* @param name - The name of this enum constant, which is the identifier
* used to declare it.
* @param ordinal - The ordinal of this enumeration constant (its position
* in the enum declaration, where the initial constant is
assigned
*/
private Sex(String name, int ordinal)
super(name, ordinal);
public static Sex[] values()
return $VALUES.clone();
public static Sex valueOf(String name)
return Enum.valueOf(Sex.class, name);
九、try-with-resources
JDK 7 开始新增了对需要关闭的资源处理的特殊语法 try-with-resources
try(资源变量 = 创建资源对象)
catch( )
资源对象需要实现 AutoCloseable 接口,例如 InputStream 、 OutputStream 、 Connection 、 Statement 、 ResultSet 等接口都实现了 AutoCloseable ,使用 try-withresources 可以不用写 finally 语句块,编译器会帮助生成关闭资源代码
public static void main(String[] args)
try(InputStream is = new FileInputStream("d:\\\\1.txt"))
System.out.println(is);
catch (IOException e)
e.printStackTrace();
会被转换为
public static void main(String[] args)
try
InputStream is = new FileInputStream("d:\\\\1.txt");
Throwable t = null;
try
System.out.println(is);
catch (Throwable e1)
// t 是我们代码出现的异常
t = e1;
throw e1;
finally
// 判断了资源不为空
if (is != null)
// 如果我们代码有异常
if (t != null)
try
is.close();
catch (Throwable e2)
// 如果 close 出现异常,作为被压制异常添加
t.addSuppressed(e2);
else
// 如果我们代码没有异常,close 出现的异常就是最后 catch 块中的 e
is.close();
catch (IOException e)
e.printStackTrace();
设计一个 addSuppressed(Throwable e) (添加被压制异常)的方法,是为了防止异常信 息的丢失(想想 try-with-resources 生成的 fianlly 中如果抛出了异常)
public static void main(String[] args)
try (MyResource resource = new MyResource())
int i = 1/0;
catch (Exception e)
e.printStackTrace();
class MyResource implements AutoCloseable
public void close() throws Exception
throw new Exception("close 异常");
输出:
java.lang.ArithmeticException: / by zero
at test.Test6.main(Test6.java:7)
Suppressed: java.lang.Exception: close 异常
at test.MyResource.close(Test6.java:18)
at test.Test6.main(Test6.java:6)
如以上代码所示,两个异常信息都不会丢
十、方法重写时的桥接方法
方法重写时对返回值分两种情况
1、父子类的返回值完全一致
2、子类返回值可以是父类返回值的子类
class A
public Number m()
return 1;
class B extends A
@Override
// 子类 m 方法的返回值是 Integer 是父类 m 方法返回值 Number 的子类
public Integer m()
return 2;
对于子类,java 编译器会做如下处理
class B extends A
public Integer m()
return 2;
// 此方法才是真正重写了父类 public Number m() 方法
public synthetic bridge Number m()
// 调用 public Integer m()
return m();
桥接方法比较特殊,仅对 java 虚拟机可见,并且与原来的 public Integer m() 没有命名冲突,可以 用下面反射代码来验证
for (Method m : B.class.getDeclaredMethods())
System.out.println(m);
输出
public java.lang.Integer test.candy.B.m()
public java.lang.Number test.candy.B.m()
十一、匿名内部类
public static void main(String[] args)
Runnable runnable = new Runnable()
@Override
public void run()
System.out.println("ok");
;
转换后代码
// 额外生成的类
final class Candy11$1 implements Runnable
Candy11$1()
public void run()
System.out.println("ok");
public class Candy11
public static void main(String[] args)
Runnable runnable = new Candy11$1();
引用局部变量的匿名内部类,源代码
public class Candy11
public static void test(final int x)
Runnable runnable = new Runnable()
@Override
public void run()
System.out.println("ok:" + x);
;
转换后代码
// 额外生成的类
final class Candy11$1 implements Runnable
int val$x;
Candy11$1(int x)
this.val$x = x;
public void run()
System.out.println("ok:" + this.val$x);
public class Candy11
public static void test(final int x)
Runnable runnable = new Candy11$1(x);
注:
这同时解释了为什么匿名内部类引用局部变量时,局部变量必须是 final 的:因为在创建 Candy11$1 对象时,将 x 的值赋值给了 Candy11$1 对象的 val$x 属性,所以 x 不应该再发生变 化了,如果变化,那么 val$x 属性没有机会再跟着一起变化
以上是关于JVM -- 编译器处理;语法糖的主要内容,如果未能解决你的问题,请参考以下文章