文章目录
- 1. 语法糖
- 1.1. 默认构造方法
- 1.2. 自动拆装箱
- 1.3. 泛型取值
- 1.4. 可变参数
- 1.5. foreach 循环
- 1.6. switch 字符串
- 1.7. switch 枚举
- 1.8. 枚举
- 1.9. try-with-resources
- 1.10. 方法重写时的桥接方法
- 1.11. 匿名内部类
1. 语法糖
语法糖,指java编译器把 .java
源码文件编译为 .class
字节码文件的过程中,自动生成和转换的一些代码,主要是为了减轻程序员的负担,算是java编译器给我们的一个额外福利(给糖吃)。
1.1. 默认构造方法
如果没有写任何构造方法,编译器会自动生成一个默认的无参构造方法
1
2
3public class Test { }
编译成 class 后的代码:
1
2
3
4
5
6
7
8public class Test { // 这个无参构造是编译器帮助我们加上的 public Test() { // 即调用父类Object的无参构造方法,即调用java/lang/0bject. "<init>":()V super(); } }
1.2. 自动拆装箱
包装类型和基本类型,在 JDK5 之后能自动转换。
1
2
3
4
5
6
7public class Test { public static void main(String[] args) { Integer x = 1; int y = x; } }
编译成 class 后的代码:
1
2
3
4
5
6
7
8public class Test { public static void main(String[] args) { Integer x = Integer.valueOf(1); // 自动装箱 int y = x.intValue(); // 自动拆箱 } }
1.3. 泛型取值
JDK5 在编译泛型代码时,会执行类型擦除操作,即泛型信息在编译为字节码之后就丢失了,实际的类型都当做了 Object
类型来处理。
1
2
3
4
5
6
7
8
9
10public 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); } }
所以在取值时,编译器真正生成的字节码中,还要额外做一个类型转换的操作:
1
2
3// 需要将Object转为Integer Integer x = (Integer)list.get(0);
如果前面的 x
变量类型修改为 int
基本类型那么最终生成的字节码是:
1
2
3//需要将Object转为Integer, 并执行拆箱操作 int x = ((Integer)list.get(0)).intValue();
虽然会有类型擦除,但是会在 class 文件中的 LocalVariableTypeTable
中,保存泛型信息,用来做自动类型转换。因此可以使用反射获取泛型信息。
1
2
3public Set<Integer> test(List<String> list, Map<Integer, Object> map) { }
1
2
3
4
5
6
7
8
9
10
11
12
13Method 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] - %sn", 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
。
1
2
3
4
5
6
7
8
9
10
11
12
13public 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 循环
1
2
3
4
5
6
7
8
9
10
11
12
13public 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); } } }
编译后:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public 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
语句可以作用于字符串和枚举类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class Test { public void test(String s) { switch (s) { case "你好": { System.out.println("你好"); break; } case "世界": { 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
24public 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。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class Test { public void test(String s) { switch (s) { case "BM": { System.out.println("BM"); break; } case "C.": { System.out.println("C."); break; } } } }
编译后:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public 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 枚举
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public 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; } } } }
编译后:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public 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. 枚举
1
2
3
4public enum Sex { MALE, FEMALE }
编译后:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// 本质上是一个class 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}; } 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
:
1
2
3
4try(资源变量 = 创建资源对象){ } catch() { }
其中资源对象需要实现 AutoCloseable
接口,例如 Inputstream
、OutputStream
、Connection
、Statement
、ResultSet
等接口都实现了 AutoCloseable,使用 try-with-resources
可以不用写 finally
语句块,编译器会帮助生成关闭资源代码,例如:
1
2
3
4
5
6
7
8
9
10public class Test { public void test() { try (InputStream is = new FileInputStream("D:\test.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
25public 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. 方法重写时的桥接方法
方法重写时,对返回值分两种情况:
- 子类返回值和父类返回值完全一致。
- 子类返回值是父类返回值的子类。
1
2
3
4
5
6
7
8
9
10
11
12
13class A { public Number m() { return 1; } } class B extends A { //子类方法的返回值Integer,是父类方法返回值Number子类 @Override public Integer m() { return 2; } }
编译后:
1
2
3
4
5
6
7
8
9
10
11class B extends A { public Integer m() { return 2; } // 此方法オ是真正重写了父类m方i,JVM可见,我们不可见 public synthetic bridge Number m() { // 调用 public Integer m() return m(); } }
1.11. 匿名内部类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class Test { // 匿名内部类实现接口并重写接口方法 Runnable runnable = new Runnable() { @Override public void run() { } }; // 匿名内部类重写类方法 Object object = new Object() { }; // 静态常量 + 匿名内部类实现接口并重写接口方法 static final Runnable runnable2 = new Runnable() { @Override public void run() { } }; }
编译后会生成三个内部类 class:
Test$1.class
即 new Runnable() {}
的内容:
1
2
3
4
5
6
7
8
9
10
11import 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() {}
的内容:
1
2
3
4
5
6
7
8
9
10import 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() {}
的内容:
1
2
3
4class Test$3 implements Runnable { public void run() {} }
前两种写法,内部类会持有外部类的引用。如果内部类的生命周期比外部类长,则外部类容易导致内存泄漏。
最后
以上就是大力香烟最近收集整理的关于【Java进阶笔记】编译期处理(java语法糖)1. 语法糖的全部内容,更多相关【Java进阶笔记】编译期处理(java语法糖)1.内容请搜索靠谱客的其他文章。
发表评论 取消回复