Java编译器 - 语法糖

倘只看书,便变成书橱

本文总结了一部分java编译期的语法糖。为了便于理解,本文不使用java字节码来推论;采用源代码和编译器编译后的伪代码来直观的表达。理解java在编译成class文件时,实际上编译器会给我们如何传递给jvm

文章中每个语法糖都有如下两段代码,第一个源代码,第二个编译器解析后的伪代码,读者可根据两段代码的差异性,理解语法糖,格式如下:

//源代码
//编译器解析后的伪代码

一、默认构造器

当我们创建一个类,如果没有编写任何构造器,编译器默认添加一个无参构造器

public class ClassName{
}
public class ClassName{
    public ClassName(){
        super();
    }
}

二、自动拆/装箱

装箱:就是自动将基本数据类型转换为包装器类型;

拆箱:就是自动将包装器类型转换为基本数据类型;

1、编译器通过Integer.valueOf()实现基本数据类型的装箱

2、编译器通过integer.intValue()实现包装器类型的拆箱

3、如果用==比较,且一个是包装类型,一个是基本数据类型,会将包装类型拆箱

4、如果用equals比较,且一个是包装类型,一个是基本数据类型,会将基本类型装箱

public class ClassName{
    public static void main(String[] args) {
        Integer a = 1;
        int b = a;
        boolean c = a == b;
        boolean d = a.equals(b);
    }
}
public class ClassName{
    public static void main(String[] args) {
        Integer a = Integer.valueOf(1);
        int b = a.intValue();
        boolean c = a.intValue() == b; //需要注意NPE
        boolean d = a.equals(Integer.valueOf(b));
    }
}

三、范型擦除 

JDK1.5开始

编译器会在编译后将范型擦除掉,但是会将范型信息存放在字节码文件模型的LocalVariableTypeTable 中

public class ClassName{
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        Integer value = list.get(0);
    }
}
public class ClassName{
    public static void main(String[] args) {
        List list = new ArrayList<>();
        list.add(Integer.valueOf(1));
        Integer value = (Integer) list.get(0);
    }
}

由于范型信息被存放在了字节码文件的LocalVariableTypeTable 中 ,在方法的形参和返回值上的范型,我们可以通过反射拿到


public Set<String> method(Map<Integer,Object> map){
     return null;
} 

public static void main(String[] args) throws NoSuchMethodException {
        Method method = ClassName.class.getMethod("method", Map.class);
        //获取参数类型
        Type[] parameterTypes = method.getGenericParameterTypes();
        ParameterizedType parameterType = (ParameterizedType)parameterTypes[0];
        Type mapType = parameterType.getRawType(); //interface java.util.Map
        //获取参数范性
        Type[] actualTypeArguments = parameterType.getActualTypeArguments();
        Type integerType = actualTypeArguments[0]; //class java.lang.Integer
        Type objectType = actualTypeArguments[1];  //class java.lang.Object

        //获取返回值类型
        ParameterizedType returnType = (ParameterizedType)method.getGenericReturnType();
        Type setType = returnType.getRawType(); //interface java.util.Set
        //获取返回值范性
        Type stringType = returnType.getActualTypeArguments()[0]; //class java.lang.String
    }

 四、可变参数

可变长参数的定义:使用...表示可变长参数

例如 print(String... args){ ... }

在具有可变长型参的方法中可以把参数当成数组使用


    public static void method(String... args){
        String[] arr = args;
    }

    public static void main(String[] args) {
        method("a","b","c");
        method();
    }
    public static void method(String[] args){
       String[] arr = args;
    }


    public static void main(String[] args) {
        method(new String[]{"a","b","c"});
        method(new String[]{});  //无参传空数组
    }

 五、forech

forech根据迭代对象的类型分为两种

数组

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

集合

    List<Integer> list = Arrays.asList(1,2);
    for (Integer e : list) {
        System.out.println(e);
    }
   List list = Arrays.asList(Integer.valueOf(1),Integer.valueOf(2));
   Iterator iterator = list.iterator();
   while(iterator.hasNext()){
       Integer e = (Integer) iterator.next();
       System.out.println(e);
   }

六、switch的字符串

switch的实现是通过跳转指令或跳转表实现的, 这取决于分支的长度

值1代码入口地址
值2代码入口地址
值3代码入口地址

