簡體   English   中英

Java有懶惰的評估嗎?

[英]Does Java have 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()調用,即使isBTrue()返回true?

那么,就語言而言 - 是的,這兩個函數都被調用了。

如果你重寫了這個函數:

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

在Java(和其他類C語言)中,這被稱為短路評估 *

是的,在第二個例子中,始終調用isATrue 也就是說,除非編譯器/ JVM可以確定它沒有可觀察到的副作用,在這種情況下它可能會選擇優化,但在這種情況下你不會注意到差異。


* 兩者截然不同; 前者本質上是一種優化技術,而第二種是由語言強制執行,可以影響可觀察的程序行為。

我最初建議這與懶惰評估完全不同,但正如@Ingo在下面的評論中指出的那樣,這是一個可疑的斷言。 可以將Java中的短路運算符視為惰性求值的非常有限的應用。

但是,當函數式語言強制執行惰性評估語義時,通常會出於完全不同的原因,即防止無限(或至少是過度)遞歸。

不,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> 嘗試將其中一個用作另一個將導致類型錯誤 在沒有靜態類型系統的情況下,代碼仍然會失敗。 正是這種缺乏統一(靜態打字與否)回答了這個問題; 不,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類型的Boolean 統一為call-by-name。 因此,如果您訂閱了懶惰評估的松散定義(同樣,我不鼓勵這樣做),您可能會說Scala有懶惰的評估。 我在這里提供Scala作為一個很好的對比。 我建議查看Haskell的真正懶惰評估(按需調用)。

希望這可以幫助!

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

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()方法。 在這種情況下唯一的有效負載是在按需延遲評估的值之前插入()->

為簡單起見,您可以使用java 8中的Supplier接口,如下所示:

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

然后在代碼之后你可以:

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

只是想添加除了這個問題線程中提到的內容,下面是來自JVM上的Oracle Documentation

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

如果isBTrue()返回true,則調用isATrue()嗎?

是的,兩者都被稱為。

不,不是的。 isBTrue()將被調用,而不管結果, isATrue() 您可以通過編寫這樣的程序來自行驗證,並在每個方法中使用print語句。

是的isATrue()將被調用,因為你在行boolean a = isATrue();顯式調用它boolean a = isATrue();

但是如果isBTrue()返回true ,則不會在以下情況下調用它:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM