Java几种生成synthetic方法的情况

Java编译器将Java代码编译成过程中,会由编译器引入一些类、方法、属性,这些由编译器引入一些类、方法、属性会被标上synthetic字段。本文主要介绍几种编译器会生成synthetic方法的情况

1、泛型

子类在具象化泛型接口、泛型父类的泛型时,由于方法签名不一致,运行时动态分派将会查找不到重写的方法,为解决此问题,编译器会生成一个synthetic bridge方法

public class Base<T> {
    public void f(T t) {
        System.out.println("Base");
    }
}

public class Sub extends Base<String> {

    @Override
    public void f(String s) {
        System.out.println("Sub");
    }
}

public class Main {
    public static void main(String[] args) {
        Base<String> base = new Sub();
        base.f("123");
    }
}
// 输出Sub

Base类编译后,由于泛型擦除,f方法签名将变为f(Ljava/lang/Object;)V,即入参为Object类型。Sub类不存在泛型,所以编译后,f方法的签名将变为f(Ljava/lang/String;)V。由于这两个方法签名不同,Sub#f方法并未真正意义上重写Base#f方法。这就存在一个问题,main方法里调用Base#f时,由于泛型擦除和静态分派,调用方法的签名是f(Ljava/lang/Object;)V;运行时动态分派根据实际类型去查找f(Ljava/lang/Object;)V方法,而Sub里并不存在该方法,顺着类继承层次会从父类中查找到该方法,从而调用父类方法,子类重写的方法失效。
为了解决这个问题,编译器在子类Sub中生成一个签名为f(Ljava/lang/Object;)V的方法,这个方法调用重写的f(Ljava/lang/String;)V方法。
用jclasslib插件查看Base类f方法编译出来的字节码
image.png
image.png
用jclasslib插件查看Sub类f方法编译出来的字节码,存在两个f方法。重写的f方法
image.png
image.png
编译器生成的f方法
image.png
image.png

2、子类重写方法改变方法签名

如上是泛型导致的方法签名不一致,编译器生成一个synthetic bridge方法。子类在重写父类方法时,可以修改方法的返回值,也会导致啊方法签名不一致,也需要编译器生成一个synthetic bridge方法。

public class Base {
    public Number f() {
        return 1.11;
    }
}

public class Sub extends Base {
    @Override
    public Integer f() {
        return 1;
    }
}

public class Main {
    public static void main(String[] args) {
        Base base = new Sub();
        System.out.println(base.f());
    }
}
// 输出1

跟上面类似

3、可见性问题

由于反射设计问题,反射的方法访问权限控制与JVM访问权限控制存在差异,导致能直接调用的方法通过反射调用却存在问题,为解决此种不一致,编译器引入synthetic方法。
https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6342411

// package org.example.refl.a
class Base {
    public void f() {
        System.out.println("Base");
    }
}
public class Sub extends Base {
}

// package org.example.refl
public class Main {
    public static void main(String[] args) {
        Sub sub = new Sub();
        sub.f();
    }
}

// package org.example.refl
public class Main {
    public static void main(String[] args) throws Exception {
        Method f = Sub.class.getMethod("f");
        f.invoke(new Sub());
    }
}

org.example.refl.a包下面定义包可见的Base类和public可见的Sub类。Base类的f方法对Main类不可见,但对Sub可见;Sub类对Main类可见。 通过方法调用和反射两种方式调用Sub#f方法。
直接方法调用时,main方法编译成如下
image.png
invokevirtual的方法访问权限控制涉及三个阶段:验证、解析、执行阶段,本节问题只涉及解析阶段中的方法解析。解析就是将符号引用转换成常量池中的具体值引用。 方法解析将reference method解析为resolved method。reference method即invokevirtual指令操作数指定的方法,resolved method即常量池中的某个方法。方法解析首先按照类继承层次根据符号引用查找方法,然后校验权限。由于符号引用指定的方法可能不存在(继承父类获得的方法),导致reference method解析为resolved method不是同一个方法,但是校验权限仍按照reference method校验,所以Main类中可以调用org/example/refl/a/Sub.f。
通过反射调用方法时,实际上需要reflect包去实现JVM同样的权限校验逻辑,此时存在了不一致。由于Sub类没有f方法,Sub.class.getMethod(“f”)实际上会获得到Base#f方法,此时校验权限则不通过,因为Main中不能访问Base#f方法。为解决此问题,编译器在Sub类中加入了synthetic方法,在这个方法中调用Base#f方法。此时Sub.class.getMethod(“f”)拿到的方法是这个synthetic方法,不存在访问权限问题。
image.png
image.png

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值