任何人都可以解释这个陈述 - "所有lambda的要点都是延迟执行"

时间:2017-06-05 22:05:10

标签: java java-8

我在" 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有什么用?

5 个答案:

答案 0 :(得分:5)

据我所知,这完全是关于Supplier以及推迟执行的方式。更好的IMO示例是Optional两种方法:orElseorElseGet

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)

这个问题也许有点超出范围,但我认为还值得一提的两件事:

    当您在应用程序中受益匪浅时,
  1. 延迟执行通常是有意义的。例如,可能是这样:
    a-“供应”记录的消息所需的计算非常昂贵或
    b-程序从此代码点多次传递。在这种情况下,即使您执行简单的String串联,计算也会变得非常昂贵。

    否则,如果您有一个您所描述的呼叫,并且希望很少被呼叫,那么根本没有真正的理由求助于延迟执行。

    换句话说,lambda和延迟执行可以解决非美学目的的问题。但是,此类代码具有教育目的。

  2. 我在示例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>