Java 中的语法糖,真甜。(一)

简介: 我们在日常开发中经常会使用到诸如泛型、自动拆箱和装箱、内部类、增强 for 循环、try-with-resources 语法、lambda 表达式等,我们只觉得用的很爽,因为这些特性能够帮助我们减轻开发工作量;但我们未曾认真研究过这些特性的本质是什么,那么这篇文章,cxuan 就来为你揭开这些特性背后的真相。

我们在日常开发中经常会使用到诸如泛型、自动拆箱和装箱、内部类、增强 for 循环、try-with-resources 语法、lambda 表达式等,我们只觉得用的很爽,因为这些特性能够帮助我们减轻开发工作量;但我们未曾认真研究过这些特性的本质是什么,那么这篇文章,cxuan 就来为你揭开这些特性背后的真相。

语法糖

在聊之前我们需要先了解一下 语法糖 的概念:语法糖(Syntactic sugar),也叫做糖衣语法,是英国科学家发明的一个术语,通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会,真是又香又甜。

语法糖指的是计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。因为 Java 代码需要运行在 JVM 中,JVM 是并不支持语法糖的,语法糖在程序编译阶段就会被还原成简单的基础语法结构,这个过程就是解语法糖。所以在 Java 中,真正支持语法糖的是 Java 编译器,真是换汤不换药,万变不离其宗,关了灯都一样。。。。。。

下面我们就来认识一下 Java 中的这些语法糖

泛型

泛型是一种语法糖。在 JDK1.5 中,引入了泛型机制,但是泛型机制的本身是通过类型擦除 来实现的,在 JVM 中没有泛型,只有普通类型和普通方法,泛型类的类型参数,在编译时都会被擦除。泛型并没有自己独特的 Class类型。如下代码所示

List<Integer> aList = new ArrayList();
List<String> bList = new ArrayList();
System.out.println(aList.getClass() == bList.getClass());

List<Ineger> 和 List<String> 被认为是不同的类型,但是输出却得到了相同的结果,这是因为,泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。但是,如果将一个 Integer 类型的数据放入到 List<String> 中或者将一个 String 类型的数据放在 List<Ineger> 中是不允许的。

如下图所示

微信图片_20220418191105.png

无法将一个 Integer 类型的数据放在 List<String> 和无法将一个 String 类型的数据放在 List<Integer> 中是一样会编译失败。

自动拆箱和自动装箱

自动拆箱和自动装箱是一种语法糖,它说的是八种基本数据类型的包装类和其基本数据类型之间的自动转换。简单的说,装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。

我们先来了解一下基本数据类型的包装类都有哪些

微信图片_20220418191109.png

也就是说,上面这些基本数据类型和包装类在进行转换的过程中会发生自动装箱/拆箱,例如下面代码

Integer integer = 66; // 自动拆箱
int i1 = integer;   // 自动装箱

上面代码中的 integer 对象会使用基本数据类型来进行赋值,而基本数据类型 i1 却把它赋值给了一个对象类型,一般情况下是不能这样操作的,但是编译器却允许我们这么做,这其实就是一种语法糖。这种语法糖使我们方便我们进行数值运算,如果没有语法糖,在进行数值运算时,你需要先将对象转换成基本数据类型,基本数据类型同时也需要转换成包装类型才能使用其内置的方法,无疑增加了代码冗余。

那么自动拆箱和自动装箱是如何实现的呢?

其实这背后的原理是编译器做了优化。将基本类型赋值给包装类其实是调用了包装类的 valueOf() 方法创建了一个包装类再赋值给了基本类型。

int i1 = Integer.valueOf(1);

而包装类赋值给基本类型就是调用了包装类的 xxxValue() 方法拿到基本数据类型后再进行赋值。

Integer i1 = new Integer(1).intValue();

我们使用 javap -c 反编译一下上面的自动装箱和自动拆箱来验证一下

微信图片_20220418191114.png

可以看到,在 Code 2 处调用 invokestatic 的时候,相当于是编译器自动为我们添加了一下 Integer.valueOf 方法从而把基本数据类型转换为了包装类型。

在 Code 7 处调用了 invokevirtual 的时候,相当于是编译器为我们添加了 Integer.intValue() 方法把 Integer 的值转换为了基本数据类型。

