JAVA语法糖(全)

JAVA语法糖(全)

目录

概述

字符串拼接

条件编译

断言

枚举与Switch语句

字符串与Switch语句

可变参数

自动装箱/拆箱

枚举

内部类

泛型擦除

增强for循环

lambda表达式

try-with-resources语句

JDK10的局部变量类型推断

源代码

参数资料


 

 

概述

编译器是一种计算机程序, 它主要的目的是将便于人编写、阅读、维护的高级计算机语言所写的源代码程序, 翻译为计算机能解读、运行的低阶机器语言的程序, 即可执行文件。而 javac 就是java语言中的编译器, 它用于将 .java 文件转换成JVM能识别的 .class 字节码文件, 反编译则是将 .class 文件转换成 .java 文件。

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

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

关于反编译工具, 其实在JDK中自带了一个javap命令, 在以前的文章JDK的命令行工具系列 (二) javap、jinfo、jmap中也有提及到, 但是日常中很少会用到javap, 所以这次我们借助另一个反编译工具 CFR 来分析java中的语法糖, 这里我下载的是最新的cfr_0_132.jar。

字符串拼接

 

/**
 * 字符串拼接
 * option: --stringbuilder false
 */
public void stringBuilderTest(int end) {
    char[] foo = new char[]{'@', 'a', '*'};
    char ch;
    int x = 0;
    while ((ch = foo[++x]) != '*') {
        System.out.println("" + x + ": " + ch);
    }
}

 

命令行: java -jar cfr_0_132.jar CFRDecompilerDemo.class --stringbuilder false

从反编译后的代码中能看出, 当我们使用+号进行字符串拼接操作时, 编译时会自动创建一个StringBuilder对象。所以当在循环中拼接字符串时, 应避免使用+号操作, 否则每次循环都会创建一个StringBuilder对象再回收, 造成较大的开销。

条件编译

 

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

 

命令行: java -jar cfr_0_132.jar CFRDecompilerDemo.class

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

断言

 

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

 

命令行: java -jar cfr_0_132.jar CFRDecompilerDemo.class --sugarasserts false

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

枚举与Switch语句

 

/**
 * 枚举与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
}

 

命令行: java -jar cfr_0_132.jar CFRDecompilerDemo.class --decodeenumswitch false

switch支持枚举是通过调用枚举类默认继承的父类Enum中的ordinal()方法来实现的, 这个方法会返回枚举常量的序数。由于笔者的经验尚浅, 具体的实现细节还不是很清楚(比如枚举常量FOO的序数是0, 而case FOO语句编译后的 case 1, 这个1是什么? 另外switchEnumTest()方法传入一个FOO, 调用ordinal()方法得到的序数为0, 那么他又是如何与case 1进行匹配的呢?), 欢迎读者在留言区一起讨论。

字符串与Switch语句

 

/** 
 * 字符串与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;
}

 

命令行: java -jar cfr_0_132.jar CFRDecompilerDemo.class --decodestringswitch false

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

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

可变参数

 

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

 

命令行: java -jar cfr_0_132.jar CFRDecompilerDemo.class --arrayiter false

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

自动装箱/拆箱

 

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

 

命令行: java -jar cfr_0_132.jar CFRDecompilerDemo.class --sugarboxing false

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

枚举

 

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

 

命令行: java -jar cfr_0_132.jar CFRDecompilerDemo.class --sugarenums false

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

内部类

 

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

 

命令行: java -jar cfr_0_132.jar CFRDecompilerDemo.class --removeinnerclasssynthetics false

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

泛型擦除

 

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

 

命令行: java -jar cfr_0_132.jar CFRDecompilerDemo.class

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

增强for循环

 

/**
 * 增强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);
    }
}

 

命令行: java -jar cfr_0_132.jar CFRDecompilerDemo.class --collectioniter false

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

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

 

命令行: java -jar cfr_0_132.jar CFRDecompilerDemo.class --decodelambdas false

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

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

 

命令行: java -jar cfr_0_132.jar CFRDecompilerDemo.class --tryresources false

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

JDK10的局部变量类型推断

 

/**
 * 局部变量类型推断, JDK10开始支持
 * option: 不需要参数
 */
