bigywater.tech
Java · 283

接口默认方法冲突

两个接口都有同名默认方法时,编译器怎么判?父类和接口冲突时谁赢?如果接口是后来才加的默认方法,已经编译的类会怎样?这篇用十多种写法真实编译,并用分开编译的方式复现二进制兼容问题。

IncompatibleClassChangeError
不重新编译 T、只换新增了 default 的 B 时,运行到 hi() 才报错
类优先
父类 public 方法胜过接口默认方法(输出 P)
invokespecial
A.super.hi() 的字节码调用方式

冲突规则

实测 · 本机执行输出
  类同时实现 A、B(都有 default hi()),不覆盖
     → types A and B are incompatible;
  覆盖并用 A.super.hi() 指定
     → 编译通过;运行:A+B
  一个默认、一个抽象(A default,B abstract),不覆盖
     → T is not abstract and does not override abstract method hi() in B
  B 继承 A 并覆盖 hi(更具体的接口胜出)
     → 编译通过;运行:B
  B 继承 A 并覆盖 hi,T 写成 implements B, A(顺序颠倒)
     → 编译通过;运行:B
  B 继承 A 并把 hi 重新声明为抽象
     → T is not abstract and does not override abstract method hi() in B
  类优先:父类有 hi(),接口有 default hi()
     → 编译通过;运行:P
  父类的 hi() 是 abstract,接口有 default hi()
     → T is not abstract and does not override abstract method hi() in P
  父类的 hi() 是 private(接口 default hi() 是否生效)
     → 编译通过;运行:Exception in thread "main" java.lang.IllegalAccessError: class T tried to access private method 'java.lang.String P.hi()' (T and P are in unnamed module of loader 'app') | 	at T.main(T.java:1)
  父类的 hi() 是 package-private 且比接口方法弱
     → hi() in P cannot implement hi() in A
  用 B.super.hi() 调用没有直接实现的接口(T 只实现 A)
     → not an enclosing class: B
  通过间接接口调用(A.super.hi(),A 是 B 的父接口且 T 只实现 B)
     → not an enclosing class: A
  接口不能为 Object 的方法提供默认实现(default toString)
     → default method toString in interface A overrides a member of java.lang.Object
  静态接口方法不被继承(T.s() 与 A.s())
     → cannot find symbol

类优先与限制

实测 · 本机执行输出
  v1(B 无 hi)运行: A
  只替换 B 为新增 default hi() 的 v2,不重新编译 T:
     Exception in thread "main" java.lang.IncompatibleClassChangeError: Conflicting default methods: A.hi B.hi
     	at T.hi(T.java)
     	at T.main(T.java:1)
  用 v2 重新编译 T: <实验目录> error: types A and B are incompatible;

二进制兼容与字节码

实测 · 本机执行输出
    1: invokespecial #7 // InterfaceMethod A.hi:()Ljava/lang/String;
    5: invokespecial #13 // InterfaceMethod B.hi:()Ljava/lang/String;
    8: invokedynamic #16, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
  常量池里的接口方法引用: ['A.hi:()Ljava/lang/String;', 'B.hi:()Ljava/lang/String;']

反射

实测 · 本机执行输出
    T.getMethod(hi) 声明在:T isDefault=false
    U implements B:getMethod(hi) 声明在:B isDefault=true
    V implements A, B:声明在:B 调用结果=B
    B.class.getMethods() 个数(含继承的接口方法)= 1
       B.hi default=true

分步演示

默认方法冲突
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12(父类 private 方法的一例在 JDK 27 也跑过)
    • 没有验证:父类 private 方法例子里 IllegalAccessError 的原因、桥接方法是否存在、多线程下的链接时机、JDK 8 编译器对同样代码的行为、泛型接口默认方法的冲突规则
    京ICP备2026067502号-1京公网安备11010502063487号