合成和桥接方法

如果您曾经玩过反射并执行了getDeclaredMethods()您可能会感到惊讶。 您可能会获得源代码中不存在的方法。 或者,也许您看了一些方法的修饰符,发现其中一些特殊方法是易变的。 顺便说一句:对于Java采访来说,这是一个令人讨厌的问题,“当方法易变时,这意味着什么?” 正确的答案是,方法不能是易变的。 同时,在getDeclaredMethods()甚至getMethods()返回的方法中,可能存在某些方法,其中Modifier.isVolatile(method.getModifiers())为true。

是项目转换器用户之一发生的 。 他意识到,交换器(本身会深入挖掘Java的黑暗细节)生成的Java源代码无法使用关键字volatile作为方法的修饰符进行编译。 结果,它也不起作用。

那里发生了什么事? 桥接和合成方法是什么?

能见度

创建嵌套或嵌入式类时,可以从顶级类访问嵌套类的私有变量和方法。 这由不可变的嵌入式构建器模式使用 。 这是语言规范中定义的Java的明确定义的行为。

JLS7,6.6.1确定可访问性
…如果成员或构造函数被声明为私有,则访问为
当且仅当它出现在顶级类的主体中时才允许(第7.6节)
包含成员或构造函数的声明…

package synthetic;

public class SyntheticMethodTest1 {
    private A aObj = new A();

    public class A {
        private int i;
    }

    private class B {
        private int i = aObj.i;
    }

    public static void main(String[] args) {
        SyntheticMethodTest1 me = new SyntheticMethodTest1();
        me.aObj.i = 1;
        B bObj = me.new B();
        System.out.println(bObj.i);
    }
}

JVM如何处理它? JVM不知道内部或嵌套类。 对于JVM,所有类都是顶级外部类。 所有类都被编译为顶级类,这就是那些不错的方法...$. .class ...$. .class文件已创建。

$ ls -Fart
../                         SyntheticMethodTest2$A.class  MyClass.java  SyntheticMethodTest4.java  SyntheticMethodTest2.java
SyntheticMethodTest2.class  SyntheticMethodTest3.java     ./            MyClassSon.java            SyntheticMethodTest1.java

如果创建嵌套类或内部类,它将被编译为完整的顶级类。

外层如何提供私有字段? 如果这些人进入了真正的顶级阶级并且是私人的,那么他们将如何从外部阶级中获得呢?

javac解决此问题的方式是,对于任何私有字段但从顶级类使用的字段,方法或构造函数,它都会生成综合方法。 这些合成方法用于到达原始私有字段/方法/构造函数。 这些方法的生成以巧妙的方式完成:仅生成真正需要并从外部使用的那些方法。

package synthetic;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class SyntheticMethodTest2 {

    public static class A {
        private A(){}
        private int x;
        private void x(){};
    }

    public static void main(String[] args) {
        A a = new A();
        a.x = 2;
        a.x();
        System.out.println(a.x);
        for (Method m : A.class.getDeclaredMethods()) {
            System.out.println(String.format("%08X", m.getModifiers()) + " " + m.getName());
        }
        System.out.println("--------------------------");
        for (Method m : A.class.getMethods()) {
            System.out.println(String.format("%08X", m.getModifiers()) + " " + m.getReturnType().getSimpleName() + " " + m.getName());
        }
        System.out.println("--------------------------");
        for( Constructor<?> c : A.class.getDeclaredConstructors() ){
            System.out.println(String.format("%08X", c.getModifiers()) + " " + c.getName());
        }
    }
}

由于生成的方法的名称取决于实现方式,因此不能保证对上述程序的输出所能说的最多的是,在我执行该程序的特定平台上,它产生了以下输出:

2
00001008 access$1
00001008 access$2
00001008 access$3
00000002 x
--------------------------
00000111 void wait
00000011 void wait
00000011 void wait
00000001 boolean equals
00000001 String toString
00000101 int hashCode
00000111 Class getClass
00000111 void notify
00000111 void notifyAll
--------------------------
00000002 synthetic.SyntheticMethodTest2$A
00001000 synthetic.SyntheticMethodTest2$A

