重写默认方法并新增抽象方法的函数式接口执行行为异常问题
代码与问题重现
@FunctionalInterface interface StringConsumer extends Consumer<String> { @Override public default void accept(String s) { System.out.println(s.toUpperCase()); } void print(String s); } public class Test { public static void main(String[] args) { StringConsumer stringConsumerImpl= s -> System.out.println(s.toLowerCase()); List<String> list = Arrays.asList("Dr", "Mr", "Miss", "Mrs"); list.forEach(stringConsumerImpl); } }
输出:
DR MR MISS MRS
问题:定义继承自Consumer<String>的@FunctionalInterface接口StringConsumer,将accept重写为默认方法(输出大写),同时声明抽象方法print(String s)。用Lambda实现print(输出小写)后,调用list.forEach(stringConsumerImpl)却执行了默认的accept方法,与预期不符;且删除print方法时main方法会出现编译错误。
问题原因分析
forEach只调用Consumer的accept方法List.forEach的参数类型是Consumer<? super T>,你的StringConsumer实现了Consumer<String>,因此forEach会直接调用Consumer接口定义的accept方法——也就是你在StringConsumer中重写的默认实现(输出大写),和Lambda实现的print方法无关联,因为forEach不会调用print。删除
print后编译错误的本质@FunctionalInterface要求接口必须有且仅有一个抽象方法。删除print后,StringConsumer继承的Consumer抽象方法accept被你重写成了默认方法,此时接口中没有任何抽象方法,违反了函数式接口的定义,因此编译器报错。
解决方法
方案1:让accept成为抽象方法,用Lambda实现它
修改StringConsumer,去掉accept的默认实现,让它继承Consumer的抽象accept,Lambda直接实现accept逻辑:
@FunctionalInterface interface StringConsumer extends Consumer<String> { // 保留继承自Consumer的抽象accept方法 } public class Test { public static void main(String[] args) { StringConsumer stringConsumerImpl= s -> System.out.println(s.toLowerCase()); List<String> list = Arrays.asList("Dr", "Mr", "Miss", "Mrs"); list.forEach(stringConsumerImpl); } }
此时forEach调用accept时,会执行Lambda中的小写输出逻辑。
方案2:自定义遍历,调用print方法
如果要保留print抽象方法,需手动遍历列表并调用print,而非依赖forEach:
@FunctionalInterface interface StringConsumer extends Consumer<String> { @Override public default void accept(String s) { System.out.println(s.toUpperCase()); } void print(String s); } public class Test { public static void main(String[] args) { StringConsumer stringConsumerImpl= s -> System.out.println(s.toLowerCase()); List<String> list = Arrays.asList("Dr", "Mr", "Miss", "Mrs"); // 手动遍历调用print方法 for(String s : list) { stringConsumerImpl.print(s); } } }
这样输出会符合预期的小写字符串。
内容的提问来源于stack exchange,提问作者Alper ARSLAN

