1. 语法糖
语法糖,指java编译器把 .java 源码文件编译为 .class 字节码文件的过程中,自动生成和转换的一些代码,主要是为了减轻程序员的负担,算是java编译器给我们的一个额外福利(给糖吃)。
1.1. 默认构造方法
如果没有写任何构造方法,编译器会自动生成一个默认的无参构造方法
public class Test {}
编译成 class 后的代码:
public class Test {// 这个无参构造是编译器帮助我们加上的public Test() {// 即调用父类Object的无参构造方法,即调用java/lang/0bject. "<init>":()Vsuper();}}
1.2. 自动拆装箱
包装类型和基本类型,在 JDK5 之后能自动转换。
public class Test {public static void main(String[] args) {Integer x = 1;int y = x;}}
编译成 class 后的代码:
public class Test {public static void main(String[] args) {Integer x = Integer.valueOf(1); // 自动装箱int y = x.intValue(); // 自动拆箱}}
1.3. 泛型取值
JDK5 在编译泛型代码时,会执行类型擦除操作,即泛型信息在编译为字节码之后就丢失了,实际的类型都当做了 Object 类型来处理。
public class Test {public static void main(String[] args) {List<Integer> list = new ArrayList<>();// 实际调用 List.add(Object e);list.add(10);// 实际调用 Object obj = List.get(int index);Integer x = list.get(0);}}
所以在取值时,编译器真正生成的字节码中,还要额外做一个类型转换的操作:
// 需要将Object转为IntegerInteger x = (Integer)list.get(0);
如果前面的 x 变量类型修改为 int 基本类型那么最终生成的字节码是:
//需要将Object转为Integer, 并执行拆箱操作int x = ((Integer)list.get(0)).intValue();
虽然会有类型擦除,但是会在 class 文件中的 LocalVariableTypeTable 中,保存泛型信息,用来做自动类型转换。因此可以使用反射获取泛型信息。
public Set<Integer> test(List<String> list, Map<Integer, Object> map) {}
Method test = Test.class.getMethod("test", List.class, Map.class);Type[] types = test.getGenericParameterTypes();for (Type type : types) {if (type instanceof ParameterizedType) {ParameterizedType parameterizedType = (ParameterizedType) type;Ѕуѕtеm.оut.рrіntln("原始类型 - " + раrаmеtеrіzеdТуре.gеtRаwТуре());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
1.4. 可变参数
可变参数 String... args 会在编译期转换为 String[] args。
public class Test {public static void main(String[] args) {// 实际上是 test(new String[]{"Hello", "World!"});test("Hello", "World!");// 实际上是 test(new String[]{});test();}public static void test(String... args) {String[] array = args;System.out.рrіntln(array);}}
1.5. foreach 循环
public class Test {public static void main(String[] args) {int[] array = {1, 2, 3, 4, 5};for (int e : array) {System.out.println(e);}List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);for (Integer e : list) {System.out.println(e);}}}
编译后:
public class Test {public Test() {}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);}// 对于集合,则使用迭代器迭代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);}}}
1.6. switch 字符串
从 JDK7 开始,switch 语句可以作用于字符串和枚举类。
public class Test {public void test(String s) {switch (s) {case "你好": {System.out.println("你好");break;}case "世界": {System.out.println("世界");break;}}}}
编译后:
public class Test {public Test() {}public void test(String s) {byte var2 = -1;// 先计算字符串的hash值,再匹配得到一个变量值switch(s.hashCode()) {case 649718:if (s.equals("世界")) var2 = 1;break;case 652829:if (s.equals("你好")) var2 = 0;}// 再匹配变量值switch(var2) {case 0:System.out.println("你好");break;case 1:System.out.println("世界");}}}
执行了两遍 switch 语句,第一遍是根据字符串的 hashCode 和 equals 将字符串的转换为相应 byte 类型,第二遍才是利用 byte 执行进行比较。
hashCode 是为了提高效率,减少可能的比较;而 equals 为了防止 hashCode 冲突。
例如 BM 和 C. 这两个字符串的 hashCode 值都是 2123。
public class Test {public void test(String s) {switch (s) {case "BM": {System.out.println("BM");break;}case "C.": {System.out.println("C.");break;}}}}
编译后:
public class Test {public Test() {}public void test(String s) {byte var2 = -1;switch(s.hashCode()) {case 2123: // hashcode 值可能相同,需要进一步比较if (s.equals("C.")) var2 = 1;else if (s.equals("BM")) var2 = 0;default:switch(var2) {case 0:System.out.println("BM");break;case 1:System.out.println("C.");}}}}
1.7. switch 枚举
public enum Sex {MALE, FEMALE}public class Test {public void test(Sex sex) {switch (sex) {case MALE: {System.out.println("男");break;}case FEMALE: {System.out.println("女");break;}}}}
编译后:
public class Test {public Test() {}// 会生成一个合成的内部类(JVM可见,我们不可见)static class $MAP {// 数组大小即为枚举元素个数,里面存储case用来对比的数字static int[] map = new int[2];static {map[Sex.MALE.ordinal()] = 1;map[Sex.FEMALE.ordinal()] = 2;}}public void test(Sex sex) {// 从数组中取出对应的值int x = $MAP.map[sex.ordinal()];switch(x) {case 1:System.out.println("男");break;case 2:System.out.println("女");}}}
1.8. 枚举
public enum Sex {MALE, FEMALE}
编译后:
// 本质上是一个classpublic 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};}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);}}
1.9. try-with-resources
JDK 7 开始新增了对需要关闭的资源处理的特殊语法 try-with-resources:
try(资源变量 = 创建资源对象){} catch() {}
其中资源对象需要实现 AutoCloseable 接口,例如 Inputstream、OutputStream、Connection、Statement、ResultSet 等接口都实现了 AutoCloseable,使用 try-with-resources 可以不用写 finally 语句块,编译器会帮助生成关闭资源代码,例如:
public class Test {public void test() {try (InputStream is = new FileInputStream("D:\\test.txt")) {System.out.println(is);} catch (IOException e) {e.printStackTrace();}}}
编译后:
public class Test {public void test() {try {FileInputStream is = new FileInputStream("D:\\test.txt");try {System.out.println(is);} catch (Throwable e1) {// 我们自己的代码出现异常try {// 尝试关闭is.close();} catch (Throwable e2) {// close出现异常,作为被压制的异常添加e1.addSuppressed(e2);}// 抛出异常throw e1;}is.close();} catch (IOException e) {e.printStackTrace();}}}
1.10. 方法重写时的桥接方法
方法重写时,对返回值分两种情况:
- 子类返回值和父类返回值完全一致。
- 子类返回值是父类返回值的子类。
class A {public Number m() {return 1;}}class B extends A {//子类方法的返回值Integer,是父类方法返回值Number子类@Overridepublic Integer m() {return 2;}}
编译后:
class B extends A {public Integer m() {return 2;}// 此方法オ是真正重写了父类m方i,JVM可见,我们不可见public synthetic bridge Number m() {// 调用 public Integer m()return m();}}
1.11. 匿名内部类
public class Test {// 匿名内部类实现接口并重写接口方法Runnable runnable = new Runnable() {@Overridepublic void run() {}};// 匿名内部类重写类方法Object object = new Object() {};// 静态常量 + 匿名内部类实现接口并重写接口方法static final Runnable runnable2 = new Runnable() {@Overridepublic void run() {}};}
编译后会生成三个内部类 class:
Test$1.class 即 new Runnable() {} 的内容:
import com.company.Test;class Test$1 implements Runnable {// 持有外部类的引用final Test this$0;// 外部类的引用通过构造方法传入Test$1(Test this$0) {this.this$0 = this$0;}public void run() {}}
Test$2.class 即 new Object() {} 的内容:
import com.company.Test;class Test$2 {// 持有外部类的引用final Test this$0;// 外部类的引用通过构造方法传入Test$1(Test this$0) {this.this$0 = this$0;}}
Test$3.class 即 static final Runnable runnable2 = new Runnable() {} 的内容:
class Test$3 implements Runnable {public void run() {}}
前两种写法,内部类会持有外部类的引用。如果内部类的生命周期比外部类长,则外部类容易导致内存泄漏。