public void varTest() {
    //初始化局部变量  
    var string = "qingshanli";
    //初始化局部变量  
    var stringList = new ArrayList<String>();
    stringList.add("九幽阴灵,诸天神魔,以我血躯,奉为牺牲。");
    stringList.add("三生七世,永堕阎罗,只为情故,虽死不悔!");
    stringList.add("blog:http://www.cnblogs.com/qingshanli/");
    //增强for循环的索引
    for (var s : stringList){
        System.out.println(s);
    }
    //传统for循环的局部变量定义
    for (var i = 0; i < stringList.size(); i++){
        System.out.println(stringList.get(i));
    }
}

 

JDK10环境下编译: /home/qingshanli/Downloads/jdk-10.0.2/bin/javac CFRDecompilerDemo.java

命令行: java -jar cfr_0_132.jar CFRDecompilerDemo.class --collectioniter false

可以看出, 局部变量类型推断其实也是一个语法糖。在编译过程的解语法糖阶段, 会使用变量真正的类型来替代var类型。所以java由始至终是一种强类型语言, java中的var和弱类型语言JavaScript中的var是完全不一样的, 例如下图 var i = "10" - 6 这样的语法运算在JavaScript中可以的, 而在Java语言中则不被允许。

另外目前已知的允许使用var声明变量的几个场景有初始化局部变量、增强for循环的索引、传统for循环的局部变量定义。而诸如方法的形参、构造器的形参、方法的返回值类型、对象的成员变量、只进行定义而不初始化的变量等则不支持这种用法。对于后面的几种不支持, 我的猜想是因为它们会被外部访问而导致充满了不确定性, 举个栗子, 比如对象的成员变量X, 被对象A访问并赋值ArrayList类型, 被对象B访问并赋值HashMap类型, 那么问题来了, 对象A和对象B都是同一个类的实例, 这就产生了冲突, 此时虚拟机又如何区分这个对象的成员变量X到底是什么类型呢? 

源代码

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

public class CFRDecompilerDemo {

    int x = 3;

    /**
     * 字符串拼接
     * option: --stringbuilder false
     */
    public void stringBuilderTest(int end) {
        char[] foo = new char[]{'@', 'a', '*'};
        char ch;
        int x = 0;
        while ((ch = foo[++x]) != '*') {
            System.out.println("" + x + ": " + ch);
        }
    }
    
    /**
     * 条件编译
     * option: 不需要参数
     */
    public void ifCompilerTest() {
        if(false) {
            System.out.println("false if");
        }else {
            System.out.println("true else");
        }
    }
    
    /**
     * 断言, JDK1.4开始支持
     * option: --sugarasserts false
     */
    public void assertTest(String s) {
        assert (!s.equals("Fred"));
        System.out.println(s);
    }
    
    /**
     * 枚举与Switch语句
     * option: --decodeenumswitch false
     */
    public int switchEnumTest(EnumTest e) {
        switch (e) {
            case FOO:
                return 1;
            case BAP:
                return 2;
        }
        return 0;
    }
    
    /** 
     * 字符串与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;
    }
    
    /**
     * 可变参数
     * option: --arrayiter false
     */
    public void varargsTest(String ... arr) {
        for (String s : arr) {
            System.out.println(s);
        }
    }
    
    /**
     * 自动装箱/拆箱
     * option: --sugarboxing false
     */
    public Double autoBoxingTest(Integer i, Double d) {
        return d + i;
    }
    
    /**
     * 枚举, JDK1.5开始支持
     * option: --sugarenums false
     */
    public enum EnumTest {
        FOO,
        BAR,
        BAP
    }
    
    /**
     * 内部类
     * option: --removeinnerclasssynthetics false
     */
    public void innerClassTest() {
        new InnerClass().getSum(6);
    }
    
    public class InnerClass {
        public int getSum(int y) {
            x += y;
            return x;
        }
    }
    
    /**
     * 泛型擦除
     * option: 
     */
    public void genericEraseTest() {
        List<String> list =  new ArrayList<String>();
    }
    
    /**
     * 增强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);
        }
    }
    
    /**
     * 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);  
    }
    
    /**
     * 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");
        }
    }
    
    /**
     * 局部变量类型推断, JDK10开始支持
     * option: 不需要参数
     */
    public void varTest() {
        //初始化局部变量  
        var string = "qingshanli";
        //初始化局部变量  
        var stringList = new ArrayList<String>();
        stringList.add("九幽阴灵,诸天神魔,以我血躯,奉为牺牲。");
        stringList.add("三生七世,永堕阎罗,只为情故,虽死不悔!");
        stringList.add("blog:http://www.cnblogs.com/qingshanli/");
        //增强for循环的索引
        for (var s : stringList){
            System.out.println(s);
        }
        //传统for循环的局部变量定义
        for (var i = 0; i < stringList.size(); i++){
            System.out.println(stringList.get(i));
        }
    }
}

