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

JAVA语法糖(全)相关推荐

  1. 65.Java语法糖

    65.Java语法糖 语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家 Peter.J.Landin 发明的一个术语,指在计算机语言中添加的某种语法,这种语法对语言的功能并没 ...

  2. Java 语法糖详解

    语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家 Peter.J.Landin 发明的一个术语,指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序 ...

  3. Java语法糖1:可变长度参数以及foreach循环原理

    语法糖 接下来几篇文章要开启一个Java语法糖系列,所以首先讲讲什么是语法糖.语法糖是一种几乎每种语言或多或少都提供过的一些方便程序员开发代码的语法,它只是编译器实现的一些小把戏罢了,编译期间以特定的 ...

  4. java语法糖效率高吗_打包 Java将持续向“高糖”方向发展,你真的了解Java语法糖吗? _好机友...

    Java语法糖概念 1. 语法糖Syntactic Sugar 糖衣语法,方便开发人员使用,JVM并不识别,会在编译阶段解语法糖,还原为基础语法. 2. com.sun.tools.javac.mai ...

  5. 全网最全的 Java 语法糖指南

    写在前面 本文隶属于专栏<100个问题搞定Java虚拟机>,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和文献引用请见100个问题搞定Java ...

  6. Jvm 系列(十一)Java 语法糖背后的真相

    语法糖(Syntactic Sugar),也叫糖衣语法,是英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语.指的是,在计算机语言中添加某种语法,这些语法糖虽然不会对语言 ...

  7. Java语法糖之foreach

    语法糖是一种几乎每种语言或多或少都提供过的一些方便程序员开发代码的语法,它只是编译器实现的一些小把戏罢了,编译期间以特定的字节码或者特定的方式对这些语法做一些处理,开发者就可以直接方便地使用了.这些语 ...

  8. Java中switch参数传null会引起异常——Java 语法糖

    问题 switch 参数不能是null,swicth(null)会报java.lang.NullPointerException异常 查找原因 为什么会这样呢,查找一下原因: 找到编译后的class文 ...

  9. c++接口调用外部类_Java基础:Java语法糖4之内部类

    内部类 最后一个语法糖,讲讲内部类,内部类指的就是在一个类的内部再定义一个类. 内部类之所以也是语法糖,是因为它仅仅是一个编译时的概念,outer.java里面定义了一个内部类inner,一旦编译成功 ...

最新文章

  1. 惊呆了!两人发现抵用券漏洞,疯狂“薅羊毛”获利 770 余万
  2. 解决xcode升级之后安装的插件失效
  3. Github Windows安装帮助
  4. Windows 命令行大全
  5. Objective-C语法之字符串NSString去掉前后空格或回车符(可以是NSCharacterSet类型的其它字符)...
  6. linux脚本登录启动失败,linux-从bash脚本启动进程失败
  7. 如何安装 Angular CLI 并且检查 CLI 的版本
  8. Linux下启动程序常见问题,linux系统启动流程及常见故障解决方式
  9. java 保存文件在服务器_java文件保存至服务器
  10. 拒绝职场危机,程序员最核心的竞争力是什么?
  11. Adobe illustrator 直接选择工具删除白板 - 连载 6
  12. JS调用模式以及bind()方法
  13. 《信息处理技术员考试考前冲刺预测卷及考点解析》下午案例复习重点
  14. SOME/IP报文格式-Request ID
  15. Java | JPanel与JFrame的区别
  16. wxid转微信号软件执行代码。
  17. Java Day24
  18. 说一个岛上有100个人,其中有5个红眼睛
  19. token 微信access 过期_.Net微信开发之如何解决access_token过期问题
  20. Oracle导入dmp文件(cmd方式)

热门文章

  1. 自动化测试(二)02——单元测试类工具-Karma、Jasmine、Mocha、Jest、AVA E2E测试类工具-cypress、nightmare、nightwatch、testcafe
  2. 环保管家——全面排查、智能诊断、贴身帮扶
  3. TUN(IP Tunneling)介绍
  4. 链表带环问题及相遇点问题
  5. 3.31 论文笔记 | Cached and Confused: Web Cache Deception in the Wild
  6. javascript 关闭浏览器怎么清空所有的cookie
  7. c语言如何将值赋给结构体指针,C语言给结构体指针赋值
  8. 将matlab中数据保存为txt或dat格式
  9. Middle-题目95:222. Count Complete Tree Nodes
  10. 高情商的人,应该在朋友不好意思跟…