什么是编译?

因为机器是只能做数字计算的,能够让机器去运算数字语言就是机器语言。为了让机器可理解,相对于机器语言的高级语言都需要一个转换,从高级、机器不可理解,转换为机器可理解的机器语言,这样的一个转换过程就叫做 编译(Compile)。简单来说就是把人能看懂的代码,变成机器能读懂的指令的过程。负责这一过程处理的的工具叫做编译器。

不同的语言都有自己的编译器,Java语言中负责编译的编译器是一个命令:javac

在我们初学Java时,一般都会让我们写一个demo叫做“HelloWord”,我们可以使用javac这个命令将HelloWorld.java这个文件,生成HelloWorld.class文件,这个.class文件就是JVM可以识别的文件,通常我们认为这个过程就是Java语言的编译。但是这个.class文件依旧不是机器能够是别的语言,还需要JVM将这个.class文件转换成机器能够识别的语言。


什么是反编译?

反编译就是和编译正好相反的过程,就是将已经编译好的变成语言还原成未编译的状态。Java语言的反编译一般是指将.class文件装换成.java文件。


Java反编译工具

想要进行反编译就不得不提及反编译工具,Java中的反编译工具我用的最多的就是jd-gui,图形界面操作简单方便,只要要将文件拖进去就可以看到源码,但是断更很久,有些代码已经不支持,这里我们不多提及。在jd-gui不能满足我们的时候,我们可以尝试新的工具,例如CFR(官网地址:http://www.benf.org/other/cfr/)我们在官网下载好jar包,然后就可以使用了。

1.下面这个是我们已经写好的一个类,JDK1.7后添加了对String类型的判断switch反编译后的结果:

/** 
 * @author Y
 */
public class TestSwitchCompile {
    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;
        }
    }
}

  执行以下命令:

java -jar cfr_0_132.jar TestSwitchCompile.class --decodestringswitch false

  得到以下代码:

/*
 * Decompiled with CFR 0_132.
 */
import java.io.PrintStream;
public class TestSwitchCompile { 
      public static void main(String[] arrstring) {
        String string;
        String string2 = string = "world";
        int n = -1;
        switch (string2.hashCode()) {
            case 99162322: {
                if (!string2.equals("hello")) break;
                n = 0;
                break;
            }
            case 113318802: {
                if (!string2.equals("world")) break;
                n = 1;
            }
        }
        switch (n) {
            case 0: {
                System.out.println("hello");
                break;
            }
            case 1: {
                System.out.println("world");
                break;
            }
        }
    }
}

通过这段代码我们可以得到字符串的switch是通过equals()和hashCode()方法来实现的结论。

2.下面我们来看一下String对"+"的重载:

/**
 * @author Y
 */
public class TestStringCompile {
    public static void main(String[] args) {
        String hello = "hello";
        String world = hello + "world";

    }
}

  执行以下命令:

java -jar cfr_0_132.jar TestStringCompile.class --stringbuilder false

  得到以下代码:

/**
 * Decompiled with CFR 0_132.
 */
public class TestStringCompile {
    public static void main(String[] arrstring) {
        String string = "hello";
        String string2 = new StringBuilder().append(string).append("world").toString();
    }
}

看了反编译之后的代码我们发现,其实String对“+”的支持其实就是使用了StringBuilder以及他的方法append、toString两个方法。

3.下面我们看一下lambda反编译后的结果:

import java.util.ArrayList;
import java.util.List;

/**
 * @author Y
 */
public class TestLamdbaCompile {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.forEach(s -> System.out.println(s));
    }
}

  执行以下命令:

java -jar cfr_0_132.jar TestLamdbaCompile.class --decodelambdas false

  得到以下代码:

/**
 * Decompiled with CFR 0_132.
 */
import java.io.PrintStream;
import java.lang.invoke.LambdaMetafactory;
import java.util.ArrayList;
import java.util.function.Consumer;

public class TestLamdbaCompile {
    public static void main(String[] arrstring) {
        ArrayList<String> arrayList = new ArrayList<String>();
        arrayList.add("1");
        arrayList.add("2");
        arrayList.add("3");
        arrayList.forEach((Consumer<String>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$0(java.lang.String ), (Ljava/lang/String;)V)());
    }

    private static /* synthetic */ void lambda$main$0(String string) {
        System.out.println(string);
    }
}

可以看到,在forEach方法中,其实是调用了java.lang.invoke.LambdaMetafactory#metafactory方法,该方法的第四个参数implMethod指定了方法实现。可以看到这里其实是调用了lambda$main$0方法进行了输出。

4.下面我们看一下java10本地变量推断:

import java.util.ArrayList;

/**
 * @author Y
 */
public class TestVarCompile {
    public static void main(String[] args) {
        //局部变量
        var String = "hello";
        //局部变量
        var listString = new ArrayList<String>();
        listString.add(String);
        listString.add("world");
        //增强型for循环索引
        for(var list : listString){
            System.out.println(list);
        }
        //传统for循环的局部变量
        for(var i=0; i<listString.size(); i++){
            System.out.println(listString.get(i));
        }
    }
}

  执行以下命令:

java -jar cfr_0_132.jar TestVarCompile.class --collectioniter false

  得到以下代码:

/**
 * Decompiled with CFR 0_132.
 */
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;

public class TestVarCompile {
    public static void main(String[] arrstring) {
        String string = "hello";
        ArrayList<String> arrayList = new ArrayList<String>();
        arrayList.add(string);
        arrayList.add("world");
        Iterator iterator = arrayList.iterator();
        while (iterator.hasNext()) {
            String string2 = (String)iterator.next();
            System.out.println(string2);
        }
        for (int i = 0; i < arrayList.size(); ++i) {
            System.out.println((String)arrayList.get(i));
        }
    }
}

反编译后这段代码我们看起来就比较熟悉了,虽然我们定义的是var但是在.java文件编译成.class文件的时候,会进行解语法糖,用变量真正的类型来代替var。

上面的代码我们看到ArrayList泛型了String类型,我们知道泛型其实是语法糖,在JDK1.5中,Java语言引入了泛型机制。但是这种泛型机制是通过类型擦除来实现的,类型擦除的主要过程如下: 1.将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。 2.移除所有的类型参数。意思就是Java泛型只存在于我们的源码里面。但是我用cfr的反编译的时候,我没有找到哪个参数是可以去除掉这个泛型的,这个也是我的一个遗留问题。然后我使用jad去反编译看了一下,结果如下:

执行以下命令:

jad TestVarCompile.class
执行完命令后会打印出下面这一行

Parsing TestVarCompile.class... Generating TestVarCompile.jad

得到以下代码:

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;

public class TestVarCompile {

    public static void main(String args[])
    {
        String s = "hello";
        ArrayList arraylist = new ArrayList();
        arraylist.add(s);
        arraylist.add("world");
        String s1;
        for(Iterator iterator = arraylist.iterator(); iterator.hasNext();  System.out.println(s1))
            s1 = (String)iterator.next();

        for(int i = 0; i < arraylist.size(); i++)
            System.out.println((String)arraylist.get(i));

    }
}

这时我们再看arrayList,其实已经没有泛型了。所以在虚拟机中,没有泛型,只有普通类和普通方法,所有泛型类的类型参数在编译时都会被擦除,泛型类并没有自己独有的Class类对象。