枚举

我们在日常开发中经常会使用到 enum 和 public static final ... 这类语法。那么什么时候用 enum 或者是 public static final 这类常量呢?好像都可以。

但是在 Java 字节码结构中,并没有枚举类型。枚举只是一个语法糖,在编译完成后就会被编译成一个普通的类,也是用 Class 修饰。这个类继承于 java.lang.Enum,并被 final 关键字修饰。

我们举个例子来看一下

public enum School {
    STUDENT,
    TEACHER;
}

这是一个 School 的枚举,里面包括两个字段,一个是 STUDENT ,一个是 TEACHER,除此之外并无其他。

下面我们使用 javap 反编译一下这个 School.class 。反编译完成之后的结果如下

微信图片_20220418191119.png

从图中我们可以看到,枚举其实就是一个继承于 java.lang.Enum 类的 class 。而里面的属性 STUDENT 和 TEACHER 本质也就是 public static final 修饰的字段。这其实也是一种编译器的优化,毕竟 STUDENT 要比 public static final School STUDENT 的美观性、简洁性都要好很多。

除此之外,编译器还会为我们生成两个方法,values() 方法和 valueOf 方法,这两个方法都是编译器为我们添加的方法,通过使用 values() 方法可以获取所有的 Enum 属性值,而通过 valueOf 方法用于获取单个的属性值。

注意,Enum 的 values() 方法不属于 JDK API 的一部分,在 Java 源码中,没有 values() 方法的相关注释。

用法如下

public enum School {
    STUDENT("Student"),
    TEACHER("Teacher");
    private String name;
    School(String name){
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public static void main(String[] args) {
        System.out.println(School.STUDENT.getName());
        School[] values = School.values();
        for(School school : values){
            System.out.println("name = "+ school.getName());
        }
    }
}

内部类

内部类是 Java 一个小众 的特性,我之所以说小众,并不是说内部类没有用,而是我们日常开发中其实很少用到,但是翻看 JDK 源码,发现很多源码中都有内部类的构造。比如常见的 ArrayList 源码中就有一个 Itr 内部类继承于 Iterator 类;再比如 HashMap 中就构造了一个 Node 继承于 Map.Entry 来表示 HashMap 的每一个节点。

Java 语言中之所以引入内部类,是因为有些时候一个类只想在一个类中有用,不想让其在其他地方被使用,也就是对外隐藏内部细节。

内部类其实也是一个语法糖,因为其只是一个编译时的概念,一旦编译完成,编译器就会为内部类生成一个单独的class 文件,名为 outer$innter.class。

下面我们就根据一个示例来验证一下。

public class OuterClass {
    private String label;
    class InnerClass {
        public String linkOuter(){
            return label = "inner";
        }
    }
    public static void main(String[] args) {
        OuterClass outerClass = new OuterClass();
        InnerClass innerClass = outerClass.new InnerClass();
        System.out.println(innerClass.linkOuter());
    }
}

上面这段编译后就会生成两个 class 文件,一个是 OuterClass.class ,一个是 OuterClass$InnerClass.class ,这就表明,外部类可以链接到内部类,内部类可以修改外部类的属性等。

我们来看一下内部类编译后的结果

微信图片_20220418191124.png

如上图所示,内部类经过编译后的 linkOuter() 方法会生成一个指向外部类的 this 引用,这个引用就是连接外部类和内部类的引用。

变长参数

变长参数也是一个比较小众的用法,所谓变长参数,就是方法可以接受长度不定确定的参数。一般我们开发不会使用到变长参数,而且变长参数也不推荐使用,它会使我们的程序变的难以处理。但是我们有必要了解一下变长参数的特性。

其基本用法如下

public class VariableArgs {
    public static void printMessage(String... args){
        for(String str : args){
            System.out.println("str = " + str);
        }
    }
    public static void main(String[] args) {
        VariableArgs.printMessage("l","am","cxuan");
    }
}

变长参数也是一种语法糖,那么它是如何实现的呢?我们可以猜测一下其内部应该是由数组构成,否则无法接受多个值,那么我们反编译看一下是不是由数组实现的。

微信图片_20220418191129.png

