Java学习(19) -- 语法糖

一、java中的语法糖原理

语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。

Java中的泛型,变长参数,自动拆箱/装箱,条件编译等都是

二、解语法糖

java中的语法糖只存在于编译期, 在编译器将 .java 源文件编译成 .class 字节码时, 会进行解语法糖操作, 还原最原始的基础语法结构。这些语法糖包含条件编译、断言、Switch语句与枚举及字符串结合、可变参数、自动装箱/拆箱、枚举、内部类、泛型擦除、增强for循环、lambda表达式、try-with-resources语句、JDK10的局部变量类型推断等等。

三、语法糖实例

 1)switch支持String和枚举

/**
 * 枚举与Switch语句
 * option: --decodeenumswitch false
 */
public int switchEnumTest(EnumTest e) {
    switch (e) {
        case FOO:
            return 1;
        case BAP:
            return 2;
    }
    return 0;
}

/**
 * 枚举, JDK1.5开始支持
 * option: --sugarenums false
 */
public enum EnumTest {
    FOO,
    BAR,
    BAP
}

 

switch支持枚举是通过调用枚举类默认继承的父类Enum中的ordinal()方法来实现的, 这个方法会返回枚举常量的序数。 

/** 
 * 字符串与Switch语句
 * option: --decodestringswitch false
 */
public int switchStringTest(String s) {
    switch (s) {
        default:
            System.out.println("Test");
            break;
        case "BB":  // BB and Aa have the same hashcode.
            return 12;
        case "Aa":
        case "FRED":
            return 13;
    }
    System.out.println("Here");
    return 0;
}

switch支持字符串是通过hashCode()equals()方法来实现的, 先通过hashCode()返回的哈希值进行switch, 然后通过equals()方法比较进行安全检查, 调用equals()是为了防止可能发生的哈希碰撞。

另外switch还支持byte、short、int、char这几种基本数据类型, 其中支持char类型是通过比较它们的ascii码(ascii码是整型)来实现的。所以switch其实只支持一种数据类型, 也就是整型, 其他诸如String、枚举类型都是转换成整型之后再使用switch的。

2)泛型

/**
 * 泛型擦除
 * option: 
 */
public void genericEraseTest() {
    List<String> list =  new ArrayList<String>();
}

 在JVM中没有泛型这一概念,  只有普通方法和普通类, 所有泛型类的泛型参数都会在编译时期被擦除, 所以泛型类并没有自己独有的Class类对象比如List<Integer>.class, 而只有List.class对象。

3)自动装箱与拆箱

/**
 * 自动装箱/拆箱
 * option: --sugarboxing false
 */
public Double autoBoxingTest(Integer i, Double d) {
    return d + i;
}

首先我们知道, 基本类型与包装类型在某些操作符的作用下, 包装类型调用valueOf()方法的过程叫做装箱, 调用xxxValue()方法的过程叫做拆箱。所以上面的结果很容易看出, 先对两个包装类进行拆箱, 再对运算结果进行装箱。

4) 方法变长参数

/**
 * 可变参数
 * option: --arrayiter false
 */
public void varargsTest(String ... arr) {
    for (String s : arr) {
        System.out.println(s);
    }
}

可变参数其实就是一个不定长度的数组, 数组长度随传入方法的对应参数个数来决定。可变参数只能在参数列表的末位使用。

5) 枚举

/**
 * 枚举, JDK1.5开始支持
 * option: --sugarenums false
 */
public enum EnumTest {
    FOO,
    BAR,
    BAP
}

当我们自定义一个枚举类型时, 编译器会自动创建一个被final修饰的枚举类来继承Enum, 所以自定义枚举类型是无法继承和被继承的。当枚举类初始化时, 枚举字段引用该枚举类的一个静态常量对象, 并且所有的枚举字段都用常量数组$VALUES来存储。values()方法内则调用Object的clone()方法, 参照$VALUES数组对象复制一个新的数组, 新数组会有所有的枚举字段。

6) 内部类

import java.util.*;
import java.io.*;

public class CFRDecompilerDemo {

    int x = 3;

    /**
     * 内部类
     * option: --removeinnerclasssynthetics false
     */
    public void innerClassTest() {
        new InnerClass().getSum(6);
    }

    public class InnerClass {
        public int getSum(int y) {
            x += y;
            return x;
        }
    }    
}

首先我们要明确, 上述innerClassTest()方法中的this是外部类当前对象的引用, 而InnerClass类中的this则是内部类当前对象的引用。编译过程中, 编译器会自动在内部类定义一个外部类的常量引用this$0, 并且在内部类的构造器中初始化this$0, 当外部类访问内部类时, 会把当前外部类的对象引用this传给内部类的构造器用于初始化, 这样内部类就能通过所持有的外部类的对象引用, 来访问外部类的所有公有及私有成员。

7)条件编译

/**
 * 条件编译
 * option: 不需要参数
 */
public void ifCompilerTest() {
    if(false) {
        System.out.println("false if");
    }else {
        System.out.println("true else");
    }
}

很明显, javac编译器在编译时期的解语法糖阶段, 会将条件分支不成立的代码进行消除。

8) 断言

/**
 * 断言, JDK1.4开始支持
 * option: --sugarasserts false
 */
public void assertTest(String s) {
    assert (!s.equals("Fred"));
    System.out.println(s);
}

如上, 当断言结果为true时, 程序继续正常执行, 当断言结果为false时, 则抛出AssertionError异常来打断程序的执行。

9)数值字面量

publicclassTest{

     publicstaticvoidmain(String... args) {

     int i = 10_000;

     System.out.println(i);

}


//反编译后:

publicclassTest 
    publicstaticvoidmain(String[] args) {

        inti = 10000;

        System.out.println(i);

    }

}

10)for... each

/**
 * 增强for循环
 * option: --collectioniter false
 */
public void forLoopTest() {
    String[] qingshanli = {"haha", "qingshan", "helloworld", "ceshi"};  
    List<String> list =  Arrays.asList(qingshanli);
    for (Object s : list) {
        System.out.println(s);
    }
}

很明显, 增强for循环的底层其实还是通过迭代器来实现的, 这也就解释了为什么增强for循环中不能进行增删改操作。

11) try-with-resources语句

/**
 * try-with-resources语句
 * option: --tryresources false
 */
public void tryWithResourcesTest() throws IOException {
    try (final StringWriter writer = new StringWriter();
         final StringWriter writer2 = new StringWriter()) {
        writer.write("This is qingshanli1");
        writer2.write("this is qingshanli2");
    }
}

在JDK7之前, 如IO流、数据库连接等资源用完后, 都是通过finally代码块来释放资源。而try-with-resources语法糖则帮我们省去了释放资源这一操作, 编译器在解语法糖阶段时会将它还原成原始的语法结构。

12)lambda表达式

/**
 * lambda表达式
 * option: --decodelambdas false
 */
public void lambdaTest() {
    String[] qingshanli = {"haha", "qingshan", "helloworld", "ceshi"};  
    List<String> list =  Arrays.asList(qingshanli);
    // 使用lambda表达式以及函数操作
    list.forEach((str) -> System.out.print(str + "; "));
    // 在JDK8中使用双冒号操作符
    list.forEach(System.out::println);  
}

这里笔者经验尚浅, 关于lambda表达式的实现原理暂不做阐述, 以免误人子弟, 欢迎有兴趣的读者在留言区一起讨论。

 

在此感谢该博主的微博:https://www.cnblogs.com/qingshanli/p/9375040.html#_label4,大部分内容转自他

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值