Java的语法糖

  • Java的语法糖
    • 语法糖
    • 解语法糖
    • 编译与反编译
    • 泛型
    • 自动装箱/拆箱
    • ForEach循环
    • 枚举
    • switch 支持 String 与枚举
      • 字符串
      • 枚举
    • 方法变长参数
    • 条件编译
    • 数值字面量
    • java8 lambda表达式
    • try-with-resource
    • 总结

Java的语法糖_第1张图片

Java的语法糖

语法糖

语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。

解语法糖

Java中最常用的语法糖主要是泛型、变长数组、自动装箱/拆箱等等,Java虚拟机运行时不支持这些语法,它们在编译阶段还原回简单的基础语法结构,这个过程称为解语法糖(语法糖被解除)。

编译与反编译

编译

前端编译器把`*.java`文件变成`*.class`文件的过程;

后端运行期编译器(JIT编译器,Just In Time Compiler)把字节码转变成机器码的过程;

静态前端编译器(AOT编译器,Ahead Of Time Compiler)直接把 *.java文件编译成本地机器代码的过程;

本章只需要前端编译。

反编译:把*.class文件变成*.java文件的过程;

本章使用cfr_0_132.jar,下载地址cfr_0_132

泛型

泛型是JDK1.5的一项新特性,它的本质是参数化类型的应用,也就是说所有操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中。分别称为泛型类、泛型接口、泛型方法。

如:ArrayListMappublic void methodName(T t)

Java 语言中的泛型,它只在程序的源码中存在,在编译后的字节码文件中,就是已经替换为原来的原生类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转换代码,因此,对于运行期的java语言来说,ArrayListArrayList就是同一个类,所以泛型技术实际上是java语言的一颗语法糖,java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型称为伪泛型。

代码清单1是一段简单的java泛型的例子,我们可以看下代码清单2编译后的结果。

代码清单1:泛型

    private static void demo1() {
        Map map = new HashMap<>();
        map.put("hello", "你好!");
        map.put("how are you", "吃了没?");
        System.out.println(map.get("hello"));
        System.out.println(map.get("how are you"));
    }

代码清单2:反编译结果

java -jar cfr_0_132.jar class文件目录

private static void demo1() {
    HashMap map = new HashMap();
    map.put("hello", "你好!");
    map.put("how are you", "吃了没?");
    System.out.println((String)map.get("hello"));
    System.out.println((String)map.get("how are you"));
}

可以看到map.get返回类型被强转成String了。

这就可以解释非泛型与泛型间转换时为什么会出现警告,有可能在强制转换时出现ClassCastException异常,所以在这中转换时一定要小心。如:代码清单3 List 泛型与非泛型间转换。

代码清单3:

private static void demo2() {
    List list = createList();
    list.add("1");
    list.add("2");
    list.add("3");
    for (int i = 0; i < list.size(); i++) {
        String str = list.get(i);
        System.out.println(str);
    }
}

private static List createList() {
    List list = new ArrayList();
    list.add(1);
    list.add(new Date());
    return list;

}

出现ClassCastException异常:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at com.example.syntactic.sugar.Genericity.demo2(Genericity.java:24)
    at com.example.syntactic.sugar.Genericity.main(Genericity.java:15)

自动装箱/拆箱

包装类型转换成基本数据类型叫做拆箱,基本数据类型转换成包装类型叫做装箱,自java1.5以后编译器帮助我们完成装箱/拆箱叫自动装箱/拆箱。如:Integer->int基本类型是拆箱,反之int->Integer类型是装箱。

代码清单4:自动装箱/拆箱

private static void demo1() {
    Integer[] ary = new Integer[8];
    for (int i = 0; i < ary.length; i++) {
        ary[i] = i;
    }

    for (int i = 0; i < ary.length; i++) {
        int value =ary[i];
        System.out.println(value);
    }
}

反编译:自动装箱/拆箱反编译结果

java -jar cfr_0_132.jar class文件目录 –sugarboxing false

 private static void demo1() {
        int i;
        Integer[] ary = new Integer[8];
        for (i = 0; i < ary.length; ++i) {
            ary[i] = Integer.valueOf((int)i);//自动装箱
        }
        for (i = 0; i < ary.length; ++i) {
            int value = ary[i].intValue();//自动拆箱
            System.out.println((int)value);
        }
    }

反编译后我们发现自动装箱就是使用Integer.valueOf,自动拆箱Integer.intValue,如果是Long类型就是Long.valueOfLong.intValue等等。

下列代码的运行结果什么?

Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
System.out.println(c == d);
System.out.println(e == f);
System.out.println(c == (a + b));
System.out.println(c.equals(a + b));
System.out.println(g == (a + b));
System.out.println(g.equals(a + b));

ForEach循环

