33

我知道 Java 在这种情况下具有智能/惰性评估:

public boolean isTrue() {
    boolean a = false;
    boolean b = true;
    return b || (a && b); // (a && b) is not evaluated since b is true
}

但是关于:

public boolean isTrue() {
    boolean a = isATrue();
    boolean b = isBTrue();
    return b || a;
}

isATrue()即使isBTrue()返回 true也会调用?

4

9 回答 9

33

好吧,就语言而言 - 是的,这两个函数都被调用了。

如果您将函数重写为:

public boolean isTrue() {
    return isBTrue() || isATrue();
}

如果第一个函数为真,则不会调用第二个函数。


但这是短路评估,不是懒惰评估。惰性评估案例看起来像这样:

public interface LazyBoolean {
    boolean eval();
}

class CostlyComparison implements LazyBoolean {
  private int a, b;

  public CostlyComparison(int a, int b) { 
    this.a=a; 
    this.b=b; 
  }

  @Override 
  public boolean eval() {
    //lots of probably not-always-necessary computation here
    return a > b;
  }
} 

public LazyBoolean isATrue() {
  return new CostlyComparison(10,30);  //just an example
}

public boolean isTrue() {        // so now we only pay for creation of 2 objects
    LazyBoolean a = isATrue();   // but the computation is not performed; 
    LazyBoolean b = isBTrue();   // instead, it's encapsulated in a LazyBoolean
    return b.eval() || a.eval(); // and will be evaluated on demand;
                                 // this is the definition of lazy eval.
}
于 2013-03-03T18:34:57.960 回答
25

在 Java(和其他类似 C 的语言)中,这被称为短路评估*

是的,在第二个例子isATrue中总是被调用。也就是说,除非编译器/JVM 可以确定它没有可观察到的副作用,在这种情况下它可能会选择优化,但在这种情况下您无论如何都不会注意到差异。


*两者截然不同;前者本质上是一种优化技术,而后者是语言要求的,可以影响可观察的程序行为。

我最初建议这与惰性评估完全不同,但正如@Ingo 在下面的评论中指出的那样,这是一个可疑的断言。人们可能会将 Java 中的短路运算符视为惰性求值的一种非常有限的应用。

但是,当函数式语言要求惰性求值语义时,通常是出于完全不同的原因,即防止无限(或至少是过度)递归。

于 2013-03-03T18:33:49.257 回答
17

不,Java 只对用户定义的方法进行热切评估。正如您所注意到的,一些 Java 语言结构实现了非严格评估。其他包括if, ?:, while

我曾经了解到[1] 关于“进行惰性评估”的含义存在一些混淆。首先,惰性求值意味着按需调用。Java 根本没有这样的东西。然而,有一个普遍的趋势(我个人不鼓励)放宽惰性评估的定义,也包括按名称调用的评估。诸如&&在按需调用与按名称调用评估下无法区分的功能;无论如何都是一样的,这使事情变得模糊不清。

考虑到这种松动,一些进一步的反驳声称 Java 具有以下惰性评估:

interface Thunk<A> {
  A value();
}

然后,您可以&&像这样编写用户定义:

boolean and(boolean p, Thunk<Boolean> q) {
  return p && q();
}

然后提出声称这表明 Java 具有惰性求值。然而,即使在松散的意义上,这也不是懒惰的评价。这里的区别在于Java的类型系统没有统一类型boolean/BooleanThunk<Boolean>. 尝试将一个用作另一个将导致type-error。在没有静态类型系统的情况下,代码仍然会失败。正是这种缺乏统一性(静态类型与否)回答了这个问题;不,Java 没有用户定义的惰性求值。当然,它可以像上面那样模拟,但这是从图灵等价得出的一个无趣的观察。

诸如 Scala 之类的语言具有按名称调用评估,它允许用户定义的and函数等效于常规&&函数(考虑终止。参见 [1])。

// note the => annotation on the second argument
def and(p: Boolean, q: => Boolean) =
  p && q

这允许:

def z: Boolean = z
val r: Boolean = and(false, z)

请注意,这个简短的程序片段通过提供一个值来终止。它还将类型的值统一Boolean为按名称调用。因此,如果你赞同惰性求值的松散定义(同样,我不鼓励这样做),你可能会说 Scala 具有惰性求值。我在这里提供 Scala 作为一个很好的对比。我建议查看 Haskell 以获得真正的惰性评估(按需调用)。

希望这可以帮助!

[1] http://blog.tmorris.net/posts/a-fling-with-lazy-evaluation/

于 2013-05-31T10:37:01.530 回答
12

SE8 (JDK1.8) 引入了Lambda 表达式,可以让惰性求值更加透明。考虑以下代码的 main 方法中的语句:

@FunctionalInterface
public interface Lazy<T> {
   T value();
}

class Test {
   private String veryLongMethod() {
      //Very long computation
      return "";
   }

   public static <T> T coalesce(T primary, Lazy<T> secondary) {
      return primary != null? primary : secondary.value();
   }

   public static void main(String[] argv) {
      String result = coalesce(argv[0], ()->veryLongMethod());
   }
}

被调用的函数 coalesce 返回第一个给定的非空值(如在 SQL 中)。其调用中的第二个参数是 Lambda 表达式。仅当 argv[0] == null 时才会调用方法veryLongMethod()。在这种情况下,唯一的有效负载是()->在需要延迟评估的值之前插入。

于 2014-09-27T14:40:32.693 回答
3

为简单起见,您可以使用 java 8 中的 Supplier 接口,如下所示:

Supplier<SomeVal> someValSupplier = () -> getSomeValLazily();

然后在代码中你可以拥有:

if (iAmLazy) 
  someVal = someValSupplier.get(); // lazy getting the value
else 
  someVal = getSomeVal(); // non lazy getting the value
于 2017-01-30T10:33:41.817 回答
2

除了这个问题线程中提到的内容之外,只想添加以下内容,来自 JVM 上的 Oracle 文档

Java 虚拟机实现可以选择在使用时单独解析类或接口中的每个符号引用(“延迟”或“延迟”解析),或者在验证类时一次全部解析(“渴望”或“静态”分辨率)。这意味着在某些实现中,在初始化类或接口之后,解析过程可能会继续。

参考

并且作为具有惰性实现的类的示例是 Stream,这是来自 Oracle Documentation on Stream

流是懒惰的;仅在发起终端操作时才对源数据进行计算,并且仅在需要时消耗源元素。

参考

话虽如此,如果您执行以下操作,则不会显示任何内容。除非你添加启动器。

Steam.of(1, 2, 3, 4, 5).filter(number -> {
   System.out.println("This is not going to be logged");
   return true;
});
于 2018-03-17T16:57:35.670 回答
0

YesisATrue()将被调用,因为您在行中显式调用它boolean a = isATrue();

isBTrue()但如果返回,则不会在以下情况下调用它true

public boolean isTrue() {
    return isBTrue() || isATrue();
}
于 2013-03-03T18:39:57.920 回答
0

如果 isBTrue() 返回 true,是否调用 isATrue()?

是的,两者都被调用。

于 2013-03-03T18:33:36.003 回答
0

不,不是的。isBTrue()将被调用,无论isATrue(). 您可以通过编写这样的程序自己验证这一点,并在每个方法中使用 print 语句。

于 2013-03-03T18:33:51.970 回答