编译器是一种计算机程序, 它主要的目的是将便于人编写、阅读、维护的高级计算机语言所写的源代码程序, 翻译为计算机能解读、运行的低阶机器语言的程序, 便可执行文件。而 javac 就是java语言中的编译器, 它用于将 .java 文件转换成JVM能识别的 .class 字节码文件, 反编译则是将 .class 文件转换成 .java 文件。html
语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,可是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。java
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.classide
很明显, 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异常来打断程序的执行。oop
/** * 枚举与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语句 * 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循环 * 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表达式 * 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语句 * 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开始支持 * 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代码的编译与反编译那些事儿-HollisChuang's Blog
我反编译了Java 10的本地变量类型推断-HollisChuang's Blog
Java中的Switch对整型、字符型、字符串型的具体实现细节-HollisChuang's Blo...
做者:张小凡
出处:https://www.cnblogs.com/qingshanli/ 本文版权归做者和博客园共有,欢迎转载,但未经做者赞成必须保留此段声明,且在文章页面明显位置给出原文链接,不然保留追究法律责任的权利。若是以为还有帮助的话,能够点一下右下角的【推荐】。