   可以看到,printMessage() 的参数就是使用了一个数组来接收,所以千万别被变长参数忽悠了!

变长参数特性是在 JDK 1.5 中引入的,使用变长参数有两个条件,一是变长的那一部分参数具有相同的类型,二是变长参数必须位于方法参数列表的最后面。

相关文章
|
存储 Java 容器
Java基本语法详解
本文深入讲解了Java编程的基础语法,涵盖数据类型、运算符、控制结构及数组等核心内容,帮助初学者构建坚实的编程基础。
|
Java Apache Maven
Java百项管理之新闻管理系统 熟悉java语法——大学生作业 有源码!!!可运行!!!
文章提供了使用Apache POI库在Java中创建和读取Excel文件的详细代码示例,包括写入数据到Excel和从Excel读取数据的方法。
285 6
Java百项管理之新闻管理系统 熟悉java语法——大学生作业 有源码!!!可运行!!!
|
存储 SQL NoSQL
Redis-常用语法以及java互联实践案例
本文详细介绍了Redis的数据结构、常用命令及其Java客户端的使用,涵盖String、Hash、List、Set、SortedSet等数据类型及操作,同时提供了Jedis和Spring Boot Data Redis的实战示例,帮助开发者快速掌握Redis在实际项目中的应用。
905 1
Redis-常用语法以及java互联实践案例
|
Java
Java基础语法与面向对象
重载(Overload)指同一类中方法名相同、参数列表不同,与返回值无关;重写(Override)指子类重新实现父类方法,方法名和参数列表必须相同,返回类型兼容。重载发生在同类,重写发生在继承关系中。
326 1
|
Java 开发工具 Android开发
Kotlin语法笔记(26) -Kotlin 与 Java 共存(1)
Kotlin语法笔记(26) -Kotlin 与 Java 共存(1)
317 2
|
Java 开发工具 Android开发
Kotlin语法笔记(26) -Kotlin 与 Java 共存(1)
本系列教程笔记详细讲解了Kotlin语法,适合需要深入了解Kotlin的开发者。若需快速学习Kotlin,建议查看“简洁”系列教程。本期重点介绍了Kotlin与Java的共存方式,包括属性、单例对象、默认参数方法、包方法、扩展方法以及内部类和成员的互操作性。通过这些内容,帮助你在项目中更好地结合使用这两种语言。
365 1
|
Java 数据库连接 数据库
Java 相关知识点总结含基础语法进阶技巧及面试重点知识
本文全面总结了Java核心知识点,涵盖基础语法、面向对象、集合框架、并发编程、网络编程及主流框架如Spring生态、MyBatis等,结合JVM原理与性能优化技巧,并通过一个学生信息管理系统的实战案例,帮助你快速掌握Java开发技能,适合Java学习与面试准备。
642 2
Java 相关知识点总结含基础语法进阶技巧及面试重点知识
|
算法 Java 测试技术
零基础学 Java: 从语法入门到企业级项目实战的详细学习路线解析
本文为零基础学习者提供完整的Java学习路线,涵盖语法基础、面向对象编程、数据结构与算法、多线程、JVM原理、Spring框架、Spring Boot及项目实战,助你从入门到进阶,系统掌握Java编程技能,提升实战开发能力。
923 0
|
存储 安全 Java
从基础语法到实战应用的 Java 入门必备知识全解析
本文介绍了Java入门必备知识,涵盖开发环境搭建、基础语法、面向对象编程、集合框架、异常处理、多线程和IO流等内容,结合实例帮助新手快速掌握Java核心概念与应用技巧。
376 0
|
缓存 安全 Java
java面试-基础语法与面向对象
本文介绍了 Java 编程中的几个核心概念。首先,详细区分了方法重载与重写的定义、发生阶段及规则;其次,分析了 `==` 与 `equals` 的区别,强调了基本类型和引用类型的比较方式;接着,对比了 `String`、`StringBuilder` 和 `StringBuffer` 的特性,包括线程安全性和性能差异;最后,讲解了 Java 异常机制,包括自定义异常的实现以及常见非检查异常的类型。这些内容对理解 Java 面向对象编程和实际开发问题解决具有重要意义。