语法糖

所谓的 语法糖 ,其实就是指 java 编译器把 *.java 源码编译为 *.class 字节码的过程中,自动生成和转换的一些代码,主要是为了减轻程序员的负担.
注意,以下代码的分析,借助了 javap 工具,idea 的反编译功能,idea 插件 jclasslib 等工具。另外,编译器转换的结果直接就是 class 字节码,只是为了便于阅读,给出了 几乎等价 的 java 源码方式,并不是编译器还会转换出中间的 java 源码,切记。

1 默认构造器

编译成class后的代码:

public class Candy1 { }

编译成class后的代码:

public class Candy1 { // 这个无参构造是编译器帮助我们加上的 public Candy1() { super(); // 即调用父类 Object 的无参构造方法,即调用 java/lang/Object." <init>":()V } }

2 自动拆装箱

这个特性是 JDK 5 开始加入的,代码片段1 :

public class Candy2 { public static void main(String[] args) { Integer x = 1; int y = x; } }

这段代码在 JDK 5 之前是无法编译通过的,必须改写为 代码片段2 :

public class Candy2 { public static void main(String[] args) { Integer x = Integer.valueOf(1); int y = x.intValue(); } }

显然之前版本的代码太麻烦了,需要在基本类型和包装类型之间来回转换(尤其是集合类中操作的都是包装类型),因此这些转换的事情在 JDK 5 以后都由编译器在编译阶段完成。即 代码片段1 都会在编译阶段被转换为 代码片段2

3 泛型集合取值

泛型也是在 JDK 5 开始加入的特性,但 java 在编译泛型代码后会执行 泛型擦除 的动作,即泛型信息
在编译为字节码之后就丢失了,实际的类型都当做了 Object 类型来处理:

public class Candy3 { public static void main(String[] args) { List<Integer> list = new ArrayList<>(); list.add(10); // 实际调用的是 List.add(Object e) Integer x = list.get(0); // 实际调用的是 Object obj = List.get(int index); } }

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

// 需要将 Object 转为 Integer Integer x = (Integer)list.get(0);

如果前面的 x 变量类型修改为 int 基本类型那么最终生成的字节码是:

// 需要将 Object 转为 Integer, 并执行拆箱操作 int x = ((Integer)list.get(0)).intValue();

还好这些麻烦事都不用自己做。

擦除的是字节码上的泛型信息,可以看到 LocalVariableTypeTable 仍然保留了方法参数泛型的信息

4 可变参数

可变参数也是 JDK 5 开始加入的新特性: 例如:

public class Candy4 { 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 编译器会在编译期间将上述代码变换为:

public class Candy4 { public static void foo(String[] args) { String[] array = args; // 直接赋值 System.out.println(array); } public static void main(String[] args) { foo(new String[]{"hello", "world"}); } }
  • 注意 如果调用了 foo() 则等价代码为 foo(new String[]{}) ,创建了一个空的数组,而不会
    传递 null 进去

5 foreach 循环

仍是 JDK 5 开始引入的语法糖,数组的循环:

public class Candy5_1 { public static void main(String[] args) { int[] array = {1, 2, 3, 4, 5}; // 数组赋初值的简化写法也是语法糖哦 for (int e : array) { System.out.println(e); } } }
public class Candy5_1 { public Candy5_1() { } 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); } } }

而集合的循环:

public class Candy5_2 { public static void main(String[] args) { List<Integer> list = Arrays.asList(1,2,3,4,5); for (Integer i : list) { System.out.println(i); } } }

实际被编译器转换为对迭代器的调用:

public class Candy5_2 { public Candy5_2() { } public static void main(String[] args) { 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); } } }
  • 注意 foreach 循环写法,能够配合数组,以及所有实现了 Iterable 接口的集合类一起使用,其
    中 Iterable 用来获取集合的迭代器(Iterator )

6 枚举类

JDK 7 新增了枚举类,以前面的性别枚举为例:

转换后代码:

enum Sex { MALE, FEMALE }
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}; } /** * Sole constructor. Programmers cannot invoke this constructor. * It is for use by code emitted by the compiler in response to * enum type declarations. * used to declare it. * @param ordinal - The ordinal of this enumeration constant (its position * in the enum declaration, where the initial constant is assigned */ 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); } }

7 匿名内部类

源代码:

public class Candy11 { public static void main(String[] args) { Runnable runnable = new Runnable() { @Override public void run() { System.out.println("ok"); } }; } }

转换后的代码:

// 额外生成的类 final class Candy11$1 implements Runnable { Candy11$1() { } public void run() { System.out.println("ok"); } }
public class Candy11 { public static void main(String[] args) { Runnable runnable = new Candy11$1(); } }

引用局部变量的匿名内部类,源代码:

public class Candy11 { public static void test(final int x) { Runnable runnable = new Runnable() { @Override public void run() { System.out.println("ok:" + x); } }; } } // 额外生成的类 final class Candy11$1 implements Runnable { int val$x; Candy11$1(int x) { this.val$x = x; } public void run() { System.out.println("ok:" + this.val$x); } }
  • 注意 这同时解释了为什么匿名内部类引用局部变量时,局部变量必须是 final 的:因为在创建Candy11$1 对象时,将 x 的值赋值给了 Candy11$1 对象的 val属性, 不应该再发生变 了,如果变 ,那么 x 属性没有机会再跟着一起变化

__EOF__

本文作者飞飞很要强
本文链接https://www.cnblogs.com/LiPengFeiii/p/15213248.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   飞飞很要强  阅读(278)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示