语法糖。
语法糖,就是java编译器把java文件编译为class字节码的过程中,自动生成和转换的一些代码,主要是为了减轻程序员的负担。
这里使用java代码来解释一些语法糖。
1 默认构造器
编译成class后的代码
1 2 3 4 5 6 public class Candy { public Candy () { super (); } }
2 自动拆装箱
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Candy { public static void main (String[] args) { Integer x = Integer.valueOf(1 ); int y = x.intValue(); } } public class Candy { public static void main (String[] args) { Integer x = 1 ; int y = x; } }
3 泛型集合取值
泛型在JDK5之后加入的特性,但Java在编译泛型代码后会执行泛型擦除动作,即泛型信息在编译为字节码后就丢失了,实际的类型都当作了Object类型来处理。
1 2 3 4 5 6 7 public class Candy { public static void main (String[] args) { List<Integer> list = new ArrayList<>(); list.add(10 ); Integer x = list.get(0 ); } }
所以在取值时,编译器真正生成的字节码中,还需要额外做一个类型转换的动作:
1 2 Integer x = (Integer)list.get(0 );
如果前面的x变量类型修改为int基本类型那么最终生成的字节码是:
1 2 int x = ((Integer)list.get(0 )).intValue();
擦除的是字节码上的泛型信息,LocalVariabletypeTable局部变量类型泛型表仍然保留了方法参数泛型的信息。
4 可变参数
JDK5开始加入的新特性。
1 2 3 4 5 6 7 8 9 public class Candy { 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编译器会在编译期间将上述代码变换为:
1 2 3 4 5 6 7 8 9 public class Candy { public static void foo (String[] args) { String[] array = args; System.out.println(array); } public static void main (Stringp[] args) { foo(new String[]{"hello" ,"world" }); } }
如果调用了foo()则等价代码为foo(new String[]{}),创建了一个空的数组,而不会传递null进去。
5 foreach循环
1 2 3 4 5 6 7 8 public class Candy { public static void main (String[] args) { int [] array = {1 , 2 , 3 , 4 , 5 }; for (int e : array){ System.out.println(e); } } }
会被编译器转换为:
1 2 3 4 5 6 7 8 9 10 11 public class Candy { public Candy () { } 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); } } }
集合的循环,
1 2 3 4 int [] array = {1 , 2 , 3 , 4 , 5 };for (int i : array){ System.out.println(i); }
实际被编译器转化为对迭代器的引用:
1 2 3 4 5 6 7 8 9 10 11 public class Candy { public Candy () {} public static void main (String[] args) { List<Integer> list = Array.asList(1 , 2 , 3 , 4 , 5 ); Iterator iter = list.iterator(); while (iter.hasNext()){ Integer e = (Integer) iter.next(); System.out.println(e); } } }
6 switch字符串
switch可以作用于字符串和枚举类,但变量不能为null。
1 2 3 4 5 6 7 8 9 10 switch (str){ case "hello" :{ System.out.println("h" ); break ; } case "world" :{ System.out.println("w" ); break ; } }
会被编译器转换为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 switch (str.hashCode()){ case 99162322 : if (str.equals("hello" )){ x = 0 ; } break ; case 113318802 : if (str.equals("world" )){ x = 1 ; } } switch (x){ case 0 : System.out.println("h" ); break ; case 1 : System.out.println("w" ); }
hashcode相同的情况:
1 2 3 4 5 6 7 8 9 10 switch (str){ case "BM" :{ System.out.println("h" ); break ; } case "C." :{ System.out.println("w" ); break ; } }
将被编译器转换为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 switch (str.hashCode()){ case 2123 : 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" ); } }
7 switch枚举
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 enum Sex{ MALE, FEMALE } public class Candy { public static void foo (Sex sex) { switch (sex){ case MALE: System.out.println(“男”); break ; case FEMALE: System.out.println(“女”); break ; } } }
转换后代码:
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 public class Candy { static class $MAP { 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 ; } } }
8 枚举类
1 2 3 enum Sex{ MALE, FEMALE }
转换后代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public final class Sex extends Enum <Sex > { public static final Sex MALE; public static final Sex FEMALE; public static final Sex[] $VALUES; static { MALE = new Sex("MALE" , 0 ); FEMALE = new Sex("FEMALE" , 1 ); $VALUES = new Sex[]{MALE, FEMALE}; } 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 ) ; } }
9 try-with-resources
1 2 3 try (资源变量 = 创建资源对象){} catch (){ }
其中资源对象需要实现AutoCloseable接口,例如InputSteam、OutputStream、Connection、Statement、ResultSet等接口都实现了AutoCloseable,使用try-with-resources可以不用写finally语句块,编译器会帮助生成关闭资源代码,例如:
1 2 3 4 5 6 7 8 9 public class Candy { public static void main (String[] args) { try (InputStream is = new FileInputStream("d:\\q.txt" )){ System.out.println(is); } catch (IOException e){ e.printStackTrace(); } } }
会被转换为:
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 29 30 31 32 33 34 35 public class Candy { public Candy () { } public static void main (String[] args) { try { InputStream is = new FileInputStream("d:\\q.txt" ); Throwable t = null ; try { System.out.println(is); } catch (Throwable e1){ t = e1; throw e1; } finally { if (is != null ){ if (t != null ){ try { is.close(); } catch (Throwable e2){ t.addSuppressed(e2); } } else { is.close(); } } } } catch (IOException e){ e.printStackTrace(); } } }
10 方法时候的桥接方法
方法重写时候对返回值分两种情况:
父子类的返回值完全一致
子类返回值可以是父类返回值的子类
1 2 3 4 5 6 7 8 9 10 11 12 class A { public Number m () { return 1 ; } } class B extends A { @Override public Integer m () { return 2 ; } }
对于子类,编译器会做如下处理:
1 2 3 4 5 6 7 8 9 10 class B extends A { public Integer m () { return 2 ; } public synthetic bridge Number m () { return m(); } }
其中桥接方法比较特殊,仅对Java虚拟机可见,并且与原来的public Integer m()没有命名冲突,可以用下面的反射代码来验证:
1 2 3 for (Method m : B.class .getDeclaredMethods ()) { System.out.println(m); }
11 匿名内部类
匿名内部类
1 2 3 4 5 6 7 8 9 10 public class Candy { public static void main (String[] args) { Runnable runnable = new Runnable(){ @Override public void run () { System.out.println("OK" ); } }; } }
转换后代码:
1 2 3 4 5 6 7 8 final class Candy $1 implements Runnable { Candy$1 (){ } public void run () { System.out.println("OK" ); } }
1 2 3 4 5 public class Candy { public static void main (String[] args) { Runnable runnable = new Candy$1 (); } }
引用局部变量的匿名内部类:
1 2 3 4 5 6 7 8 9 10 public class Candy { public static void test (final int x) { Runnable runnable = new Runnable(){ @Override public void run () { System.out.println("OK" + x); } }; } }
转换后代码:
1 2 3 4 5 6 7 8 9 10 final class Candy $1 implements Runnable { int val$x; Candy$1 (int x){ this .val$x = x; } public void run () { System.out.println("OK" + this .val$x); } }
这同时解释了为什么匿名内部类引用局部变量时,局部变量必须是final的:因为在创建Candy$1对象时,将x的值赋值给了Candy$q对象的val$x属性,所以x不应该再发生变化了,如果变化,那么val$x属性没有机会再跟着一起变化。