Java Method 桥接和复合方法

简介

在阅读MyBatis源码的时候看到了MapperAnnotationBuilder中调用了一个method.isBrige()方法,顺便看了一下这个issue#237。issue237传送门,顺便还看到了有一个isSynthetic方法。就顺便了解了一下这2个方法。

Brige

import java.lang.reflect.Method;

public class BrigeSynthetic {
    
    interface GenericInterface<E>{
        void function(E e);
    }
    
    class Brige implements GenericInterface<String>{

        @Override
        public void function(String e) {
            
        }
        
    }
    
    public static void main(String[] args) {
        Method[] methods = Brige.class.getDeclaredMethods();
        for(Method method:methods){
            System.out.println(method.toString() + "   isBrige:" + method.isBridge() + "   isSynthetic:" + method.isSynthetic());
        }
    }

}

我们先看一下输出的结果:

public void cn.freemethod.reflect.BrigeSynthetic$Brige.function(java.lang.String)   isBrige:false   isSynthetic:false
public void cn.freemethod.reflect.BrigeSynthetic$Brige.function(java.lang.Object)   isBrige:true   isSynthetic:true

我们知道Java的泛型是伪泛型,是一种语法糖,实际上在编译的时候就进行了泛型擦除。对于类和泛型方法我们知道可以指定泛型参数和类型推导完成替换为实际的类型。但是对于接口怎么办?

实际上对于接口泛型擦除使用的是Object类作为实际类型,这和如果一个泛型类不没有指定泛型参数所用的类型一样。

如上的:

interface GenericInterface<E>{
        void function(E e);
}

泛型擦除之后,就变为:

interface GenericInterface{
    void function(Object e);
}

又如:一个泛型类ArrayList,没有指定泛型参数:

ArrayList list = new ArrayList();
//警告:ArrayList is a raw type. 
//References to generic type ArrayList<E> should be parameterized

实际上编译的时候是使用Object替换了ArrayList中的泛型参数的。

那么问题来了,根据上面的理论,我们开始的代码就变成了下面这样:

import java.lang.reflect.Method;

public class BrigeSynthetic {
    
    interface GenericInterface{
        void function(Object e);
    }
    
    class Brige implements GenericInterface{

        @Override
        public void function(String e) {
            
        }
        
    }
    
    public static void main(String[] args) {
        Method[] methods = Brige.class.getDeclaredMethods();
        for(Method method:methods){
            System.out.println(method.toString() + "   isBrige:" + method.isBridge() + "   isSynthetic:" + method.isSynthetic());
        }
    }

}

事实上上面的代码是通不过编译的,有2个问题,一是@Override必须在重写了父类的方法,二是Brige有没有实现的方法。归根结底一个问题,方法类型不匹配。上面的是方法重载,而不是重写。

那为什么使用泛型是没有问题呢?这就是我们要介绍的重点,Brige方法,编译器为我们生成了一个Brige方法。实际编译之后相等于下面这样的代码。

import java.lang.reflect.Method;

public class BrigeSynthetic {
    
    interface GenericInterface{
        void function(Object e);
    }
    
    class Brige implements GenericInterface{

        public void function(String e) {
            
        }

        @Override
        public void function(Object e) {
            function((String)e);
        }
        
    }
    
    public static void main(String[] args) {
        Method[] methods = Brige.class.getDeclaredMethods();
        for(Method method:methods){
            System.out.println(method.toString() + "   isBrige:" + method.isBridge() + "   isSynthetic:" + method.isSynthetic());
        }
    }

}

其中,下面的方法就是Brige方法。

@Override
        public void function(Object e) {
            function((String)e);
        }

我们可以反编译一下Brige类,看一下字节码验证一下:

使用javap命令,使用-v输出全部细节,重定向到文件中:

javap -v BrigeSynthetic$Brige.class >> brige.txt
Classfile /E:/studio/inspect/target/classes/cn/freemethod/reflect/BrigeSynthetic$Brige.class
  MD5 checksum fde974f5a6da0b44dcadf5c53243f3da
  Compiled from "BrigeSynthetic.java"
