JVM学习笔记之四

13 篇文章 0 订阅

3.编译期处理

所谓的语法糖,其实就是指java编译器把*.java源码编译为*.class字节码的过程中,自动生成和转换的一些代码,主要为了减轻程序员的负担,算是java编译器给我们的一个额外福利(给糖吃嘛)
注意,以下代码的分析,借助了javap工具,IDEA的反编译功能,IDEA插件jclasslib等工具。另外,编译器转换的结果直接就是class字节码,只是为了便于阅读,给出了几乎等价的java源码方式,并不是编译器还会转换中间的java源码,切记。

3.1默认构造器

public class Candy1{
}
//编译成class后的代码
public class Candy1 {
	//这个无参构造是编译器帮助我们加上的
    public Candy1() {
    	//super();//即调用父类Object的无参构造方法
    	//java/lang/Object."<init>":()v
    }
}

3.2 自动拆装箱

  • 这个特性是JDK 5开始加入的,代码片段1:
public class Candy2{
	public static void main(String[] args){
        Integer x = 1;
        int y = x;
    }
}
  • 这段代码在JDK 5 之前是无法编译通过的,必须改写为,代码片段2:
public class Candy2{
    public static void main(String[] args){
        Integer x = Integer.valueOf(1);
        int y = x.intValue();//拆箱
    }
}
  • 显然之前版本的代码太麻烦了,需要在基本类型和包装类型之间来回转换(尤其是集合类中操作的都是包装类型),因为这些转换的事情在JDK 5 以后都由编译器在编译阶段完成。即 代码片段1 都会在编译阶段被转换为代码片段2

3.3泛型集合取值

  • 泛型也是JDK 5 开始加入的特性,但java在编译泛型代码后会执行泛型擦除的动作,即泛型信息在编译为字节码之后就会丢掉,实际的类型都当作Object类型处理(有一些不会被擦除):
import java.util.ArrayList;
import java.util.List;

public class Candy3{
    public static void main(String[] args){
        List<Integer> list = new ArrayList<>();
        list.add(10); // 实际调用的是List.add(Object e);
        Integer x = list.get(0); // 实际调用的是 Object obj = List.get(int index);
    }
}
  • 所以在取值时,编译器真正生成的字节码中,还要额外做一个类型转换的操作
// 需要将 Object 转为 Integer
Integer x = list.get(0);
  • 如果前面的 x 变量类型修改为 int 基本类型,那么最终生成字节码为:
// 需要将 Object 转为 Integer,并执行拆箱操作
int = ((Integer)list.get(0)).inValue();
  • JDK 5 后就不用我们程序员
    上面代码的字节码:
public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: new           #2                  // class java/util/ArrayList
         3: dup
         4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
         7: astore_1
         8: aload_1
         9: bipush        10
        11: invokestatic  #4                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        14: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
        19: pop
        20: aload_1
        21: iconst_0
        22: invokeinterface #6,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
        27: checkcast     #7                  // class java/lang/Integer
        30: astore_2
        31: return
      LineNumberTable:
        ...
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      32     0  args   [Ljava/lang/String;
            8      24     1  list   Ljava/util/List;
           31       1     2     x   Ljava/lang/Integer;
      LocalVariableTypeTaVable:
        ...

  • 擦除的是字节码上的泛型信息,可以看到LocalVariableTable仍然保留了方法参数的泛型信息
  • 使用反射,仍然能够获得这些信息
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class Candy4{
    public static void main(String[] args) throws NoSuchMethodException {
        Method test = Candy4.class.getMethod("test", List.class, Map.class);
        Type[] types = test.getGenericParameterTypes();
        for (Type type : types) {
            if (type instanceof ParameterizedType){
                ParameterizedType parameterizedType = (ParameterizedType) type;
                System.out.println("原始类型 - " + parameterizedType.getRawType());
                Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                for (int i = 0; i < actualTypeArguments.length; i++) {
                    System.out.printf("泛型参数[%d] - %s\n", i, actualTypeArguments[i]);
                }
            }
        }
    }

    public static Set<Integer> test(List<String> list, Map<Integer, String> map){
        return new HashSet<>();
    }
}
=================输出========================
原始类型 - interface java.util.List
泛型参数[0] - class java.lang.String
原始类型 - interface java.util.Map
泛型参数[0] - class java.lang.Integer
泛型参数[1] - class java.lang.String

3.4可变参数

  • 可变参数也是JDK 5 开始加入的新特性
    例如:
public class Candy5{
    public static void foo(String... args){
        String[] array = args; // 直接赋值
        System.out.println(array);
    }

    public static void main(String[] args) {
        foo("Hello", "World");
    }
}
  • 可变参数String… args 其实是一个String[] args,从代码中的赋值语句就可以看出来。
  • 同样java编译器会在编译期间将上述代码变换为:
public class Candy5{
    public static void foo(String[] args){
        String[] array = args; // 直接赋值
        System.out.println(array);
    }

    public static void main(String[] args) {
        foo("Hello", "World");
    }
}
  • 两中方法的字节码(可以发现是一样的)
 public static void foo(java.lang.String...);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS
    Code:
      stack=2, locals=2, args_size=1
         0: aload_0
         1: astore_1
         2: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         5: aload_1
         6: invokevirtual #3                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
         9: return
      LineNumberTable:
        ...
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  args   [Ljava/lang/String;
            2       8     1 array   [Ljava/lang/String;

=====================================
public static void foo(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: aload_0
         1: astore_1
         2: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         5: aload_1
         6: invokevirtual #3                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
         9: return
      LineNumberTable:
        ...
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  args   [Ljava/lang/String;
            2       8     1 array   [Ljava/lang/String;
===================注意====================
如果调用了foo()则等价代码为foo(new String[]),创建了一个空的数组,而不会传递null进去

3.5foreach循环

  • 依然是JDK 5 开始引入的语法糖,数组循环:
public class Candy6{

    public static void main(String[] args){
        int[] array = {1, 2, 3, 4, 5}; // 这种初始化写法也是语法糖
        for (int i : array) {
            System.out.println(i);
        }
    }
}
  • 会被编译器转换为
public class Candy6 {
    public Candy6() {
    }

    public static void main(String[] args) {
        int[] array = new int[]{1, 2, 3, 4, 5};
        int[] var2 = array;
        int var3 = array.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            int i = var2[var4];
            System.out.println(i);
        }

    }
}
  • 集合:
import java.util.Arrays;
import java.util.List;

public class Candy7{
    public static void main(String[] args){
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        for (int i : list) {
            System.out.println(i);
        }
    }
}
  • 编译为 迭代器调用
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

public class Candy7 {
    public Candy7() {
    }

    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        Iterator var2 = list.iterator();

        while(var2.hasNext()) {
            int i = (Integer)var2.next();
            System.out.println(i);
        }

    }
}

注意
foreach循环写法,能够配合数组以及所有实现了Iterator接口的集合类一起使用,其中Iterator用来获取集合的迭代器(Iterator

3.6 switch 字符串

  • 从JDK 7 开始,switch可以作用于字符串和枚举类,这个功能其实也是语法糖,例如:
public class Candy8{
    public static void choose(String str){
        switch (str){
            case "Hello":{
                System.out.println("H");
                break;
            }
            case "World":{
                System.out.println("W");
                break;
            }
        }
    }
}

注意
switch 配合 String 和枚举使用时,变量不能为null,原因分析完语法糖转换后的代码应当自然清楚

  • 编译为:
public class Candy8 {
    public Candy8() {
    }

    public static void choose(String str) {
        byte var2 = -1;
        switch(str.hashCode()) {
        case 69609650: // Hello 的 hashCode
            if (str.equals("Hello")) {
                var2 = 0;
            }
            break;
        case 83766130: // World 的 hashCode
            if (str.equals("World")) {
                var2 = 1;
            }
        }

        switch(var2) {
        case 0:
            System.out.println("H");
            break;
        case 1:
            System.out.println("W");
        }

    }
}
===================================================
可以看出,执行了两遍switch,第一遍是根据字符串的 hashCode 和 equals 将字符串转换为相应的byte类型,第二遍才是利用byte执行进行比较

为什么第一遍时必须比较 hashCode ,又利用 equals 比较呢?hashCode 是为了提高效率,减少可能的比较,而 equals 是为了解决 hashCode 冲突,例如 BM 和 C. ,这两个字符串的 hashCode 值都是 2123,例如:
===================================================
public class Candy9{
    public static void choose(String str){
        switch (str){
            case "C.":{
                System.out.println("C");
                break;
            }
            case "BM":{
                System.out.println("BW");
                break;
            }
        }
    }
}
=========================编译为=============================
public class Candy9 {
    public Candy9() {
    }

    public static void choose(String str) {
        byte var2 = -1;
        switch(str.hashCode()) {
        case 2123: // hashCode 冲突
            if (str.equals("BM")) {
                var2 = 1;
            } else if (str.equals("C.")) {
                var2 = 0;
            }
        default:
            switch(var2) {
            case 0:
                System.out.println("C");
                break;
            case 1:
                System.out.println("BW");
            }

        }
    }
}

3.7 switch 枚举

  • switch枚举的例子,原始代码:
enum Sex {
    MALE,
    FEMALE;
}
public class Candy10 {
    public static void sexChoose(Sex sex) {
        switch (sex) {
            case MALE:
                System.out.println("MALE");
                break;
            case FEMALE:
                System.out.println("FEMALE");
                break;
        }
    }
}
=============编译后========================
public class Candy10 {
    public Candy10() {
    }

    public static void sexChoose(Sex sex) {
        switch(sex) {
        case MALE:
            System.out.println("MALE");
            break;
        case FEMALE:
            System.out.println("FEMALE");
        }

    }
}
  • 这里有另一个编译的版本你,但是我编译后没出现,仅供参考
    在这里插入图片描述

3.8 枚举

  • JDK 7 新增了枚举类,以前面的性别枚举为例:
enum Sex {
    MALE,
    FEMALE;
}
==========编译后==============
enum Sex {
    MALE,
    FEMALE;

    private Sex() {
    }
}
  • 另一个版本,仅供参考
    在这里插入图片描述
    在这里插入图片描述

3.9 try-with-resources

  • JDK 7 开始新增了对需要关闭资源处理的特殊语法 try-with-resources
try(资源变量 = 创建资源对象){
}catch(){
}
  • 其中资源对象需要实现 AutoCloseable 接口,例如 InputStreamOutputStreamConnectionStatnmentResultSet等接口都实现了,使用try-with-resources可以不用写finally语句块,编译器会帮助生成关闭资源代码,例如:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class Candy11 {
    public static void main(String[] args) throws FileNotFoundException {
        try(InputStream is = new FileInputStream("./1.txt")){
            System.out.println(is);
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}
=================编译后=================================
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class Candy11 {
    public Candy11() {
    }

    public static void main(String[] args) throws FileNotFoundException {
        try {
            InputStream is = new FileInputStream("./1.txt");
            Throwable var2 = null;

            try {
                System.out.println(is);
            } catch (Throwable var12) {
            	// var2 是我们代码出现的异常
                var2 = var12;
                throw var12;
            } finally {
            	// 判断了资源不为空
                if (is != null) {
                	// 如果代码有异常
                    if (var2 != null) {
                        try {
                            is.close(); // 关闭
                        } catch (Throwable var11) {
                        	// 如果 close 出现了异常,作为被压制异常添加
                            var2.addSuppressed(var11);
                        }
                    } else {
                    	// 如果我们代码没有异常,close 出现了异常就是最后的 catch 块中的 e
                        is.close(); // 关闭
                    }
                }

            }
        } catch (IOException var14) {
            var14.printStackTrace();
        }

    }
}

  • 为什么要设计一个 addSuppressed(Exception e);(添加被压制异常)的方法呢?是为了防止异常信息的丢失(想想 try-with-resources生成的finally中如果抛出了异常):
public class Candy12 {
    public static void main(String[] args) {
        try (MyResource myResource = new MyResource()){
            int i = 1/0;
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

class MyResource implements AutoCloseable{

    @Override
    public void close() throws Exception {
        throw new Exception("close 异常");
    }
}
===============运行输出=====================
java.lang.ArithmeticException: / by zero
	at Candy12.main(Candy12.java:5)
	Suppressed: java.lang.Exception: close 异常
		at MyResource.close(Candy12.java:16)
		at Candy12.main(Candy12.java:6)
============================================
如以上代码所示,两个异常信息都不会丢失

3.10 方法重写的桥接方法

  • 我们都知道,方法重写时对返回值分两种情况:
  1. 父子类的返回值完全一致
  2. 字类返回值可以是父类返回值的字类(比较绕口,见下面例子)
class A {
    public Number m(){
        return 1;
    }
}

class B extends A {
    // 子类 m 方法的返回值 Integer 是 Number 的子类
    @Override
    public Integer m() {
        return 2;
    }
}
==============编译后=======================

在这里插入图片描述
在这里插入图片描述

3.11 匿名内部类

  • 代码片段
public class Candy13 {

    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("ok");
            }
        };
    }
}
===============编译后====================
public class Candy13 {
    public Candy13() {
    }

    public static void main(String[] args) {
        Runnable var10000 = new Runnable() {
            public void run() {
                System.out.println("ok");
            }
        };
    }
}
  • 另一个版本
    在这里插入图片描述
  • 引用局部变量的匿名内部类,代码片段
public class Candy14 {

    public static void test(final int x){
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("ok" + x);
            }
        };
    }
}
============编译后======================
public class Candy14 {
    public Candy14() {
    }

    public static void test(final int x) {
        Runnable var10000 = new Runnable() {
            public void run() {
                System.out.println("ok" + x);
            }
        };
    }
}
  • 另一个版本
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值