前言
语法糖
语法糖是一种用来方便程序员代码开发的手段,简化程序开发,但是不会提供实质性的功能改造,但可以提高开发效率或者语法的严谨性或者减少编码出错的机会。
总而言之,语法糖可以看作是编译器实现的一种小把戏。
解语法糖
语法糖的存在主要是方便开发人员使用。但其实,Java虚拟机并不支持这些语法糖。这些语法糖在编译阶段就会被还原成简单的基础语法结构,这个过程就是解语法糖。
编译
说到编译,大家肯定都知道,Java语言中,javac命令可以将后缀名为.java的源文件编译为后缀名为.class的可以运行于Java虚拟机的字节码。
反编译
如果你去看com.sun.tools.javac.main.JavaCompiler的源码,你会发现在compile()中有一个步骤就是调用desugar(),这个方法就是负责解语法糖的实现的。
Java 中最常用的语法糖主要有泛型、变长参数、条件编译、自动拆装箱、foreach ,枚举,内部类,switch,枚举
语法糖1–泛型
如:ArrayList、Map<K,V>、public void methodName(T t)。
Java 语言中的泛型,它只在程序的源码中存在,在编译后的字节码文件中,就是已经替换为原来的原生类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转换代码,因此,对于运行期的java语言来说,ArrayList与ArrayList就是同一个类,所以泛型技术实际上是java语言的一颗语法糖,java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型称为伪泛型。
private static void demo1() {
Map<String, String> map = new HashMap<>();
map.put("你好","恩");
map.put("在吗", "吃了没?");
System.out.println(map.get("你好"));
System.out.println(map.get("在吗"));
}
反编译结果
private static void demo1() {
HashMap map = new HashMap();
map.put("你好", "恩!");
map.put("在吗", "吃了没?");
System.out.println((String)map.get("你好"));
System.out.println((String)map.get("在吗"));
}
语法糖2–变长参数
private static void demo1() {
method1("123", "456","789");
}
private static void method1(String... str) {
System.out.println(Arrays.toString(str));
}
反编译结果
private static void demo() {
method(new String[]{
"123", "456"."789"});
}
private static void method(String[] str) {
System.out.println(Arrays.toString(str));
}
语法糖3–条件编译
public static void main(String[] args) {
private static final int NUMBER_INT = 1;
if (true) {
System.out.println("JAVA");
}
if (false) {
System.out.println("JAVAEE");
}
if (NUMBER_INT == 1){
System.out.println("NUMBER_INT == 1");
}
}
反编译结果
System.out.println("JAVA");
System.out.println("NUMBER_INT == 1");
语法糖–4自动拆装箱
Integer a = 0;
int b = a;
反编译结果
Integer a = Integer.valueof(0);
int b = a.intValue();
语法糖–5 ForEach循环
集合
private static void demo() {
List<String> list = new LinkedList<>();
list.add("a");
list.add("b");
list.add("c");
for (String str : list) {
System.out.println(str);
}
}
反编译结果
private static void demo() {
List list = new LinkedList();
list.add("a");
list.add("b");
list.add("c");
for (Iterator iterator =list.iterator();iterator.hasNext()) {
System.out.println((String)iterator.next());
}
}
数组
private static void demo() {
int[] array = {
1,2,3,4};
for (int integer : array) {
System.out.println(integer);
}
}
反编译结果
private static void demo() {
int[] array;
int[] arrinteger = array = new int[]{
1, 2, 3, 4};
int n = arrinteger.length;
for (int i = 0; i < n; ++i) {
int integer = arrinteger[i];
System.out.println(integer);
}
}
因为只是遍历所以重新定义了一个新数组,也是为什么在for each遍历的时候赋新值也不会改变的原因。
语法糖6–枚举
public enum EnumDemo {
SPRING,
SUMMER,
}
反编译结果
public final class EnumDemo extends Enum
{
private EnumDemo(String s, int i)
{
super(s, i);
}
public static EnumDemo[] values()
{
EnumDemo enum2[];
int i;
EnumDemo enum1[];
System.arraycopy(enum2 = ENUM_VALUES, 0, enum1 = new EnumDemo[i = enum2.length], 0, i);
return enum1;
}
public static EnumDemo valueOf(String s)
{
return (EnumDemo)Enum.valueOf(s);
}
public static final EnumDemo SPRING;
public static final EnumDemo SUMMER;
private static final EnumDemo ENUM_VALUES[];
static
{
SPRING = new T("SPRING", 0);
SUMMER = new T("SUMMER", 1);
ENUM_VALUES = (new EnumDemo[] {
SPRING, SUMMER
});
}
}
语法糖7–内部类
public class OutterClass {
private String userName;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public static void main(String[] args) {
}
class InnerClass{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
以上代码编译后会生成两个class文件:OutterClass$InnerClass.class 、OutterClass.class 。
当我们尝试使用jad对OutterClass.class文件进行反编译的时候,他会把两个文件全部进行反编译,然后一起生成一个OutterClass.jad文件。内容如下:
反编译结果
public class OutterClass
{
class InnerClass
{
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
private String name;
final OutterClass this$0;
InnerClass()
{
this.this$0 = OutterClass.this;
super();
}
}
public OutterClass()
{
}
public String getUserName()
{
return userName;
}
public void setUserName(String userName){
this.userName = userName;
}
public static void main(String args1[])
{
}
private String userName;
}
语法糖8-- switch 支持 String 与枚举
字符串
public class switchDemoString {
public static void main(String[] args) {
String str = "world";
switch (str) {
case "hello":
System.out.println("hello");
break;
case "world":
System.out.println("world");
break;
default:
break;
}
}
}
反编译结果
public class switchDemoString
{
public switchDemoString()
{
}
public static void main(String args[])
{
String str = "world";
String s;
switch((s = str).hashCode())
{
default:
break;
case 99162322:
if(s.equals("hello"))
System.out.println("hello");
break;
case 113318802:
if(s.equals("world"))
System.out.println("world");
break;
}
}
}
Java中的swith自身原本就支持基本类型。比如int、char等。
对于int类型,直接进行数值的比较。对于char类型则是比较其ascii码。所以,对于编译器来说,switch中其实只能使用整型,任何类型的比较都要转换成整型。比如byte。short,char(ascii码是整型)以及int。switch是通过equals()和hashCode()方法来实现的.
仔细看下可以发现,进行switch的实际是哈希值,然后通过使用equals方法比较进行安全检查,这个检查是必要的,因为哈希可能会发生碰撞。
枚举
class enum Enum{
A,B,C,D;
}
public class Demo{
Enum grade = Enum.A;
switch (grade) {
case A:
System.out.println("Excellent");
break;
case B:
System.out.println("Good");
break;
case C:
System.out.println("Not bad");
break;
case D:
System.out.println("Not good");
break;
default:
System.out.println("unknown");
}
}
反编译结果
class enum Enum{
A,B,C,D;
}
public class Demo{
Enum grade = Enum.A;
switch (grad.ordinal()) {
case 0:
System.out.println("Excellent");
break;
case 1:
System.out.println("Good");
break;
case 2:
System.out.println("Not bad");
break;
case 3:
System.out.println("Not good");
break;
default:
System.out.println("unknown");
}
}
可见枚举的反编译匹配是根据父类里面的 public final int ordinal()方法获取的值