不知道有多少人针对于java语法是死记硬背的,读完这一篇,让你真正的去理解每一个语法,理解完之后会发现你的技术层次又提高了一个等级。
文章当中泛型、自动装箱拆箱、字符串相加 这三个相对来说写的内容比较多,原因是我写这篇文章的时候,不仅仅想的是 要知道语言背后的转换,而且还想要知道为什么这么做。其次文章当中很多内容都是面试经常会问的。
本篇当中一共记录了十五个Java语法糖,相对来说比较全了,欢迎大家点赞收藏!
目录
一、语法糖是什么?
语法糖(Syntactic Sugar),也称糖衣语法,指在计算机语言中添加的某种语法,这种语法对语言本身的功能来说没有什么影响,只是为了方便程序员进行开发,提高开发效率,使用这种语法写出来的程序可读性也更高。说白了,语法糖就是对现有语法的一个封装。
但其实,Java虚拟机是并不支持语法糖的,语法糖在程序编译阶段就会被还原成简单的基础语法结构,这个过程就是解语法糖。所以在Java中真正支持语法糖的是Java编译器。
在了解语法糖前,先了解一下Java编译期。Java当中分为以下三种编译:
前端编译器
(叫“编译器的前端”更准确一些)把*.java文件转变成*.class文件的过程
,典型的有JDK的Javac
,Javac编译器是一个由Java语言编写的程序。ide当中target目录就是生成的字节码文件;而我们经常有时候代码没生效然后会使用mvn clean install,这个命令主要作用就是清理字节码文件并重新生成字节码文件然后进行打包。- Java虚拟机的
即时编译器
(常称JIT编译器,Just In Time Compiler)运行期把字节码转变成本地机器 码的过程
,典型的有HotSpot虚拟机的C1、C2编译器,Graal编译器。 - 还可能是指使用静态的提前编译器(常称AOT编译器,Ahead Of Time Compiler)直接
把程序编译成与目标机器指令集相关的二进制代码
的过程,典型的有JDK的Jaotc(一般我们用不到这个,了解即可,真正一直用到的是上面两个)。
很多新生的Java语法特性,都是靠Javac编译器的“语法糖”来实现
,而不 是依赖字节码或者Java虚拟机的底层改进来支持。包括我们所用到的泛型、自动装箱、Lamda等等,实际上都是由前端编译器
在将.java
文件转换成.class
文件的过程 将泛型以及自动装箱等等进行了转换。而.class文件才是在虚拟机真正运行的文件。
Java虚拟机设计团队选择把对性能的优化全部集中到运 行期的即时编译器中
,我们可以这样认为,Java中即时编译器
在运行期的优 化过程,支撑了程序执行效率的不断提升
;而前端编译器
在编译期的优化过程,则是支撑着程序员的 编码效率和语言使用者的幸福感的提高
。
二、jad反编译工具
我们想要真正了解语法糖,离不开反编译,反编译指的是将class文件进行编译成我们可以看懂的文件
。因为正常.class文件我们是看不懂的,全是特殊符号。这个没试过的可以将class文件放到记事本看一下。
这里我用的jad工具对class文件进行的反编译。jad可以通过将class文件进行反编译成jad文件,为什么要用jad?虽然ider也可以打开class文件,但是ider反编译过后的class有很多地方并没有保留.class文件原样,并不是专用的反编译工具。而用jad反编译生成的jad文件,可以保留原样,基本上不会做什么修改。
反编译只是为了让我们能看懂class文件,了解class的目的就是为了看看.java文件在编译成.class文件之后发生了哪些改变。
jad工具用法:https://blog.youkuaiyun.com/weixin_43888891/article/details/122977886
ider当中是能打开jad文件的,绿记事本也是可以打开jad文件的。
三、变长参数
变长参数也是一个比较小众的用法,所谓变长参数,就是方法可以接受长度不定确定的参数。一般我们开发不会使用到变长参数,而且变长参数也不推荐使用,它会使我们的程序变的难以处理。但是我们有必要了解一下变长参数的特性。
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");
}
}
首先通过javac命令生成class文件,然后通过jad工具进行反编译,生成jad文件:
通过上面示例发现编译后有很多地方发生了变化:
- 可变长参数变成了数组
"str = " + str
字符串拼接转换成了StringBuilder进行拼接- 增强for循环变成了原始的循环
可变长参数就是在编译的时候转换成了数组,使用变长参数有两个条件,一是变长的那一部分参数具有相同的类型,二是变长参数必须位于方法参数列表的最后面。
四、枚举
我们在日常开发中经常会使用到 enum 和 public static final … 这类语法。那么什么时候用 enum 或者是 public static final 这类常量呢?好像都可以。
在 Java 字节码结构中,并没有枚举类型。枚举只是一个语法糖,在编译完成后就会被编译成一个普通的类,也是用 Class 修饰。这个类继承于 java.lang.Enum,并被 final 关键字修饰。
我们举个例子来看一下:
一般我们使用枚举都是这样来用的,来记录一些状态等等。
public enum StatusEnum {
NO_COMMIT(0, "未提交"),
COMMIT(2, "已提交"),
;
private int code;
private String desc;
StatusEnum(int code, String desc) {
this.code = code;
this.desc = desc;
}
// get、set方法省略...
}
首先通过javac命令生成class文件,然后通过jad工具进行反编译,生成jad文件:
反编译过后如下:
package com.gzl.cn;
public final class StatusEnum extends Enum
{
public static StatusEnum[] values()
{
return (StatusEnum[])$VALUES.clone();
}
public static StatusEnum valueOf(String s)
{
return (StatusEnum)Enum.valueOf(com/gzl/cn/StatusEnum, s);
}
private StatusEnum(String s, int i, int j, String s1)
{
super(s, i);
code = j;
desc = s1;
}
public static final StatusEnum NO_COMMIT;
public static final StatusEnum COMMIT;
private int code;
private String desc;
private static final StatusEnum $VALUES[];
static
{
NO_COMMIT = new StatusEnum("NO_COMMIT", 0, 0, "未提交");
COMMIT = new StatusEnum("COMMIT", 1, 2, "已提交");
$VALUES = (new StatusEnum[] {
NO_COMMIT, COMMIT
});
}
}
从反编译结果我们可以看到,枚举其实就是一个继承于 java.lang.Enum 类的 class 。而里面的属性 NO_COMMIT 和 COMMIT 本质也就是 public static final 修饰的对象实例。
其次他的内部就是依靠static静态块 来对NO_COMMIT 和 COMMIT 赋值的,那也就是意味着只要类初始化,那两个静态对象就会实例化,于是我们就可以使用对象了。什么时候会类初始化?第一次使用NO_COMMIT或 COMMIT的时候,就会类初始化。
除此之外还有一个StatusEnum数组,这个数组在static静态块中,将内存指向了NO_COMMIT 和 COMMIT。
编译器还会为我们生成两个方法,values() 方法和 valueOf 方法,这两个方法都是编译器为我们添加的方法,通过使用 values() 方法可以获取所有的 Enum 属性值,说白了就是获取的那个数组。而通过 valueOf 方法用于获取单个的属性值。
五、泛型
泛型的本质是参数化类型
(Parameterized Type)或者参数化多态
(Parametric Polymorphism)的 应用。能够用在类、接口 和方法的创建中,分别构成泛型类、泛型接口和泛型方法。
Java的泛型直到今天依然作为Java 语言不如C#语言好用的“铁证”被众人嘲讽,Java选择这样的泛型实现,是出于当时语言现状的权衡,而不是语言先进性或者设计者水 平不如C#之类的原因。
5.1、Java与C#的泛型区别
Java选择的泛型实现方式叫作“类型擦除式泛型
”,而C#选择的泛型实现 方式是“具现化式泛型
”。
C#里面泛型无论在程序源码里面、编译后的中间语言表示里面,抑或是运行期的CLR里面都是切实存在的,List<int>与 List<string>就是两个不同的类型
,它们由系统在运行期生成,有着自己独立的虚方法表和类型数据。
Java
语言中的泛型则不同,它只在程序源码中存在,在编译后的字节码文件中,全部泛型都被替换 为原来的裸类型了
。并且在相应的地方插入了强制 转型代码,因此对于运行期的Java语言来说,ArrayList<int>与ArrayList<String>其实是同一个类型
。由 此读者可以想象“类型擦除
”这个名字的含义和来源。
如果读者是一名C#开发人员,可能很难想象下面中的Java 代码都是不合法的。