SLF4J与Java 8报错:表达式目标类型必须是函数式接口
你遇到的报错核心原因是lambda表达式的目标类型不明确。
你调用的log.debug("{}", () -> expensive())里,第二个参数是一个lambda,但debug(String, Object...)这个重载方法的参数是Object类型——lambda必须绑定到一个函数式接口(比如Supplier)才能被编译器识别,而Object不是函数式接口,所以编译器不知道该把这个lambda转换成什么类型,就抛出了「表达式目标类型必须是函数式接口」的错误。
根据你的SLF4J版本,有几种靠谱的解决方式:
方式1:用SLF4J的Supplier重载(推荐)
如果你的SLF4J是1.8+或2.x版本,框架提供了直接接收Supplier<String>的debug方法,这时候不需要占位符,直接把生成日志的逻辑丢进去就行:
log.debug(() -> expensive());
这样只有当debug级别日志处于开启状态时,才会执行expensive(),完美实现延迟加载的需求。
方式2:显式指定lambda的类型(适配旧版SLF4J)
如果你的SLF4J版本比较老,没有上面的重载,可以手动把lambda转成Supplier<String>,让编译器能识别:
log.debug("{}", (Supplier<String>) () -> expensive());
注意:这种方式需要你的日志实现(比如Logback、Log4j2)支持处理Supplier参数,不然可能会打印出lambda对象的toString(类似com.xxx.Class$$Lambda$1/xxxx)。
方式3:手动判断日志级别(最兼容)
如果不想依赖版本或日志实现,最稳妥的方式是先判断当前是否开启了debug日志,再执行昂贵操作:
if (log.isDebugEnabled()) { log.debug("{}", expensive()); }
这种写法兼容性拉满,任何SLF4J版本都能用,逻辑也一目了然。
顺便提一句,你的expensive()方法可以简化一下,用Stream API替代forEach拼接:
private String expensive() { return aLongList.stream() .map(Object::toString) .collect(Collectors.joining()); }
代码更简洁,可读性也更好~
内容的提问来源于stack exchange,提问作者Phate

