接口用默认方法重写Object方法:错误差异与权限疑问
一、程序1的误导性错误与程序2的清晰错误原因
程序1的情况
先看程序1的代码:
interface ClonerFinalizer { default Object clone() { System.out.println("default clone"); return this; } default void finalize() { System.out.println("default finalize"); } } public class FOO implements ClonerFinalizer { // @Override // public Object clone() { // return ClonerFinalizer.super.clone(); // } // @Override // public void finalize() { // ClonerFinalizer.super.finalize(); // } public static void main(String... args) { new FOO().clone(); new FOO().finalize(); } }
这段代码编译时会弹出误导性错误:
FOO.java:10: error: finalize() in Object cannot implement finalize() in ClonerFinalizer
public class FOO implements ClonerFinalizer {
^
attempting to assign weaker access privileges; was public
错误提示看似指向Object的方法无法实现接口方法,实际原因是:
- 接口的默认方法默认都是public访问权限;
- Object类的
finalize()和clone()都是protected的; - 当FOO类没有显式重写这两个方法时,编译器会认为类需要满足接口的public方法要求,但FOO继承自Object的这两个方法是protected的,访问权限比接口要求的更弱——Java规定子类方法的访问权限不能比父类更弱,这里相当于要把父类的protected方法"提升"为public,因此编译器抛出权限不符的错误,只是提示表述容易让人误解。
取消注释重写代码后,FOO类显式把方法定义为public,既符合接口的权限要求,又覆盖了Object的protected方法,自然能正常编译运行。
程序2的情况
再看程序2的代码:
interface ToStringer { default String toString() { System.out.println("default clone"); return "HI"; } } public class FOO implements ToStringer { @Override public String toString() { return ToStringer.super.toString(); } public static void main(String... args) { new FOO().toString(); } }
这段代码编译时直接给出清晰错误:
FOO.java:2: error: default method toString in interface ToStringer overrides a member of java.lang.Object
default String toString() {
^
原因是Java有明确规则:接口不能定义默认方法来覆盖Object类的public方法。所有类都继承自Object,toString()、equals()、hashCode()都是Object的public方法,如果接口定义同名同签名的默认方法,会和类继承的父类方法产生无法解决的冲突——编译器直接在接口定义阶段就禁止这种操作,因此错误提示直接点明问题。
二、为什么clone()、finalize()可在接口重写,而toString()等不行?
核心原因在于Object类中这些方法的访问权限和Java接口规则:
toString()、equals(Object)、hashCode()都是public方法:Java规范禁止接口通过默认方法覆盖Object的public方法,因为所有类都继承了这些方法,接口的默认方法会和父类方法产生必然冲突,编译器直接拦截这类定义。clone()、finalize()是protected方法:接口的默认方法是public的,两者签名相同但接口方法的访问权限更宽。编译器允许接口定义这类默认方法,因为类实现接口时,只需显式把方法重写为public就能满足要求,不会像public方法那样产生无法调和的冲突——当然不重写会出现程序1的权限错误,但这种情况可以通过显式重写解决,因此编译器允许接口先定义这类默认方法。
内容的提问来源于stack exchange,提问作者PANKAJ DUBEY

