语法糖是一种方便程序员使用的语法,可以让程序更加简介,有更高的可读性,在编译阶段会被还原成简单的基础语法,这个过程就是解语法糖。java语言中给我们提供了一些语法糖使用,比如泛型、自动拆装箱、增强for循环、switch支持String与枚举等等。
package com.linyf.demo.sugar;
import java.util.Arrays;
import java.util.List;
public class ForEachDemo {
public static void main(String[] args) {
String[] strings = {"zhangsan", "lisi", "wangwu"};
for (String string : strings) {
System.out.println(string);
}
List<String> list = Arrays.asList("zhangsan", "lisi", "wangwu");
for (String s : list) {
System.out.println(s);
}
}
}
反编译后:
package com.linyf.demo.sugar;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
public class ForEachDemo {
public ForEachDemo() {
}
public static void main(String[] args) {
String[] strings = new String[]{"zhangsan", "lisi", "wangwu"};
String[] var2 = strings;
int var3 = strings.length;
for(int var4 = 0; var4 < var3; ++var4) {
String string = var2[var4];
System.out.println(string);
}
List<String> list = Arrays.asList("zhangsan", "lisi", "wangwu");
Iterator var7 = list.iterator();
while(var7.hasNext()) {
String s = (String)var7.next();
System.out.println(s);
}
}
}
可以看到数组的增强for循环其实是普通for循环,而集合是使用迭代器。
从Java 7开始,switch开始支持String。之前switch支持基本类型,比如int、char等。对于int类型,直接比较数值;对于char类型,比较ascII码。
对于编译器来说,switch中其实只能用整型,任何类型的比较都会转换成整型,比如byte,short、char(ascII码是整型)。
package com.linyf.demo.sugar;
public class SwitchDemo {
public static void main(String[] args) {
String str = "zhangsan";
switch (str) {
case "zhangsan":
System.out.println("zhangsan");
break;
case "lisi":
System.out.println("lisi");
break;
default:
break;
}
}
}
反编译后:
package com.linyf.demo.sugar;
public class SwitchDemo {
public SwitchDemo() {
}
public static void main(String[] args) {
String str = "zhangsan";
byte var3 = -1;
switch(str.hashCode()) {
case -1432604556:
if (str.equals("zhangsan")) {
var3 = 0;
}
break;
case 3322003:
if (str.equals("lisi")) {
var3 = 1;
}
}
switch(var3) {
case 0:
System.out.println("zhangsan");
break;
case 1:
System.out.println("lisi");
}
}
}
可以看到,字符串的switch是先通过两次switch来实现的,其中第一次switch后返回一个数值,再次进行switch,这样主要是为了哈希碰撞。
对于虚拟机来说,根本不认识Map
类型擦除的主要过程:
1、将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。
2、移除所有的类型参数。
package com.linyf.demo.sugar;
import java.util.HashMap;
import java.util.Map;
public class TDemo {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("a", "张三");
map.put("b", "李四");
}
}
解语法糖后:
package com.linyf.demo.sugar;
import java.util.HashMap;
import java.util.Map;
public class TDemo {
public static void main(String[] args) {
Map map = new HashMap<>();
map.put("a", "张三");
map.put("b", "李四");
}
}
虚拟机中没有泛型,只有普通类和普通方法,所有泛型类的类型参数在编译时都会被擦除,泛型类并没有自己的Class类对象。
八种基本数据类型分别有对应的包装类型:byte、short、char、int、long、float、double和Boolean。自动装箱就是Java自动将原始类型转换为对应的包装对象,比如int类型的变量转换为Integer对象。自动拆箱相反。
package com.linyf.demo.sugar;
public class BoxDemo {
public static void main(String[] args) {
int a = 10;
Integer b = a;
Integer c = 10;
int d = c;
}
}
反编译后:
package com.linyf.demo.sugar;
public class BoxDemo {
public BoxDemo() {
}
public static void main(String[] args) {
int a = 10;
Integer b = Integer.valueOf(a);
Integer c = Integer.valueOf(10);
int d = c.intValue();
}
}
可以看出,装箱的时候调用的是valueOf()方法;拆箱的时候调用的intValue()方法。
所以,装箱过程是通过调用包装类的valueOf()方法实现的,拆箱过程是通过调用包装类的xxxValue()方法实现的。
可变参数是在Java1.5中引入的一个特性。它允许一个方法把任意数量的值作为参数。
package com.linyf.demo.sugar;
public class VariableArguments {
public static void main(String[] args)
{
aaa("zhangsan", "lisi");
}
public static void aaa(String... strs)
{
for (int i = 0; i < strs.length; i++)
{
System.out.println(strs[i]);
}
}
}
反编译后代码:
package com.linyf.demo.sugar;
public class VariableArguments {
public VariableArguments() {
}
public static void main(String[] args) {
aaa("zhangsan", "lisi");
}
public static void aaa(String strs[]) {
for(int i = 0; i < strs.length; ++i) {
System.out.println(strs[i]);
}
}
}
可以看出,可变参数在被使用的时候,首先会创建一个数组,数组的长度就是调用该方法时传递的参数的个数,然后再把所有参数放到这个数组中,最后把这个数组作为参数传到被调用的方法中。
Java1.5提供了一种新的类型-枚举,关键字enum可以将一组具名的值得有限集合创建为一种新的类型。enum和class一样,只是一个关键字。
public enum EnumDemo {
SPRING,SUMMER;
}
反编译后:
可以看到:public final class EnumDemo extends Enum,说明了enum声明的是一个不能被继承的并且继承了Enum的特殊类。
内部类只是一个编译时的概念,可以看做是外部类的一个普通成员。
一旦编译成功,会生成两个.class文件,分别是outer.class和outer$inner.class。所以内部类的名字可以和它外部类的名字一样。
一般情况下,程序中的每一行代码都要参加编译,但有时候出于对程序优化的考虑,希望对其中一部分进行编译,此时需要在程序中加上条件,让编译器只对满足条件的代码进行编译,这就是条件编译。
package com.linyf.demo.sugar;
public class ConditionCompile {
public static void main(String[] args) {
final boolean aaa= true;
if(aaa) {
System.out.println("aaa!");
}
final boolean bbb= false;
if(bbb){
System.out.println("bbb!");
}
}
}
反编译后:
package com.linyf.demo.sugar;
public class ConditionCompile {
public ConditionCompile() {
}
public static void main(String[] args) {
boolean aaa = true;
System.out.println("aaa!");
boolean bbb = false;
}
}
首先,反编译后代码中没有System.out.println(“bbb!”);,这其实就是条件编译。当bbb为false的时候,编译器就没有对里面的代码进行编译。
Java的条件编译,是通过判断条件为常量的if语句实现的。编译器根据if判断真假后,直接把false的分支代码消除。通过这种方式实现的条件编译,必须在方法体内实现,不能在整个Java类结构或者类属性上进行条件编译,所以Java的条件编译其实没什么卵用。。。
assert关键字是在Java4中引入的,为了避免和老版本的Java代码中使用了assert关键字导致错误,Java在执行的时候默认是不启动断言检查。
其实断言的底层就是if,如果断言为true,程序继续执行,如果为false,程序就会抛出AssertError。
在Java7中引入了数值字面量,无论是整数还是浮点数,都允许在数字间插入多个下划线,不会对数值产生影响,目的是方便阅读。
package com.linyf.demo.sugar;
public class MathDemo {
public static void main(String... args) {
int i = 10_000;
System.out.println(i);
}
}
反编译后:
package com.linyf.demo.sugar;
public class MathDemo {
public MathDemo() {
}
public static void main(String... args) {
int i = 10000;
System.out.println(i);
}
}
可以看到,反编译后把_删除了。说明编译器并不认识在数值字面量中的下划线,需要在编译阶段去掉
从Java7开始,Java提供了一种更好的方式关闭资源,使用try-with-resources语句。
1.7之前:
private static void aaa7(){
BufferedReader br = null;
try {
String line;
br = new BufferedReader(new FileReader("d:\\aaa.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
}
}
}
1.7开始:
private static void bbb7(){
try (BufferedReader br = new BufferedReader(new FileReader("d:\\aaa.xml"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
// handle exception
}
}
可以省去很多代码。
对上面代码反编译后:
private static void bbb7() {
try {
BufferedReader br = new BufferedReader(new FileReader("d:\\aaa.xml"));
Throwable var1 = null;
try {
String line;
try {
while((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (Throwable var11) {
var1 = var11;
throw var11;
}
} finally {
if (br != null) {
if (var1 != null) {
try {
br.close();
} catch (Throwable var10) {
var1.addSuppressed(var10);
}
} else {
br.close();
}
}
}
} catch (IOException var13) {
}
}
其实就是编译器帮我们做了关闭资源的操作。
Lambda表达式不是匿名内部类的语法糖,包含lambda表达式的类编译后只有一个文件,他也是一个语法糖。实现方式其实是依赖了JVM底层提供的lambda相关的api。
lambda表达式的实现其实是依赖了一些JVM底层的api,在编译阶段,编译器会把lambda表达式进行解糖,转换成调用底层api的方式。
泛型的类型参数不能用在Java异常处理的catch语句中。因为异常处理是由JVM在运行时进行的。由于类型信息被擦除,JVM无法区分两个异常类型MyException和MyException。
package com.linyf.demo.sugar;
public class T_StaticDemo {
public static void main(String[] args){
GT<Integer> gti = new GT<Integer>();
gti.var=1;
GT<String> gts = new GT<String>();
gts.var=2;
System.out.println(gti.var);
}
}
class GT<T>{
public static int var=0;
public void nothing(T x){}
}
上面这段代码输入为:2。由于类型擦除,所以泛型类实例都关联到同一份字节码上,泛型类的所有静态变量是共享的。
Integer源码:
整型对象通过使用相同的对象引用实现了缓存和重用。
可以通过JVM参数进行调整。适用-128—127.只适用于自动装箱,适用构造函数创建对象不适用。
会抛出ConcurrentModificationException异常。因为底层是迭代器实现的,迭代器在工作的时候不允许被迭代的对象被改变。可以使用迭代器本身的remove()方法删除对象。