语法糖

语法糖是一种方便程序员使用的语法,可以让程序更加简介,有更高的可读性,在编译阶段会被还原成简单的基础语法,这个过程就是解语法糖。java语言中给我们提供了一些语法糖使用,比如泛型、自动拆装箱、增强for循环、switch支持String与枚举等等。

1、for-each增强for循环

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循环,而集合是使用迭代器。

2、switch 支持 String 与枚举

从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,这样主要是为了哈希碰撞。

3、泛型

对于虚拟机来说,根本不认识Map 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类对象。

4、自动装箱和拆箱

八种基本数据类型分别有对应的包装类型: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()方法实现的。

5、方法变长参数

可变参数是在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]);
        }

    }
}

可以看出,可变参数在被使用的时候,首先会创建一个数组,数组的长度就是调用该方法时传递的参数的个数,然后再把所有参数放到这个数组中,最后把这个数组作为参数传到被调用的方法中。

6、枚举

Java1.5提供了一种新的类型-枚举,关键字enum可以将一组具名的值得有限集合创建为一种新的类型。enum和class一样,只是一个关键字。

public enum EnumDemo {
    SPRING,SUMMER;
}

反编译后:
语法糖_第1张图片
可以看到:public final class EnumDemo extends Enum,说明了enum声明的是一个不能被继承的并且继承了Enum的特殊类。

7、内部类

内部类只是一个编译时的概念,可以看做是外部类的一个普通成员。
一旦编译成功,会生成两个.class文件,分别是outer.class和outer$inner.class。所以内部类的名字可以和它外部类的名字一样。

8、条件编译

一般情况下,程序中的每一行代码都要参加编译,但有时候出于对程序优化的考虑,希望对其中一部分进行编译,此时需要在程序中加上条件,让编译器只对满足条件的代码进行编译,这就是条件编译。

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的条件编译其实没什么卵用。。。

9、断言

assert关键字是在Java4中引入的,为了避免和老版本的Java代码中使用了assert关键字导致错误,Java在执行的时候默认是不启动断言检查。
其实断言的底层就是if,如果断言为true,程序继续执行,如果为false,程序就会抛出AssertError。

10、数值字面量

在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);
    }
}

可以看到,反编译后把_删除了。说明编译器并不认识在数值字面量中的下划线,需要在编译阶段去掉

11、try-with-resource

从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) {
        }

    }

其实就是编译器帮我们做了关闭资源的操作。

12、Lambda表达式

Lambda表达式不是匿名内部类的语法糖,包含lambda表达式的类编译后只有一个文件,他也是一个语法糖。实现方式其实是依赖了JVM底层提供的lambda相关的api。
lambda表达式的实现其实是依赖了一些JVM底层的api,在编译阶段,编译器会把lambda表达式进行解糖,转换成调用底层api的方式。

1、泛型遇上重载

语法糖_第2张图片
这段代码由于类型擦除,编译通不过。

2、泛型遇上catch

泛型的类型参数不能用在Java异常处理的catch语句中。因为异常处理是由JVM在运行时进行的。由于类型信息被擦除,JVM无法区分两个异常类型MyException和MyException。

3、泛型遇到静态变量

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。由于类型擦除,所以泛型类实例都关联到同一份字节码上,泛型类的所有静态变量是共享的。

4、自动装箱和拆箱—对象相等比较

语法糖_第3张图片
语法糖_第4张图片
Integer源码:
语法糖_第5张图片
整型对象通过使用相同的对象引用实现了缓存和重用。
可以通过JVM参数进行调整。适用-128—127.只适用于自动装箱,适用构造函数创建对象不适用。

5、增强for循环

在这里插入图片描述
会抛出ConcurrentModificationException异常。因为底层是迭代器实现的,迭代器在工作的时候不允许被迭代的对象被改变。可以使用迭代器本身的remove()方法删除对象。

你可能感兴趣的:(java)