跳转表的特性在于key是整数且有序的,可以通过二分法快速匹配到,但JDK7开始,switch可以使用字符串和枚举。字符串与整形之前如何转换?这个功能实际上是语法糖来实现的。

    public static void method(String str){
        switch (str){
            case "Aa":
                System.out.println("Aa");
                break;
            case "AA":
                System.out.println("AA");
                break;
            case "BB":
                System.out.println("BB");
                break;
        }
    }
    public static void method(String str){
        byte x = -1;
        switch (str.hashCode()){
            case 2112:
                if(str.equals("Aa")){
                    x = 0;
                }else if(str.equals("BB")){
                    x = 1;
                }
                break;
            case 2080:
                if(str.equals("AA")){
                    x=2;
                }
                break;
            default:
        }
        switch (x){
            case 0:
                System.out.println("Aa");
                break;
            case 1:
                System.out.println("BB");
                break;
            case 2:
                System.out.println("AA");
                break;
            default:
        }
    }

七、switch的枚举

    public static void method(SexEnum sexEnum){
        switch (sexEnum){
            case BOY: break;
            case GIRL:break;
        }
    }
    //在本类中生成一个静态的内部类,仅jvm识别
    synthetic static class $MAP{
        //将使用到的枚举,所有的选项放入一个数组中和switch的int值对应
        synthetic static final int[] map = new int[SexEnum.values().length];
        static{
            map[SexEnum.BOY.ordinal()] = 1;
            map[SexEnum.GIRL.ordinal()] = 2;
        }
    }
    public static void method(SexEnum sexEnum){
        //使用枚举的ordinal取出int值对应
        switch (map[sexEnum.ordinal()]){
            case 1: break;
            case 2: break;
        }
    }

八、枚举类

jdk7开始,枚举实际是一个继承了Enum的类

enum Sex{
    BOY(100),
    GIRL(200);
    private Integer code;
    Sex(Integer code){
        this.code = code;
    }
}

final class Sex extends Enum<Sex>{
    public static final Sex BOY;
    public static final Sex GIRL;
    public static final Sex[] $VALUE;
    static {
        BOY = new Sex("BOY",0,100);
        GIRL = new Sex("GIRL",1,200);
        $VALUE = new Sex[]{BOY,GIRL};
    }
    private Integer code;

    private Sex(String name, Integer ordinal,Integer code){
        super(name,ordinal);
        this.code = code;
    }
    public static Sex[] values(){
        return $VALUE.clone();
    }
    public static Sex valueOf(String name){
        return Enum.valueOf(Sex.class,name);
    }
}

九、重写方法 - 方法桥接

abstract class A{
    public abstract Number m();
}
class B extends A{
    @Override
    public Integer m() {
        return null;
    }
}
abstract class A{
    public abstract Number m();
}
class B extends A{
    public Integer m() {
        return null;
    }
    /*编译器生成桥接方法,这个方法我们不可见由jvm执行
    这个方法才是真正的实现父类的方法
    这个方法不受同名限制
    这个方法直接调用子类的同名方法
    */
    @Override
    public synthetic bridge Number m(){
       return m();
    }
}

通过打印B类的方法,我们可以验证

    for (Method declaredMethod : B.class.getDeclaredMethods()) {
        System.out.println(declaredMethod);
    }

打印出来两个方法 

public java.lang.Integer utils.B.m()
public java.lang.Number utils.B.m()

十、内部类

成员内部类

class A{
    private String filed;
    class B{
        public void test(){
            //内部类引用了外部类的成员变量
            System.out.println(filed);
        }
    }
}

class A{
    private String filed;
    //将权限暴露
    static String filed0(A a){
        return a.filed;
    }
}
class B{
    //持有外部类的引用
    private A a;
    //创建外部类,需要传递外部类的引用
    public B(A a){
        this.a = a;
    }
    public void test(){
        System.out.println(A.filed0(a));
    }
}

可以看出:外部类和内部类都有相应的改动

1、在外部类存在一个公用的静态方法,将字段权限暴露出来

2、在内部类创建时,会通过构造器将外部类对象注入到自身成员变量中

3、内部类调用外部类的静态方法,获取到私有属性的值

方法内部类

class A{
    public void testA(String name){
        class B{
            public void testB(){
                //内部类引用了方法的局部变量
                System.out.println(name);
            }
        }
        new B().testB();
    }
}
class A{
    public void testA(String name){
        //编译器注入
        new B(this,name).testB();
    }
}
class B{
    private A a;
    private String name;
    
    //在创建内部类时,将引用的外部方法的局部变量作为内部的成员变量
    public B(A a,String name){
        this.a = a;
        this.name = name;
    }
    
    public void testB(){
        //实际是用的自己的成员变量
        System.out.println(name);
    }
}

其内部类的构造器上相对于成员内部类基础上多了局部变量的入参,并将其作为成员变量。

所以为什么方法内部类调用的变量要用final修饰?因为在内部类对象创建时就确定一切了。外面改了,里面就不一致了。

* 内部类中存在指针指向外部类对象,所以当大量使用 new Object(){{}}这样的语法时,要注意是否可能存在内存泄漏的危险

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值