如何使用延迟求值表达式记录非final变量?
问题描述
我想生成包含局部变量值的日志消息,为了利用延迟求值的优势,打算向日志框架传递一个Supplier<String>对象。但下面的代码无法编译:
private void doSomething(Object a, Object b){ var myLocalVar = deriveValFrom(a); if (null == myLocalVar){ myLocalVar = deriveValFrom(b); } LOG.debug(() -> String.format("settled on value %s", myLocalVar)); }
原因是myLocalVar既不是final变量,也不是等效final(effectively final)变量。
现有思路
- 定义临时的等效final变量捕获
myLocalVar,但会增加代码冗余。 - 实现私有方法封装日志逻辑:
private void logToDebug(String formatStr, Object p0, Object... objs){ LOG.debug(() -> String.format(formatStr, p0, objs)); }
该方法可解决编译问题,但会给类添加无关代码。
实际问题
有没有既能实现延迟求值,又能保持代码简洁的更好方法?
相关信息
日志底层基于log4j实现,但必须通过自定义门面(facade)操作,我可以扩展该门面(比如添加debug(String formatStr, p0, ...)方法),但希望尽可能减少扩展内容。
解决方案
方案1:临时变量捕获最终值(最轻量本地改动)
既然myLocalVar完成赋值后不再修改,只需在日志前定义一个等效final的临时变量捕获它,代码改动极小:
private void doSomething(Object a, Object b){ var myLocalVar = deriveValFrom(a); if (null == myLocalVar){ myLocalVar = deriveValFrom(b); } // 用等效final变量捕获最终值 var finalVal = myLocalVar; LOG.debug(() -> String.format("settled on value %s", finalVal)); }
这种方式没有过度冗余,且无需额外方法或门面扩展。
方案2:用Optional简化赋值+捕获逻辑
如果deriveValFrom的返回值适合用Optional处理,可将赋值和null判断合并,同时让变量天然成为等效final:
private void doSomething(Object a, Object b){ var finalVal = Optional.ofNullable(deriveValFrom(a)) .orElseGet(() -> deriveValFrom(b)); LOG.debug(() -> String.format("settled on value %s", finalVal)); }
既简化了null判断逻辑,又完美适配Lambda的变量要求。
方案3:给门面添加轻量重载方法(全局复用)
如果不想在业务代码中加临时变量,可给自定义门面添加一个支持延迟格式化的默认方法,仅需一行代码:
// 在自定义门面接口中添加 default void debug(String format, Object... args) { debug(() -> String.format(format, args)); }
之后业务代码可直接调用,自动实现延迟求值:
LOG.debug("settled on value %s", myLocalVar);
该扩展非常轻量,后续所有业务代码都能复用,无需再写Lambda或临时变量。
内容的提问来源于stack exchange,提问作者leuchtstoffratte
相关产品推荐
相关产品推荐

