我是靠谱客的博主 大力香烟,这篇文章主要介绍【Java进阶笔记】编译期处理(java语法糖)1. 语法糖,现在分享给大家,希望可以做个参考。

文章目录

  • 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
3
public class Test { }

编译成 class 后的代码:

复制代码
1
2
3
4
5
6
7
8
public class Test { // 这个无参构造是编译器帮助我们加上的 public Test() { // 即调用父类Object的无参构造方法,即调用java/lang/0bject. "<init>":()V super(); } }

1.2. 自动拆装箱

包装类型和基本类型,在 JDK5 之后能自动转换。

复制代码
1
2
3
4
5
6
7
public class Test { public static void main(String[] args) { Integer x = 1; int y = x; } }

编译成 class 后的代码:

复制代码
1
2
3
4
5
6
7
8
public 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
10
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); } }

所以在取值时,编译器真正生成的字节码中,还要额外做一个类型转换的操作:

复制代码
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
3
public Set<Integer> test(List<String> list, Map<Integer, Object> map) { }
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
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] - %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
13
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 循环

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
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); } } }

编译后:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 语句可以作用于字符串和枚举类。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public 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
24
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 语句,第一遍是根据字符串的 hashCodeequals 将字符串的转换为相应 byte 类型,第二遍才是利用 byte 执行进行比较。

hashCode 是为了提高效率,减少可能的比较;而 equals 为了防止 hashCode 冲突。

例如 BMC. 这两个字符串的 hashCode 值都是 2123。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public 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
21
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 枚举

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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; } } } }

编译后:

复制代码
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 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
4
public 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
4
try(资源变量 = 创建资源对象){ } catch() { }

其中资源对象需要实现 AutoCloseable 接口,例如 InputstreamOutputStreamConnectionStatementResultSet 等接口都实现了 AutoCloseable,使用 try-with-resources 可以不用写 finally 语句块,编译器会帮助生成关闭资源代码,例如:

复制代码
1
2
3
4
5
6
7
8
9
10
public 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
25
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. 方法重写时的桥接方法

方法重写时,对返回值分两种情况:

  • 子类返回值和父类返回值完全一致。
  • 子类返回值是父类返回值的子类。
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
class 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
11
class 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
18
public 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.classnew Runnable() {} 的内容:

复制代码
1
2
3
4
5
6
7
8
9
10
11
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.classnew Object() {} 的内容:

复制代码
1
2
3
4
5
6
7
8
9
10
import com.company.Test; class Test$2 { // 持有外部类的引用 final Test this$0; // 外部类的引用通过构造方法传入 Test$1(Test this$0) { this.this$0 = this$0; } }

Test$3.classstatic final Runnable runnable2 = new Runnable() {} 的内容:

复制代码
1
2
3
4
class Test$3 implements Runnable { public void run() {} }

前两种写法,内部类会持有外部类的引用。如果内部类的生命周期比外部类长,则外部类容易导致内存泄漏。

最后

以上就是大力香烟最近收集整理的关于【Java进阶笔记】编译期处理(java语法糖)1. 语法糖的全部内容,更多相关【Java进阶笔记】编译期处理(java语法糖)1.内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(64)

评论列表共有 0 条评论

立即
投稿
返回
顶部