Java语法糖
指的是:在计算机 语言中添加的某种语法,这种语法对语言功能没有影响,只是方便使用,看起来更简洁,可读性更好
Java常见的语法糖
注意:Java虚拟机不支持语法糖,这些语法糖在编译阶段会被还原成简单的基础语法结构,这个过程叫解语法糖
javac命令将.java文件编译成.class文件,然后.class文件可以运行在Java虚拟机中,它的底层源码除了compile()外还有一个步骤就是调用desugar(),这个方法就是解语法糖的实现
Java比较常用的语法糖有:泛型、变长参数、条件编译、自动拆装箱、内部类等
switch支持String和枚举
Java中switch开始支持String
通过反编译可知,字符串的switch是通过equals()和hashCode()实现的,而且还需要注意,避免哈希碰撞带来的错误
泛型
一个编译器处理泛型有两种方式:
Code speccialization:C++/C#使用这个机制Code sharing:Java使用这个机制
对于Java虚拟机他看不懂Map<String, String> map这样的语法,需要在编译阶段通过类型擦除的方式进行解语法糖
类型擦除的过程:
- 将所有泛型参数用其最左边界(最顶级父类型)类型替换
- 移除所有的类型参数
虚拟机中没有泛型,只有普通类和普通方法,所有泛型类的类型参数在编译时都会被擦除,泛型并没有自己独立的Class类对象,比如并不存在List<String>.class或List<Integer>.class,而只有List.class
自动装箱和自动拆箱
自动装箱: Java将原始类型值转换成对应的对象,也可以认为是基本数据类型转换成引用数据类型,例如int类型的变量转换成Integer对象
**自动拆箱:**将Integer对象转换成int类型值
原始类型:byte、short、char、int、long、float、double、boolean
对应的封装类:Byte、Short、Character、Integer、Long、Folat、Double、Boolean
代码示例
java
public static void main(String[] agrs){
int i = 10;//基本类型/原始类型
Integer n = i;//自动装箱
}
自动装箱底层调用的是:
Integer n = Integer.valueOf(i);
java
public static void main(String[] args){
Integer i = 10;//先定义一个原始类型对应的对象
int n = i;//自动拆箱
}
自动拆箱底层调用的是:
int n = i.intValue();
可变长参数
variable arguments是在Java1.5中引入的一个特性,他允许一个方法把任何数量的值作为参数
代码实现
java
public static void main(String[] args) {
print("JunLa", "CSDN:JunLa", "博客:https.github.JunLa.io");
}
//print接收可变长参数
public static transient print(String... strs) {
for(int i = 0; i < strs.length; i++){
System.out.println(strs[i]);
}
}
可变长参数底层本质上就是:首先创建一个数组,然后数组的长度就是调用该方法时传递的实际参数个数,然后把参数值全部放入这个数组中,然后再将这个数组作为实际参数传递给要调用的这个方法,然后执行方法即可
枚举
关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件来使用,这是一种非常有用的功能
注意:enum是一个关键字
java
public enum t {
SPRING,SUMMER;
}
注意:Java编译器会自动将枚举名处理为合法类名,即首字母要大写,t -> T
反编译看底层:实际上是:public final class T extends Enum,T类基础了enum类,同时final关键字告诉我们,这个类不能被继承
所以当我们使用enum定义一个枚举类的时候,编译器会自动给我们创建一个final关键字修饰的、继承了enum类的类,所以枚举类型不能被继承
内部类
内部类也叫嵌套类,可以把内部类理解成是外部类的一个普通成员
内部类之所以也是语法糖,是因为他仅仅是一个编译时的概念,outer.java里定义了一个内部类inner,一旦编译成功后,就会生成两个完全不同的.class文件,分别是outer.class和outer$inner.class。所以内部类的名字完全可以和外部类名字相同
java
public class OuterClass {
private String userName;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public static void main(String[] args) {
}
class InnerClass{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
代码编译后会生成两个class文件,分别是OuterClass$InnerClass.class、OuterClass.class,当我们尝试对OuterClass.class文件进行反编译的时候,命令行会打印以下内容:Parsing OuterClass.class...Parsing inner class OuterClass$InnerClass.class... Generating OuterClass.jad,他会把两个文件都进行反编译操作,然后一起生成一个OuterClass.jad文件
- 匿名内部类、局部内部类、静态内部类也是通过桥方法来获取
private属性 - 静态内部类没有
this$0的引用 - 匿名内部类、局部内部类通过赋值使用局部变量,该变量初始化之后就不能别修改
条件编译
在一般情况下,程序中的每一行代码都要参加编译,但有时候出于对程序代码优化的考虑,希望只对其中的一部份代码进行编译,此时就需要在程序中加上条件,让编译器只对满足条件的代码进行编译,将不满足条件的代码舍弃,这就是条件编译
在C/CPP中可以通过预编译处理语句来实现条件编译,在Java中也可以实现条件编译:
java
public class ConditionalCompilation {
public static void main(String[] args) {
final boolean DEBUG = true;
if(DEBUG) {
System.out.println("Hello, DEBUG!");
}
final boolean ONLINE = false;
if(ONLINE){
System.out.println("Hello, ONLINE!");
}
}
}
通过反编译可知:代码中没有了System.out.println("Hello, ONLINE");,这就是条件编译,因为在源代码中if(ONLINE)是false,那么编译器就对其代码不进行编译
所以Java语言实现条件编译就是通过if判断来实现的,根据条件判断的真假来决定是否进行编译
断言
assert关键字就是断言,在Java程序执行的时候默认不开启断言检测,这个时候所有的断言语句都会被忽略,如果开启断言间车,则需要用开关-enableassertions或-ea来开启
java
public class AssertTest {
public static void main(String args[]) {
int a = 1;
int b = 1;
assert a == b;
System.out.println("公众号:Hollis");
assert a != b : "Hollis";
System.out.println("博客:www.hollischuang.com");
}
}
反编译之后会发现底层代码比原代码要复杂很多,所以使用assert这个语法糖之后可以节省很多带阿们,本质上断言就是使用if来实现的,如果断言结果是true什么都不做,程序继续执行,如果是false则抛出AssertError来打断程序的执行
数值字面量
不管是浮点数还是整数都允许在数字之间插入任意多个下划线,这些下划线不会对字面量的数值产生影响,就是为了方便阅读
java
public class Test {
public static void main(String... args) {
int i = 10_000;
System.out.println(i);
}
}
反编译后,就是把下划线给删除了,即编译器是不认识数字字面量的下划线的,,所以在编译阶段会把它去掉
for-each
增强for循环可以比for循环少写很多代码
java
public static void main(String... args) {
String[] strs = {"Hollis", "公众号:Hollis", "博客:www.hollischuang.com"};
for (String s : strs) {
System.out.println(s);
}
List<String> strList = ImmutableList.of("Hollis", "公众号:Hollis", "博客:www.hollischuang.com");
for (String s : strList) {
System.out.println(s);
}
}
反编译后,增强for循环的底层其实就是普通for循环实现的
java
public static transient void main(String args[])
{
String strs[] = {
"Hollis", "\u516C\u4F17\u53F7\uFF1AHollis", "\u535A\u5BA2\uFF1Awww.hollischuang.com"
};
String args1[] = strs;
int i = args1.length;
for(int j = 0; j < i; j++)
{
String s = args1[j];
System.out.println(s);
}
List strList = ImmutableList.of("Hollis", "\u516C\u4F17\u53F7\uFF1AHollis", "\u535A\u5BA2\uFF1Awww.hollischuang.com");
String s;
for(Iterator iterator = strList.iterator(); iterator.hasNext(); System.out.println(s))
s = (String)iterator.next();
}
try-with-resource
Java里,对于文件操作IO流、数据库连接等开销非常昂贵的资源,用完之后必须及时通过close方法将其关闭释放资源,否则资源会一直处于打开状态,可能会导致内存泄漏等问题
关闭资源的常用方式就是在finally块里释放,即调用close方法,比如:
java
public static void main(String[] args) {
BufferedReader br = null;
try {
String line;
br = new BufferedReader(new FileReader("d:\\hollischuang.xml"));
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
// handle exception
} finally {
try {
if (br != null) {
br.close();
}
} catch (IOException ex) {
// handle exception
}
}
}
java
public static void main(String... args) {
try (BufferedReader br = new BufferedReader(new FileReader("d:\\ hollischuang.xml"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
// handle exception
}
}
反编译后的底层也很简单,就是没有做关闭资源的操作,编译器都帮我们做了