class cn.freemethod.reflect.BrigeSynthetic$Brige extends java.lang.Object implements cn.freemethod.reflect.BrigeSynthetic$GenericInterface<java.lang.String>
  SourceFile: "BrigeSynthetic.java"
  Signature: #33                          // Ljava/lang/Object;Lcn/freemethod/reflect/BrigeSynthetic$GenericInterface<Ljava/lang/String;>;
  InnerClasses:
       #37= #1 of #35; //Brige=class cn/freemethod/reflect/BrigeSynthetic$Brige of class cn/freemethod/reflect/BrigeSynthetic
       static #38= #5 of #35; //GenericInterface=class cn/freemethod/reflect/BrigeSynthetic$GenericInterface of class cn/freemethod/reflect/BrigeSynthetic
  minor version: 0
  major version: 51
  flags: ACC_SUPER

Constant pool:
   #1 = Class              #2             //  cn/freemethod/reflect/BrigeSynthetic$Brige
   #2 = Utf8               cn/freemethod/reflect/BrigeSynthetic$Brige
   #3 = Class              #4             //  java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Class              #6             //  cn/freemethod/reflect/BrigeSynthetic$GenericInterface
   #6 = Utf8               cn/freemethod/reflect/BrigeSynthetic$GenericInterface
   #7 = Utf8               this$0
   #8 = Utf8               Lcn/freemethod/reflect/BrigeSynthetic;
   #9 = Utf8               <init>
  #10 = Utf8               (Lcn/freemethod/reflect/BrigeSynthetic;)V
  #11 = Utf8               Code
  #12 = Fieldref           #1.#13         //  cn/freemethod/reflect/BrigeSynthetic$Brige.this$0:Lcn/freemethod/reflect/BrigeSynthetic;
  #13 = NameAndType        #7:#8          //  this$0:Lcn/freemethod/reflect/BrigeSynthetic;
  #14 = Methodref          #3.#15         //  java/lang/Object."<init>":()V
  #15 = NameAndType        #9:#16         //  "<init>":()V
  #16 = Utf8               ()V
  #17 = Utf8               LineNumberTable
  #18 = Utf8               LocalVariableTable
  #19 = Utf8               this
  #20 = Utf8               Lcn/freemethod/reflect/BrigeSynthetic$Brige;
  #21 = Utf8               function
  #22 = Utf8               (Ljava/lang/String;)V
  #23 = Utf8               e
  #24 = Utf8               Ljava/lang/String;
  #25 = Utf8               (Ljava/lang/Object;)V
  #26 = Class              #27            //  java/lang/String
  #27 = Utf8               java/lang/String
  #28 = Methodref          #1.#29         //  cn/freemethod/reflect/BrigeSynthetic$Brige.function:(Ljava/lang/String;)V
  #29 = NameAndType        #21:#22        //  function:(Ljava/lang/String;)V
  #30 = Utf8               SourceFile
  #31 = Utf8               BrigeSynthetic.java
  #32 = Utf8               Signature
  #33 = Utf8               Ljava/lang/Object;Lcn/freemethod/reflect/BrigeSynthetic$GenericInterface<Ljava/lang/String;>;
  #34 = Utf8               InnerClasses
  #35 = Class              #36            //  cn/freemethod/reflect/BrigeSynthetic
  #36 = Utf8               cn/freemethod/reflect/BrigeSynthetic
  #37 = Utf8               Brige
  #38 = Utf8               GenericInterface
{
  final cn.freemethod.reflect.BrigeSynthetic this$0;
    flags: ACC_FINAL, ACC_SYNTHETIC


  cn.freemethod.reflect.BrigeSynthetic$Brige(cn.freemethod.reflect.BrigeSynthetic);
    flags: 

    Code:
      stack=2, locals=2, args_size=2
         0: aload_0       
         1: aload_1       
         2: putfield      #12                 // Field this$0:Lcn/freemethod/reflect/BrigeSynthetic;
         5: aload_0       
         6: invokespecial #14                 // Method java/lang/Object."<init>":()V
         9: return        
      LineNumberTable:
        line 11: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0      10     0  this   Lcn/freemethod/reflect/BrigeSynthetic$Brige;

  public void function(java.lang.String);
    flags: ACC_PUBLIC

    Code:
      stack=0, locals=2, args_size=2
         0: return        
      LineNumberTable:
        line 16: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       1     0  this   Lcn/freemethod/reflect/BrigeSynthetic$Brige;
               0       1     1     e   Ljava/lang/String;

  public void function(java.lang.Object);
    flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC

    Code:
      stack=2, locals=2, args_size=2
         0: aload_0       
         1: aload_1       
         2: checkcast     #26                 // class java/lang/String
         5: invokevirtual #28                 // Method function:(Ljava/lang/String;)V
         8: return        
      LineNumberTable:
        line 1: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
}