ForEach循环相信大家都很熟悉了,使用ForEach循环必须是数组或实现Iterable接口,最常用的就是List集合使用ForEach循环遍历元素了,那么它是如何实现的呢,我们通过反编译看看就什么都知道了。

代码清单5:List集合使用ForEach循环

//源码
private static void demo1() {
    List list = new LinkedList<>();
    list.add("a");
    list.add("b");
    list.add("c");
    for (String str : list) {
        System.out.println(str);
    }
}
//

反编译:是不是一目了然,while循环 + Iterator迭代接口,处理ForEach

java -jar cfr_0_132.jar class文件目录 –collectioniter false

    private static void demo1() {
        LinkedList list = new LinkedList();
        list.add("a");
        list.add("b");
        list.add("c");
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            String str = (String)iterator.next();
            System.out.println(str);
        }
    }

结论:只要类继承了Iterable接口就可以使用ForEach循环。

代码清单6:Integer数组使用ForEach循环

private static void demo2() {
    Integer[] array = {1,2,3,4};
    for (Integer integer : array) {
        System.out.println(integer);
    }
}

反编译:数组和List集合遍历的方式就有点不同了,使用的是标准的fori循环。

java -jar cfr_0_132.jar class文件目录 –arrayiter false

private static void demo2() {
    Integer[] array;
    Integer[] arrinteger = array = new Integer[]{1, 2, 3, 4};
    int n = arrinteger.length;
    for (int i = 0; i < n; ++i) {
        Integer integer = arrinteger[i];
        System.out.println(integer);
    }
}

枚举

同样枚举也是java1.5版本添加的类型,使用关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用,这是一种非常有用的功能。

通过反编译揭开枚举的面纱。

代码清单7:非常简单的枚举类型

public enum  Enumeration {
    SUNDAY,
    MONDAY,
}

反编译:

java -jar cfr_0_132.jar class文件目录 –sugarenums false

public final class Enumeration
extends Enum<Enumeration> {
    public static final /* enum */ Enumeration SUNDAY = new Enumeration();
    public static final /* enum */ Enumeration MONDAY = new Enumeration();
    private static final /* synthetic */ Enumeration[] $VALUES;

    public static Enumeration[] values() {
        return (Enumeration[])$VALUES.clone();
    }

    public static Enumeration valueOf(String name) {
        return Enum.valueOf(Enumeration.class, name);
    }

    private Enumeration() {
        super(string, n);
    }

    static {
        $VALUES = new Enumeration[]{SUNDAY, MONDAY};
    }
}

反编译后可以解释很多问题

1、final class不可被继承。

2、extends Enum继承了Enum类,枚举实例可以使用Enum的方法,Enum实现Serializable接口,所以枚举是可以进行序列化的,参考深度分析Java的枚举类型—-枚举的线程安全性及序列化问题

3、public static final修饰了实例,所以每个枚举实例都是一个全局常量。

4、values()valueOf(String name) 静态方法

5、private Enumeration() 构造器私有

上面是最简单的枚举的应用,接下来编写一个复杂点的枚举。

代码清单8:复杂枚举类型,实现接口、成员变量、枚举实例实现抽象方法。

public enum Enumeration implements KeyValue {

    SUNDAY("周日", 0) {
        @Override
        public String details() {
            return "一周的最后一天";
        }
    },
    MONDAY("周一", 1) {
        @Override
        public String details() {
            return "一周的第一天";
        }
    };

    private final String name;

    private final Integer code;

    Enumeration(String name, Integer code) {
        this.name = name;
        this.code = code;
    }


    public abstract String details();

    @Override
    public PropertyEntity getPropertyEntity() {
        return new PropertyEntity(code, name);
    }
}

反编译:

java -jar cfr_0_132.jar class文件目录 –sugarenums false

public abstract class Enumeration
extends Enum<Enumeration>
implements KeyValue {
    public static final /* enum */ Enumeration SUNDAY = new Enumeration("SUNDAY", 0, "\u5468\u65e5", Integer.valueOf(0)){

        @Override
        public String details() {
            return "\u4e00\u5468\u7684\u6700\u540e\u4e00\u5929";
        }
    };
    public static final /* enum */ Enumeration MONDAY = new Enumeration("MONDAY", 1, "\u5468\u4e00", Integer.valueOf(1)){

        @Override
        public String details() {
            return "\u4e00\u5468\u7684\u7b2c\u4e00\u5929";
        }
    };
    private final String name;
    private final Integer code;
    private static final /* synthetic */ Enumeration[] $VALUES;

    public static Enumeration[] values() {
        return (Enumeration[])$VALUES.clone();
    }

    public static Enumeration valueOf(String name) {
        return Enum.valueOf(Enumeration.class, name);
    }

    private Enumeration(String name, Integer code) {
        super(string, n);
        this.name = name;
        this.code = code;
    }

    public abstract String details();

    @Override
    public KeyValue.PropertyEntity getPropertyEntity() {
        return new KeyValue.PropertyEntity(this.code, this.name);
    }

    static {
        $VALUES = new Enumeration[]{SUNDAY, MONDAY};
    }

}

