Java有懒惰的评估吗?

时间:2013-03-03 18:31:59

标签: java lazy-evaluation

我知道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()返回true,也会调用isBTrue()吗?

9 个答案:

答案 0 :(得分:28)

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

如果你重写了这个功能:

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.
}

答案 1 :(得分:21)

在Java(和其他类C语言)中,这被称为short-circuit evaluation *

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

<小时/> * 两者截然不同;前者本质上是一种优化技术,而第二种是由语言强制执行,可能影响可观察的程序行为。

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

然而,当函数式语言强制执行惰性评估语义时,它通常是出于完全不同的原因,即防止无限(或至少是过度)递归。

答案 2 :(得分:17)

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

我曾经学过[1],对于它对于懒惰的评价意味着什么存在一些混淆。&#34;首先,延迟评估意味着按需调用评估。 Java完全没有这样的东西。但是,有一个共同的趋势(我个人不鼓励)放松懒惰评估的定义,也包括按名称调用评估。诸如&&之类的功能无法在按需调用与按名称调用评估之间进行区分;无论如何都会是一样的,这会掩盖这个问题。

考虑到这种放松,有人进一步反驳说声称Java有以下懒惰的评价:

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

然后,您可以编写用户定义的&&,如下所示:

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

然后提出声明,这表明Java具有惰性评估。但是,这是懒惰评估,即使在松散的意义上。这里的区别在于Java类型系统不统一类型boolean / BooleanThunk<Boolean>。尝试将其中一个用作另一个将导致类型错误。在没有静态类型系统的情况下,代码仍然会失败。正是这种缺乏统一(静态打字与否)回答了这个问题;不,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/

答案 3 :(得分:11)

SE8(JDK1.8)引入了Lambda expressions,可以使延迟评估更加透明。请考虑以下代码的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()方法。在这种情况下,唯一的有效负载是在要按需延迟评估的值之前插入()->

答案 4 :(得分:2)

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

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

然后在代码中你可以:

if (iAmLazy) 
  someVal = someValSupplier.get(); // lazy getting the value
else 
  someVal = getSomeVal(); // non lazy getting the value

答案 5 :(得分:1)

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

  

Java虚拟机实现可以选择解析每个   在类或接口中单独的符号引用   使用(“懒惰”或“迟”分辨率),或一次解决所有问题   当班级被验证时(“急切”或“静态”解决)。   这意味着在某些情况下,解决过程可能会继续   实现,在初始化类或接口之后。

reference

并且作为具有延迟实现的类的示例是Stream,这来自Oracle文档在流

  流是懒惰的;仅对源数据进行计算   当启动终端操作时,源元素是   只在需要时消费。

reference

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

Steam.of(1, 2, 3, 4, 5).filter(number -> {
   System.out.println("This is not going to be logged");
   return true;
});

答案 6 :(得分:0)

  

如果isBTrue()返回true,则调用isATrue()吗?

是的,两者都被召唤。

答案 7 :(得分:0)

不,它没有。无论isBTrue()的结果如何,都会调用isATrue()。您可以通过编写这样的程序来自行验证,并在每种方法中使用print语句。

答案 8 :(得分:0)

是的isATrue()将被调用,因为您在行boolean a = isATrue();

中明确地调用它

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

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