JAVA 桥接方法

JAVA BRIDGE METHOD

Posted by gomyck on August 18, 2020

mybatis 源码发现的一段代码, 详细查看之后, 发现事情并不是那么简单

1. 起因

阅读 mybatis 源码时, 看到这么一段代码:

1
2
3
4
5
6
7
8
9
10
for (Method method : methods) {
    try {
      // issue #237
      if (!method.isBridge()) {
        parseStatement(method);
      }
    } catch (IncompleteElementException e) {
      configuration.addIncompleteMethod(new MethodResolver(this, method));
    }
}

2. 探索

从源码上字面意义上来看, 如果方法不是桥接方法, 那么 doSomething…

查看 isBridge 的源码, jdk 上是这么说的:

1
2
3
4
5
6
7
8
9
10
11
/**
 * Returns {@code true} if this method is a bridge
 * method; returns {@code false} otherwise.
 *
 * @return true if and only if this method is a bridge
 * method as defined by the Java Language Specification.
 * @since 1.5
 */
public boolean isBridge() {
  return (getModifiers() & Modifier.BRIDGE) != 0;
}

从注释上来看, 啥也看不出来, 只强调了如果是一个 java 语言声明的桥接方法, 那么返回 true

3. why

google java bridge method

首先就是 oracle 官网给出的帖子: https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html

点进去看了看, 大致是这样解释的:

有时候, 泛型擦除会导致某些问题, 为了解决问题, 编译器生成一些假方法, 这种方法称为桥接方法

直接看例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Node<T> {

    public T data;

    public Node(T data) { this.data = data; }

    public void setData(T data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}

public class MyNode extends Node<Integer> {
    public MyNode(Integer data) { super(data); }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

上面方法在进行编译的时候, 由于泛型擦除, 这将会导致编译后的两个类型在方法表上处于分裂状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Node {

    public Object data;

    public Node(Object data) { this.data = data; }

    public void setData(Object data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}

public class MyNode extends Node {

    public MyNode(Integer data) { super(data); }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

在泛型擦除之后, 方法的签名会不匹配, 入参类型一个为 Object, 一个为 Integer, 这将会因为类型不一致导致方法没有覆盖, 为了解决问题, 保持多态的特性, java 编译器会生成一个桥接方法 来确保擦除后的子类按照预期工作, 大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyNode extends Node {

    // Bridge method generated by the compiler
    //
    public void setData(Object data) {
        setData((Integer) data);
    }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }

    // ...
}

桥接方法与父类签名一致setData(Object data), 且在调用后(override), 会调用本类的同名方法setData(Integer data)