参数资料

Java的编译原理

Java代码的编译与反编译那些事儿-HollisChuang's Blog

我反编译了Java 10的本地变量类型推断-HollisChuang's Blog

Java中的Switch对整型、字符型、字符串型的具体实现细节-HollisChuang's Blo...

一些防止java代码被反编译的方法

 

转载 https://www.cnblogs.com/qingshanli/p/9375040.html

  • 7
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java语法糖是指Java语言中为了更方便开发者使用而提供的一些特性,这些特性本质上并不会改变Java语言的运行机制,而是通过编译器或运行时库等方式将这些语法糖转换为Java语言本身能够识别的代码。这些语法糖的目的是为了简化代码的写法,让代码更易于理解和维护。 一些典型的Java语法糖包括: - 自动装箱与拆箱:可以在Java代码中直接使用基本数据类型,而无需手动创建对应的包装类对象。 - for-each循环:可以直接遍历数组或集合中的所有元素,而无需使用下标或迭代器。 - 可变参数列表:可以将一组参数封装为数组传递给方法,而无需手动创建数组。 - Lambda表达式:可以创建简单的匿名函数,而无需定义单独的函数对象。 这些语法糖都是在编译期间转换为Java语言本身的特性,因此不会对程序的性能造成影响。 ### 回答2: Java语法糖是指在Java编程语言中的一些语法上的改进和简化,它使得代码更加易读、简洁和易于理解。语法糖不是新增加的语言功能,而是对现有功能的语法上的改良。 一个常见的Java语法糖是自动装箱和拆箱。在Java 1.5之前,基本类型(如int、float等)和它们对应的包装类(如Integer、Float等)之间不能直接进行赋值或比较操作,需要通过手动装箱和拆箱的方式。但通过语法糖的改进,现在可以直接在基本类型和对应的包装类之间进行自动转换,使得代码更加简洁和优雅。 另一个例子是增强的for循环。在Java 1.5之前,遍历数组或集合需要使用传统的for循环,并且需要手动获取和指定迭代器。而通过语法糖的改进,现在可以使用更加简洁的增强的for循环,将原始的方法调用、初始化和变量声明过程都隐藏在背后,使得代码更加易读和简洁。 还有一些其他的语法糖,如可变参数、枚举类型、Lambda表达式等,它们都是通过简化和优化语法上的表示方式,提高代码的可读性和可维护性。 需要注意的是,尽管语法糖使得代码更加简洁,但底层执行的逻辑并没有改变。编译器会将语法糖转换为等价的原始代码,然后再进行编译和执行。所以在阅读和理解代码时,还是需要了解底层的语言特性和实现细节。 ### 回答3: Java语法糖是一种语法的简化形式,它能够使得代码更加易读易写,并且不会增加程序的运行效率。 在Java语言中,有些常见的操作会使用较为繁琐的语法去实现,为了简化这些操作的写法,Java引入了语法糖语法糖并不是一种新的特性或者语法规则,而是一种编译器提供的功能,可以将一些常见的代码模式转化为更简洁的语法结构。 常见的Java语法糖包括自动拆装箱、泛型、枚举类型、增强的for循环以及可变参数等。通过使用这些语法糖,可以使得代码更加简洁易读,并且减少了一些常见错误的发生。 比如,自动拆装箱允许我们在基本类型和包装类型之间进行自动的转换,不需要手动进行转换操作。使用泛型可以在编译时进行类型检查,避免了类型转换的错误。枚举类型提供了更好的可读性和类型安性。增强的for循环可以简化对数组和集合的迭代操作。可变参数允许我们以更方便的方式传递不定数量的参数。 尽管语法糖提供了更加简洁的写法,但是在编译过程中,这些语法糖都会被转化为等价的标准Java代码,所以对于程序的运行效率没有实质的影响。 总的来说,Java语法糖使得代码更加易读易写,并且减少了一些常见错误的发生,提高了程序的可维护性和开发效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值