1、类不是final而是abstract抽象类,其实原理很简单,因为枚举类details方法是抽象方法,所以枚举类中包含抽象方法,编译后就是抽象类。

2、每个实例必须实现details抽象方法,以匿名内部类的方式。

3、当然枚举也可以实现一个或多个接口。

switch 支持 String 与枚举

switch java6 开始支持支持枚举类型,java8开始支持String类型,其实这个些都是表象,我们通过反编译解释下

代码清单9:String 与枚举使用switch

private static void demo2() {
    String str = "a";
    switch (str) {
        case "a":
            System.out.println("这是 a ");
            break;
        case "b":
            System.out.println("这是 b ");
            break;
        default:
            System.out.println(" 没找到 String case");
    }
}

private static void demo3() {
    Enumeration monday = Enumeration.MONDAY;
    switch (monday) {
        case MONDAY:
            System.out.println(monday.details());
            break;
        case SUNDAY:
            System.out.println(monday.details());
            break;
    }

}

反编译:

 java -jar cfr_0_132.jar class文件目录 --decodeenumswitch false --decodestringswitch false
private static void demo2() {
    String str;
    String string = str = "a";
    int n = -1;
    switch (string.hashCode()) {
        case 97: {
            if (!string.equals("a")) break;
            n = 0;
            break;
        }
        case 98: {
            if (!string.equals("b")) break;
            n = 1;
        }
    }
    switch (n) {
        case 0: {
            System.out.println("\u8fd9\u662f a ");
            break;
        }
        case 1: {
            System.out.println("\u8fd9\u662f b ");
            break;
        }
        default: {
            System.out.println(" \u6ca1\u627e\u5230 String case");
        }
    }
}

private static void demo3() {
    Enumeration monday = Enumeration.MONDAY;
    switch (.$SwitchMap$com$example$syntactic$sugar$Enumeration[monday.ordinal()]) {
        case 1: {
            System.out.println(monday.details());
            break;
        }
        case 2: {
            System.out.println(monday.details());
        }
    }
}

字符串

反编译后可以看出,String使用了两个switch实现的。

第一个switch使用StringhashCodeequals方法给int类型变量n(存在变量n,就不使用n作为变量)赋值。

第二个switch根据变量n判读执行那个case

枚举

反编译后可以看出:

switch (monday) 被替换成Switch$1.$SwitchMap$com$example$syntactic$sugar$Enumeration[monday.ordinal()]

Switch$1.$SwitchMap$com$example$syntactic$sugar$Enumeration:编译器生成的一个数组,下标是monday.ordinal()值还是monday.ordinal(),所以枚举switch实际上就是利用的monday.ordinal()方法返回的int值的。

方法变长参数

可变参数是java5加入新的语法,支持同一类型可以是任意数量

代码清单10:方法变长参数的使用

private static void demo1() {

    method1();
    method1("123");
    method1("123", "456");

}

private static void method1(String... str) {
    System.out.println(Arrays.toString(str));
}

反编译:

 java -jar cfr_0_132.jar class文件目录 
private static void demo1() {
    VariableMethod.method1(new String[0]);
    VariableMethod.method1(new String[]{"123"});
    VariableMethod.method1(new String[]{"123", "456"});
}

private static /* varargs */ void method1(String[] str) {
    System.out.println(Arrays.toString(str));
}

反编译后发现可变长方法参数其实就是一个数组类型。

条件编译

条件编译很简单,使用if语句实现,代码清单10

代码清单11:条件编译

    private static boolean conditional;
    private final static boolean conditional_final = true;
    private final static int conditional_int = 1;

    public static void main(String[] args) {
        if (true) {
            System.out.println("if inner");
        }
        if (false) {
            System.out.println("if inner 1");
        } else {
            System.out.println("else inner 1");
        }
        boolean b = false;
        if (b) {
            System.out.println("if inner 2");
        } else {
            System.out.println("else inner 2");
        }

        if (conditional) {
            System.out.println("conditional if");
        }
        if (conditional_final) {
            System.out.println("conditional_final if" );
        }
        if (conditional_int == 1) {
            System.out.println("conditional_int == 1");
        }
    }

