Java教程

java编译过程-解语法糖

本文主要是介绍java编译过程-解语法糖,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

 

转自:https://1fishman.github.io/2019/04/21/java%E7%BC%96%E8%AF%91%E4%BC%98%E5%8C%96/

java编译

java编译器为我们做了很多优化,比如在java中泛型并不是真正的泛型,在编译的时候会进行泛型擦除,使用的时候再进行类型转换.或者Integer自动装箱和拆箱.foreach循环遍历等等.

泛型擦除

在java中泛型并不是真正的泛型,因为有一个java早期没有泛型的时候都是通过Object来代替泛型的,因为java中每个对象都是继承自Object的.在通过类型转换来实现泛型.现在有了泛型.通过泛型来指定类型.但是这个泛型也不是真正的泛型.在编译期间都会进行泛型擦除.使其变为普通的类型.下面来看个例子
源代码

ArrayList<Integer> ls = new ArrayList<>();
ls.add(1);
ls.add(2);
int a = ls.get(0);
System.out.println(a);

 

 

编译之后的字节码反编译过来的文件.

ArrayList var1 = new ArrayList();
        var1.add(1);
        var1.add(2);
        int var2 = (Integer)var1.get(0);
        System.out.println(var2);

 

 

这里可以看到这个时候哦ArrayList已经没有泛型了,只是他原来的类型.在获取字段的时候会有一个类型转换的操作.所以有一点就是在进行方法重载的时候ArrayList 和 ArrayList 类型是一样的,编译会出现错误.
也正是有了泛型擦除,就有了一个问题,你可以向一个定义了类型的容器中添加其他类型的变量.看下面代码:

1

4
List<String> ls = new ArrayList<>();
Class<? extends ArrayList> cls = (Class<? extends ArrayList>) ls.getClass();
Method method = cls.getDeclaredMethod("add",Object.class);
method.invoke(ls,10);

 

这里通过反射向list中添加Integer变量,并没有报错,但是,这个时候如果在运行时候调用get()方法,那么如上所说,会在get方法前加入强制类型转换,所以会在运行时期报错.

Integer. Double 自动装箱与拆箱

在java中提供了自动装箱与拆箱的功能,就是把int变成Integer对象或者反过来.因为在泛型中只能存储对象而不能是普通值.而且在Integer或者Long中都有自己的数字缓存.都缓存了从-128~127之间的数字.意思就是在这些数字范围内的Integer对象都引用的是同一个对象.在看一个例子

1


5


9


13

15
public static void main(String[] args) {
    Integer a = 128;
    Integer b = 128;
    Integer c = 1;
    Integer d = 2;
    Integer e = 3;
    Integer f = 3;
    System.out.println(f == e);
    System.out.println(c+d == e );
    System.out.println(a==b);
}
输出:
true
true
false

 

这里可以看到从128开始就不缓存了.但是在128之前的数字都是缓存的.都引用的是同一个缓存的对象.但是在代码中最后还是使用equals来比较对象.这是最稳妥的.

foreach循环遍历

foreach循环遍历是代码中很常见的一个用法,但是他底层是怎么实现的呢,很多人不知道.其实也是很简单的,下面看实例;

1


5


9

12
public static void main(String[] args) {
    ArrayList<Integer> ls = new ArrayList<>();
    // 普通容器变为迭代器遍历
    for (int i: ls){
        System.out.println(i);
    }
    int[] arr = new int[10];
    // foreach循环在数组中变为普通的for遍历循环
    for (int i: arr){
        System.out.println(i);
    }
}

 

上面代码编译过后就能看到

1


5


9


13


17

19
public static void main(String[] var0) {
        ArrayList var1 = new ArrayList();

        Iterator var2 = var1.iterator();
        //这里能看到 循环遍历变成了通过迭代器遍历
        int var3;
        while(var2.hasNext()) {
            var3 = (Integer)var2.next();
            System.out.println(var3);
        }
        int[] var7 = new int[10];
        int[] var3 = var7;
        int var4 = var7.length;
        // 数组的变成了普通的遍历
        for(int var5 = 0; var5 < var4; ++var5) {
            int var6 = var3[var5];
            System.out.println(var6);
        }
    }

 

循环遍历在普通容器中变成了迭代器遍历,在数组中变成了普通的for遍历.这也是为什么循环遍历的容器必须实现iterator接口的原因.

变长参数

变长参数其实就是一个数组,取决于你传入了几个参数.

1


5
public static void close(Closeable... objs) throws IOException {
        for (Closeable obj: objs){
            obj.close();
        }
    }

 

字节码反编译过后的代码

1


5


9
public static void close(Closeable... var0) throws IOException {
        Closeable[] var1 = var0;
        int var2 = var0.length;
        for(int var3 = 0; var3 < var2; ++var3) {
            Closeable var4 = var1[var3];
            var4.close();
        }

    }

 

能够看到实际上objs就是一个数组,应该就是通过得到一个数组,然后在循环遍历.所以说尽量少用变长参数,因为变长参数会有一个内部的数组建立的过程,所以速度肯定会降低.

int short 优化

平时编写代码的时候可能不注意,但是看的话会发现编译器做了很多优化,比如就是int优化,这也是不小心发现的.例如下面的例子

1


5


9

11
public static void main(String[] args) {
        int a = 10;
        int b = 128;
        int c = 255;
        int d = 1000000;
        b+=10000000;
        char g = 100;
        double e = 20;
        double f = 1000000;
        System.out.println(a+b+c+d);
    }

 

编译过后

1


5


9

11
public static void main(String[] var0) {
    byte var1 = 10;    //int a = 10;
    short var2 = 128;   // int b = 128;
    short var3 = 255;  // int c = 255;
    int var4 = 1000000;  // int d = 1000000;
    int var10 = var2 + 10000000;  // b+=10000000;  这里就是当数值变大了之后就会发现新申请了一个数值,之后变量var2的使用都变成了var10.
    boolean var5 = true;  //char g = 100;
    double var6 = 20.0D;  //double e = 20;
    double var8 = 1000000.0D;  //double f = 1000000;
    System.out.println(var1 + var10 + var3 + var4);
}

 

可以看到当int的值比较小的时候可能会用byte或者short来代替int.
当此值变成比这个值更大的值的时候就能发现这个变量变了.var2变成var10,在之后用到var2的地方也都变成了var10.
但是细看这里的编译过后的代码就会发现一个问题,就是在char变量g变成了boolean类型.这里也就是编译器的第二个优化了.这里它会看此变量是否用过,如果在只有的程序中没有用过,那么就会赋值一个boolean类型.因为boolean类型可能是占用内存最小的了.

 

这篇关于java编译过程-解语法糖的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!