在上面的程序中,我们为字段x赋值,并且还调用了相同名称的方法。 需要这些来触发编译器生成综合方法。 您可以看到它生成了三种方法,大概是字段x的setter和getter以及方法x()的综合方法。 但是,这些综合方法未在getMethods()返回的下一个列表中列出,因为它们是综合方法,因此不适用于通用调用。 从这种意义上讲,它们是私有方法。

十六进制数字可以用作解释器,查看类java.lang.reflect.Modifier定义的常量:

00001008 SYNTHETIC|STATIC
00000002 PRIVATE
00000111 NATIVE|FINAL|PUBLIC
00000011 FINAL|PUBLIC
00000001 PUBLIC
00001000 SYNTHETIC

列表中有两个构造函数。 有一个私人的和一个合成的。 私有存在,因为我们定义了它。 另一方面,合成的存在是因为我们从外部调用了私有的。 到目前为止,桥接方法还没有。

泛型和继承

到目前为止还不错,但是我们仍然没有看到任何“易变”的方法。

查看java.lang.reflec.Modifier的源代码,您会看到常量0x00000040定义了两次。 一次是VOLATILE ,一次是BRIDGE (后者是私有程序包,不用于一般用途)。

要拥有这样一种方法,一个非常简单的程序就可以做到:

package synthetic;

import java.lang.reflect.Method;
import java.util.LinkedList;

public class SyntheticMethodTest3 {

    public static class MyLink extends LinkedList<String> {
        @Override
        public String get(int i) {
            return "";
        }
    }

    public static void main(String[] args) {

        for (Method m : MyLink.class.getDeclaredMethods()) {
            System.out.println(String.format("%08X", m.getModifiers()) + " " + m.getReturnType().getSimpleName() + " " + m.getName());
        }
    }
}

我们有一个链表,该链表的方法get(int)返回String 。 我们不要讨论干净的代码问题。 这是演示该主题的示例代码。 干净的代码中也会出现相同的问题,尽管更复杂,并且在导致问题时更难指出问题所在。

输出显示:

00000001 String get
00001041 Object get

我们有两个get()方法。 一个出现在源代码中,另一个出现在合成和桥接中。 反编译器javap表示生成的代码是:

public java.lang.String get(int);
  Code:
   Stack=1, Locals=2, Args_size=2
   0:   ldc     #2; //String
   2:   areturn
  LineNumberTable:
   line 12: 0

public java.lang.Object get(int);
  Code:
   Stack=2, Locals=2, Args_size=2
   0:   aload_0
   1:   iload_1
   2:   invokevirtual   #3; //Method get:(I)Ljava/lang/String;
   5:   areturn

有趣的是,这两种方法的签名是相同的,只是返回类型不同。 即使在Java语言中这是不可能的,但在JVM中允许这样做。 bridge方法不执行其他任何操作,而是调用原始方法。

为什么需要这种合成方法? 谁来使用它。 例如,想要使用类型MyLink的变量来调用方法get(int)MyLink

List<?> a = new MyLink();
        Object z = a.get(0);

它不能调用返回String的方法,因为List没有这样的方法。 为了使其更具说明性,让我们重写方法add()而不是get()

package synthetic;

import java.util.LinkedList;
import java.util.List;

public class SyntheticMethodTest4 {

    public static class MyLink extends LinkedList<String> {
        @Override
        public boolean add(String s) {
            return true;
        }
    }

    public static void main(String[] args) {
        List a = new MyLink();
        a.add("");
        a.add(13);
    }
}

我们可以看到桥接方法

public boolean add(java.lang.Object);
  Code:
   Stack=2, Locals=2, Args_size=2
   0:   aload_0
   1:   aload_1
   2:   checkcast       #2; //class java/lang/String
   5:   invokevirtual   #3; //Method add:(Ljava/lang/String;)Z
   8:   ireturn

不仅叫原版。 它还检查类型转换是否正确。 这是在运行时完成的,而不是由JVM本身完成的。 如您所料,它确实出现在第18行中:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
	at synthetic.SyntheticMethodTest4$MyLink.add(SyntheticMethodTest4.java:1)
	at synthetic.SyntheticMethodTest4.main(SyntheticMethodTest4.java:18)

下次在面试中遇到关于不稳定方法的问题时,您可能比面试官了解的更多。

参考: Java Deep博客中JCG合作伙伴 Peter Verhas的合成方法和桥接方法

翻译自: https://www.javacodegeeks.com/2014/03/synthetic-and-bridge-methods.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值