我在" Java SE 8中为真正的不耐烦:使用Lambdas进行编程"。
让我们看一个简单的例子。假设您记录了一个事件:
logger.info("x: " + x + ", y: " + y);
如果将日志级别设置为禁止INFO消息会发生什么?计算消息字符串并将其传递给info方法,然后该方法决定将其丢弃。如果字符串连接仅在必要时发生,那不是更好吗?
仅在必要时运行代码是lambda的用例。标准习惯用法是将代码包装在无参数lambda:
中() -> "x: " + x + ", y: " + y
以下方法提供了延迟日志记录:
public static void info(Logger logger, Supplier<String> message) { if (logger.isLoggable(Level.INFO)) logger.info(message.get()); }
我们使用
isLoggable
类的Logger
方法来决定是否应记录INFO消息。如果是这样,我们通过调用它的抽象方法调用lambda,该方法恰好称为get。
所以我不理解的是 - 我们可以使用示例1中的logger.isLoggable(Level.INFO)
(不使用lambdas的代码),只有{{{}才会计算消息字符串1}}很满意。
logger.isLoggable(Level.INFO)
在这种情况下使用lambdas有什么用?
答案 0 :(得分:5)
据我所知,这完全是关于Supplier
以及推迟执行的方式。更好的IMO示例是Optional
两种方法:orElse
和orElseGet
。
orElse
返回T
或在您的示例中返回String
;另一方面,orElseGet
返回Supplier<T>
,仅在需要时计算(当Optional实际上缺失时):
public T orElseGet(Supplier<? extends T> supplier) {
return value != null ? value : supplier.get();
}
这里的区别在于orElse
计算值总是,有点像急切地;即使不需要它。
答案 1 :(得分:4)
我们可以使用示例1中的logger.isLoggable(Level.INFO),只有当我们满足logger.isLoggable(Level.INFO)时才会计算消息字符串。
这是真的,是的,但只有在每次日志调用时都检查isLoggable
时才会这样:
if (logger.isLoggable(Level.INFO))
logger.info("x: " + x + ", y: " + y);
if (logger.isLoggable(Level.INFO))
logger.info("another log entry");
因此,您基本上要求用户明确检查每个日志调用的日志级别 。也无法实际强制用户一直检查它,因此您需要还检查方法内的日志级别。正如您可能想象的那样,如果您最终必须为每个单个日志调用添加if
,这将变得非常冗长。
另一方面,如果您使用的是lambda,则可以在info
方法中移动该条件,因此不必须明确检查日志级别。
答案 2 :(得分:1)
出于性能考虑,通过对每个信息级别日志使用logger.isLogable( Level.Info )
,代码将看起来像这样:
if( logger.isLogable( Level.Info ) )
{
logger.info( "x: " + x + ", y: " + y );
}
要以给定的方式编写每个信息日志会带来额外的困难。具体地说,想象一下可以使用各种不同日志级别的软件(如果使用if语句和实际的logger方法,很容易出错)。
使用递延功能,如书所示:
logger.info( ( ) -> "x: " + x + ", y: " + y );
以上延迟的功能实际上不执行String串联,除非logger.isLogable( Level.Info )
为true(在后续方法中)。因此,从本质上讲,它是更快的,因为仅发送方法的引用,并且仅在需要时才执行String连接。
答案 3 :(得分:0)
根据Logger类的JavaSE8文档:
https://docs.oracle.com/javase/8/docs/api/java/util/logging/Logger.html
以我自己的话说:(尽管强烈建议您也阅读文档)
在Logger类中,每个“记录”方法都有重载方法。这些方法采用供应商FunctionalInterface实现,而不是将消息字符串作为输入。任何FunctionalInterface都可以由lambda表达式或方法引用替换。这些Logger类的特殊方法调用lambda的正文(或者,如果您选择使用方法引用,则调用方法引用的代码)仅当消息实际上将基于“有效日志级别” 。
这样,如果日志级别不允许记录消息,则Logger类可以帮助您避免构造消息。因此,不需要消息构造计算工作。这就是通常称为延迟执行或惰性执行或惰性的机制。尽管所有这些表达式也可能仅指延迟执行,但是直到实际需要执行结果为止。不一定是“有条件的执行”,或者只有当我满足某些条件时才会发生的 t。
您可以通过在其原型中寻找供应商来识别这些方法。
您的示例未使用lambda表达式的Java8概念,因此无论是否实际写入日志,都将执行String连接。
//example 1: (your example)
logger.info("x: " + x + ", y: " + y);
另一方面,如果您使用Java8 lambda,则代码如下:
//example 2:
logger.info((x,y) -> "x: " + x + ", y: " + y);
请注意,示例1中使用的Logger类方法与示例2中使用的方法不同!
在示例1中,您正在使用方法:
public void info(String msg)
在示例2中,我正在使用:
public void info(Supplier<String> msgSupplier)
这个问题也许有点超出范围,但我认为还值得一提的两件事:
延迟执行通常是有意义的。例如,可能是这样:
a-“供应”记录的消息所需的计算非常昂贵或
b-程序从此代码点多次传递。在这种情况下,即使您执行简单的String串联,计算也会变得非常昂贵。
否则,如果您有一个您所描述的呼叫,并且希望很少被呼叫,那么根本没有真正的理由求助于延迟执行。
换句话说,lambda和延迟执行可以解决非美学目的的问题。但是,此类代码具有教育目的。
我在示例2中实际执行的操作比在示例1中执行的操作要复杂得多。为什么?
Lambda表达式可以替代方法引用和FunctionalInterface参数。 FunctionalInterface是那些只有一种方法的接口,例如Supplier。
因此,在lambda主体中,我定义了代码,该代码“覆盖” 供应商的 get()方法,除了返回结果String并指出该位置应该发生。 x和y的有效类型是可以连接到String的任何类型。
答案 4 :(得分:0)
考虑您建议案例中的方法签名:
logger.info(message)
logger.info(level, message)
您所建议的是基于 logger.isLoggable() 我们可以调用 logger.info(message) 的选项,但是您忽略了生成该消息参数的表达式,我们认为这是一个昂贵的操作。所以 logger.info(level, () -> message) 是调用,即使 api 调用有两个参数,但第二个参数在函数执行之前没有被评估。在您的示例中, message = "x: " + x, "y: " + y 在调用进入第一个检查 logger.isloggable 之前执行,从而导致对昂贵的语句进行不必要的评估,该语句可以使用 Lambda 表达式延迟。< /p>