简体   繁体   中英

Does Java have lazy evaluation?

I know that Java has smart/lazy evaluation in this case:

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

But what about:

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

Is isATrue() called even if isBTrue() returns true?

Well, as far as the language is concerned - yes, both functions are called.

If you rewrote the function to this:

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

then the second function will not be called, if the first is true.


But this is short-circuit evaluation , not lazy evaluation . Lazy evaluation case would look something like this:

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

In Java (and other C-like languages), this is referred to as short-circuit evaluation . *

And yes, in the second example isATrue is always called. That is, unless the compiler/JVM can determine that it has no observable side-effects, in which case it may choose to optimize, but in which case you wouldn't notice the difference anyway.


* The two are quite distinct; the former is essentially an optimization technique, whereas the second is mandated by the language and can affect observable program behaviour.

I originally suggested that this was quite distinct from lazy evaluation, but as @Ingo points out in comments below, that's a dubious assertion. One may view the short-circuit operators in Java as a very limited application of lazy evaluation.

However, when functional languages mandate lazy-evaluation semantics, it's usually for a quite different reason, namely prevention of infinite (or at least, excessive) recursion.

No, Java has only eager evaluation for user-defined methods. Some of Java's language constructs implement non-strict evaluation as you note. Others include if , ?: , while .

I once learned[1] that there is some confusion around what it means to "have lazy evaluation." First, lazy evaluation means call-by-need evaluation. Java has nothing at all like this. However, there is a common trend (which I personally discourage) to loosen the definition of lazy evaluation to also include call-by-name evaluation. Functions such as && cannot be distinguished under call-by-need versus call-by-name evaluation; it would be the same regardless, which obscures the matter.

Taking this loosening into account, some further counter-argue by claiming Java has lazy evaluation by the following:

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

Then, you might write a user-defined && like so:

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

The claim is then put forward that this demonstrates that Java has lazy evaluation. However, this is not lazy evaluation, even in the loose sense. The distinguishing point here is that Java's type system does not unify the types boolean / Boolean and Thunk<Boolean> . Attempting to use one as the other will result in a type-error . In the absence of a static type system, the code would still fail. It is this lack of unification (static typing or not) that answers the question; no, Java does not have user-defined lazy evaluation. Of course, it can be emulated as above, but this is an uninteresting observation which follows from turing-equivalence.

A language such as Scala has call-by-name evaluation, which allows a user-defined and function which is equivalent to the regular && function (taking termination into account. See [1]).

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

This allows:

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

Note that this short program snippet terminates by providing a value. It also unifies values of type Boolean as call-by-name. Therefore, if you subscribe to the loose definition of lazy evaluation (and again, I discourage this), you might say that Scala has lazy evaluation. I provide Scala here as a good contrast. I recommend looking at Haskell for true lazy evaluation (call-by-need).

Hope this helps!

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

SE8 (JDK1.8) has introduced Lambda expressions , which can make lazy evaluations more transparent. Consider the statement in the main method of the following code:

@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());
   }
}

The called function coalesce returns the first given non-null value (as in SQL). The second parameter in its call is a Lambda expression. The method veryLongMethod() will be called only when argv[0] == null. The only payload in this case is inserting ()-> before the value to be lazily evaluated on demand.

For simplicity you can use the Supplier interface from java 8 like this:

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

Then in code afterwards you can have:

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

Just wanted to add in addition to what have been mentioned in this question thread, below is from Oracle Documentation on JVM

a Java Virtual Machine implementation may choose to resolve each symbolic reference in a class or interface individually when it is used ("lazy" or "late" resolution), or to resolve them all at once when the class is being verified ("eager" or "static" resolution). This means that the resolution process may continue, in some implementations, after a class or interface has been initialized.

reference

and as an example of the classes that has lazy implementation is Stream, this is from Oracle Documentation on Stream

Streams are lazy; computation on the source data is only performed when the terminal operation is initiated, and source elements are consumed only as needed.

reference

That being said, if you do the following, nothing will be displayed. Unless you add initiator.

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

Is isATrue() called if isBTrue() returns true?

Yes, both are called.

No, it does not. isBTrue() will be invoked, regardless of the result of isATrue() . You can verify this yourself by writing such a program, with print statements in each of the methods.

Yes isATrue() will be called because you are calling it explicitly in line boolean a = isATrue();

But it won't be called in following case if isBTrue() returns true :

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM