JVM 语法糖(对不同的语法进行分析)

语法糖

所谓的 语法糖 ,其实就是指 java 编译器把 *.java 源码编译为 *.class 字节码的过程中,自动生成和转换的一些代码,主要是为了减轻程序员的负担,算是 java 编译器给我们的一个额外福利。

注意,以下代码的分析,借助了 javap 工具,idea 的反编译功能,idea 插件 jclasslib 等工具。另外,编译器转换的结果直接就是 class 字节码,只是为了便于阅读,给出了 几乎等价 的 java 源码方式,并不是编译器还会转换出中间的 java 源码。

默认构造

在java文件当中进行编写一个不提供构造方法,在进行编译的时候会自动加上一个默认无参构造方法。使用javac命令进行编译,编译后如下:

在这里插入图片描述
自动拆装箱

这个特性是 JDK 5 开始加入的,也就是基本数据类型和包装数据类型之间是可以进行相互转换的,也就是说如下代码所示:这种事可以进行编译通过的,而在jdk5版本之前要自己进行数据类型转换。

在这里插入图片描述
泛型集合

泛型也是在 JDK 5 开始加入的特性,但 java 在编译泛型代码后会执行 泛型擦除 的动作,即泛型信息在编译为字节码之后就丢失了,实际的类型都当做了 Object 类型来处理:

package Class_load.init;

import java.util.ArrayList;
public class generic_paradigm {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(10);
        Integer x = list.get(0);
    }
}

编译后的字节码

         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: invokevirtual #5                  // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
        17: pop
        18: aload_1
        19: iconst_0
        20: invokevirtual #6                  // Method java/util/ArrayList.get:(I)Ljava/lang/Object;
        23: checkcast     #7                  // class java/lang/Integer
        26: astore_2
        27: return

可以看到这这里我们的int和integer类型之间不需要自动转换,而且在这里进行插入、取出都是用object对象进行,在23行字节码当中,checkcast表示将object进行强制类型转换Integer。

擦除的是字节码上的泛型信息,可以看到 LocalVariableTypeTable 仍然保留了方法参数泛型的信息

使用反射,仍然能够获得这些信息:如下代碼所示:

//添加一個方法
    public Set<Integer> test(List<Integer> list, Map<String, Integer> map) {
        return null;
    }
// 在main當中進行測試
        Method test = generic_paradigm.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[] arguments = parameterizedType.getActualTypeArguments();
                for (int i = 0; i < arguments.length; i++) {
                    System.out.printf("泛型参数[%d] - %s\n", i, arguments[i]);
                }
            }
        }

查看輸出:

在这里插入图片描述
可变参数

可变参数也是 JDK 5 开始加入的新特性: 例如:

public class change_true {
    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 ,从代码中的赋值语句中就可以看出来。
如果调用了 foo() 则等价代码为 foo(new String[]{}) ,创建了一个空的数组,而不会传递 null 进去。

foreach 循环

仍是 JDK 5 开始引入的语法糖,数组的循环:

public class foreach {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5};
        for (int i : arr) {
            System.out.println(i);
        }
    }
}

对这个代码进行编译后,新增的语法糖,在数组初始化和for循环增强都会进行编译过来:

int[] array = new int[]{1, 2, 3, 4, 5};
	for(int i = 0; i < array.length; ++i) { 
		int e = array[i];
		System.out.println(e);
	}

对简单的数组进行循环是直接使用for循环进行遍历,而对List集合进行for循环增强又会编译成什么呢?如下List集合,

        List<Integer> list = Arrays.asList(1,2,3,4,5);
        for (Integer j :list){
            System.out.println(j);
        }

在这里会被编译器转换为对迭代器的调用:

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); 
Iterator iter = list.iterator();
 while(iter.hasNext()) { 
 	Integer e = (Integer)iter.next(); 
 	System.out.println(e); 
 }

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

Switch case 分支

从 JDK 7 开始,switch 可以作用于字符串和枚举类,这个功能其实也是语法糖,例如:

    public static void choose(String str) {
        switch (str) {
            case "1":
                System.out.println("this is one");
                break;
            case "2":
                System.out.println("this is two");
                break;
        }
    }

    public static void main(String[] args) {
        choose("1");
    }

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

为什么第一遍时必须既比较 hashCode,又利用 equals 比较呢?hashCode 是为了提高效率,减少可能的比较;而 equals 是为了防止 hashCode 冲突,

在这里插入图片描述
Switch enum 枚举 和 枚举类

使用一个switch 枚举的例子,原始代码:

package Class_load.init;

public class switch_enum {
    enum Sex {
        men, women
    }

    public static void foo(Sex sex) {
        switch (sex) {
            case men:
                System.out.println("男");
                break;
            case women:
                System.out.println("女");
                break;
        }
    }

    public static void main(String[] args) {
        foo(Sex.men);
    }
}

编译代码得到class文件,进行查看,可以发现枚举当中有了一个构造方法,也就是说在这里枚举被当成了一个类。并且修饰符使用的是private,避免使用该类时被创建新的对象。

在这里插入图片描述
try-with-resources

JDK 7 开始新增了对需要关闭的资源处理的特殊语法 try-with-resources。

其中资源对象需要实现 AutoCloseable 接口,例如 InputStream 、 OutputStream 、 Connection 、 Statement 、 ResultSet 等接口都实现了 AutoCloseable ,使用 try-with- resources 可以不用写 finally 语句块,编译器会帮助生成关闭资源代码,例如:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class resource {
    public static void main(String[] args) {
        try (InputStream is = new FileInputStream("d:\\1.txt")) {
            System.out.println(is);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

查看对应的class文件,

在这里插入图片描述
为什么要设计一个 addSuppressed(Throwable e) (添加被压制异常)的方法呢?是为了防止异常信息的丢失(想想 try-with-resources 生成的 fianlly 中如果抛出了异常):

public class resource_catch {
    public static void main(String[] args) {
        try (MyResource resource = new MyResource()) {
            int i = 1 / 0;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class MyResource implements AutoCloseable {
    public void close() throws Exception {
        throw new Exception("close 异常");
    }
}

查看抛出的异常:异常没有丢失,两个异常都被抛出。

在这里插入图片描述

方法重写时的桥接方法

方法重写时对返回值分两种情况:

  • 父子类的返回值完全一致
  • 子类返回值可以是父类返回值的子类

匿名内部类

为什么匿名内部类引用局部变量时,局部变量必须是 final 的:因为在创建类对象时,将 x 的值赋值给了类对象的val。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Modify_QmQ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值