反编译:

 java -jar cfr_0_132.jar class文件目录
    private static boolean conditional;
    private static final boolean conditional_final = true;
    private static final int conditional_int = 1;

    public static void main(String[] args) {
        System.out.println("if inner");
        System.out.println("else inner 1");
        boolean b = false;
        if (b) {
            System.out.println("if inner 2");
        } else {
            System.out.println("else inner 2");
        }
        if (conditional) {
            System.out.println("conditional if");
        }
        System.out.println("conditional_final if");
        System.out.println("conditional_int == 1");
    }

反编译后可以发现,ifelse语句内在编译期能确定一定执行,编译器就去掉没有必要的代码块。

数值字面量

Java 7中,数值字面量,不管是整数还是浮点数,都允许在数字之间插入任意多个下划线。这些下划线不会对字面量的数值产生影响,目的就是方便阅读。

代码清单12:数值字面量 使用_分割

private static void demo1() {
    //一亿
    int i = 1_0000_0000;
    System.out.println(i);
}

反编译:

 java -jar cfr_0_132.jar class文件目录
private static void demo1() {
    int i = 100000000;
    System.out.println(i);
}

java8 lambda表达式

lambdajava8新加入的一种语法,使我们的代码更简洁,开发效率更高,但对于刚接触lambda的开发人员可能不太会用,idea开发工具会为我们提示那些代码可以转换成lambda表达式。

既然是语法糖,JVM肯定是没有对改语法的支持,那么它是如何实现的呢,我们通过反编译看看就知道了。

代码清单13:lambda表达式的简单使用

private static void demo1() {
    List list = new ArrayList<>();
    list.add("1");
    list.add("2");
    list.add("3");
    list.stream()
            .peek(s -> {
                System.out.println("内容:");
                System.out.println(s);
            })
            .map(Integer::valueOf)
            .forEach(System.out::println);

}

反编译:

 java -jar cfr_0_132.jar class文件目录 --decodelambdas false 
private static void demo1() {
    ArrayList list = new ArrayList();
    list.add("1");
    list.add("2");
    list.add("3");
    PrintStream printStream = System.out;
    printStream.getClass();
    list.stream()
        .peek(
        (Consumer)LambdaMetafactory.metafactory
        (null, null, null, (Ljava/lang/Object;)V,  
                              //方法体
                             lambda$demo1$0(java.lang.String),                                                                                (Ljava/lang/String;)V)()
             ).map(
        (Function)LambdaMetafactory.metafactory
        (null, null, null, (Ljava/lang/Object;)Ljava/lang/Object;,
         //Integer::valueOf
         valueOf(java.lang.String),(Ljava/lang/String;)Ljava/lang/Integer;)()
    ).forEach(
        (Consumer)LambdaMetafactory.metafactory
        (null, null, null, (Ljava/lang/Object;)V, 
         //System.out::println
         println(java.lang.Object), (Ljava/lang/Integer;)V)((PrintStream)printStream)
    );
}

private static /* synthetic */ void lambda$demo1$0(String s) {
    System.out.println("\u5185\u5bb9\uff1a");
    System.out.println(s);
}

其实我们只需关心以下两点:

1、lambda表达式使用的不是内部类,是lambdaAPI LambdaMetafactory 实现的。

2、lambda代码块(()->{})在编译期间生成了对应的静态方法如:lambda$demo1$0,双冒号就是调用调用对应的方法,如:System.out::println

更多请参考 :Translation of Lambda Expressions

try-with-resource

java7开始提供了一种新的关闭资源的方式,try-with-resource 被关闭资源只需实现AutoCloseable 如:代码清单13

代码清单14:try-with-resource关闭资源

public static void main(String[] args) {
    try (BufferedReader br = new BufferedReader(new FileReader("file.xml"))) {
        String line;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
    } catch (IOException e) {
        // handle exception
    }
}

反编译

 java -jar cfr_0_132.jar class文件目录  --tryresources false
   public static void main(String[] args) {
        try {
            BufferedReader br = new BufferedReader(new FileReader("file.xml"));
            Throwable throwable = null;
            try {
                String line;
                while ((line = br.readLine()) != null) {
                    System.out.println(line);
                }
            }
            catch (Throwable line) {
                throwable = line;
                throw line;
            }
            finally {
                if (br != null) {
                    if (throwable != null) {
                        try {
                            br.close();
                        }
                        catch (Throwable line) {
                            throwable.addSuppressed(line);
                        }
                    } else {
                        br.close();
                    }
                }
            }
        }
        catch (IOException br) {
            // empty catch block
        }
    }

通过观察反编译代码可以看到,其实try-with-resource就是通过try+catch+finally实现资源关闭的。

总结

其实java不断升级的过程中添加了很多语法糖,其背后隐藏了很多我们看不到东西,相信同学们看完篇文章应该对其语法糖也有了一些了解,在看到新的语法用反编译看看它是如何实现的,这样在使用的时候更加得心应手了。

你可能感兴趣的:(Java基础,源码)