我们在最后可以看到有一个public void function(java.lang.Object)方法。并且调用了重载方法Method function:(Ljava/lang/String;)V。 其实在我们的反射输出中已经验证了这一点。

synthetic

至于什么是synthetic方法,前面其实都已经看到了一些。就是编译器添加的方法,这个说法可能不太准确,但是大概的意思就是这个。通过Javaassist,cglib添加的方法都不是synthetic方法。

下面看一个测试的例子:

import java.lang.reflect.Method;

//import javassist.CannotCompileException;
//import javassist.ClassPool;
//import javassist.CtClass;
//import javassist.CtMethod;
//import javassist.CtNewMethod;
//import javassist.NotFoundException;

public class OutClass {

    private int out;

    public void accessInnerClass() {
        InnerClass innerClass = new InnerClass();
        innerClass.in = 1;// 语句1
    }

    class InnerClass {

        private int in;

        public void printOut() {
            System.out.println(out);// 语句2
        }

        public int getIn() {
            return in;
        }
    }

    public static void main(String[] args) {
//        CtClass cc = null;
//        try {
//            cc = ClassPool.getDefault().get("cn.freemethod.reflect.DynamicClass");
//            CtMethod m = CtNewMethod.make("public void xxx() { }", cc);
//            cc.addMethod(m);
//        } catch (NotFoundException | CannotCompileException e) {
//            e.printStackTrace();
//        }

        Method[] methods = null;
        methods = OutClass.class.getDeclaredMethods();
//        try {
//            methods = cc.toClass().getDeclaredMethods();
//        } catch (SecurityException e) {
//            e.printStackTrace();
//        } catch (CannotCompileException e) {
//            e.printStackTrace();
//        }
        System.out.println("----------OutClass-----------");
        printMethod(methods);
        methods = InnerClass.class.getDeclaredMethods();
        System.out.println("----------InnerClass-----------");
        printMethod(methods);
    }

    public static void printMethod(Method[] methods) {
        for (Method method : methods) {
            System.out.println(
                    method.toString() + "   isBrige:" + method.isBridge() + "   isSynthetic:" + method.isSynthetic());
        }
    }
}

输出结果:

----------OutClass-----------
public static void cn.freemethod.reflect.OutClass.main(java.lang.String[])   isBrige:false   isSynthetic:false
public void cn.freemethod.reflect.OutClass.accessInnerClass()   isBrige:false   isSynthetic:false
static int cn.freemethod.reflect.OutClass.access$0(cn.freemethod.reflect.OutClass)   isBrige:false   isSynthetic:true
public static void cn.freemethod.reflect.OutClass.printMethod(java.lang.reflect.Method[])   isBrige:false   isSynthetic:false
----------InnerClass-----------
static void cn.freemethod.reflect.OutClass$InnerClass.access$0(cn.freemethod.reflect.OutClass$InnerClass,int)   isBrige:false   isSynthetic:true
public void cn.freemethod.reflect.OutClass$InnerClass.printOut()   isBrige:false   isSynthetic:false
public int cn.freemethod.reflect.OutClass$InnerClass.getIn()   isBrige:false   isSynthetic:false

我们看到OutClass和InnerClass中都多了一个access$0这个静态方法,其实这是因为在我们外部类要访问内部类成员,内部类要访问外部类成员的结果。在我们标注语句1的地方访问了内部类的成员,其实是通过access$0这个静态方法传递一个内部类实例获取的。内部类也一样。

有兴趣的同学可以自己反编译一下OutClass和InnerClass看一下。

注释是部分是测试Javaassist动态添加的方法是不是synthetic方法。

转载于:https://my.oschina.net/u/2474629/blog/1503235

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值