语法糖。
语法糖,就是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属性没有机会再跟着一起变化。