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

trueを返しisATrue()ても呼び出されますか?isBTrue()

4

9 に答える 9

33

ええと、言語に関する限り、そうです、両方の関数が呼び出されます。

関数をこれに書き直した場合:

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

最初の関数がtrueの場合、2番目の関数は呼び出されません。


ただし、これは短絡評価であり、遅延評価ではありません。遅延評価の場合は次のようになります。

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のような言語)では、これは短絡評価と呼ばれます。*

そして、はい、2番目の例isATrueでは常に呼び出されます。つまり、コンパイラ/ JVMが観察可能な副作用がないと判断できない場合は、最適化を選択できますが、その場合は、とにかく違いに気付くことはありません。


* 2つはまったく異なります。前者は本質的に最適化手法ですが、後者は言語によって義務付けられており、観察可能なプログラムの動作に影響を与える可能性があります。

私は当初、これは遅延評価とはまったく異なることを提案しましたが、@ Ingoが以下のコメントで指摘しているように、これは疑わしい主張です。Javaの短絡演算子は、遅延評価の非常に限られたアプリケーションと見なすことができます。

ただし、関数型言語が遅延評価セマンティクスを義務付けている場合、それは通常、まったく異なる理由、つまり無限(または少なくとも過度の)再帰の防止のためです。

于 2013-03-03T18:33:49.257 に答える
17

いいえ、Javaにはユーザー定義メソッドの熱心な評価しかありません。ご存知のように、Javaの言語構造のいくつかは非厳密な評価を実装しています。その他には、、、が含まif?:ますwhile

私はかつて[1]、「遅延評価を行う」とはどういう意味かについて混乱があることを学びました[1]。まず、遅延評価とは、必要に応じた評価を意味します。Javaにはこのようなものはまったくありません。ただし、遅延評価の定義を緩めて名前による呼び出しの評価も含めるという一般的な傾向があります(私は個人的にはお勧めしません)。などの機能は&&、必要性による呼び出しと名前による呼び出しの評価では区別できません。それは関係なく同じだろう、それは問題を曖昧にする。

この緩みを考慮に入れて、Javaが次のように遅延評価を行っていると主張することにより、さらに反論する人もいます。

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

&&次に、次のようにユーザー定義を記述します。

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

次に、これはJavaの評価が遅延していることを示しているという主張が提起されます。ただし、これは緩い意味でも遅延評価ではありません。booleanここでの際立った点は、Javaの型システムは型/Booleanとを統合しないということThunk<Boolean>です。一方を他方として使用しようとすると、タイプエラーが発生します。静的型システムがない場合でも、コードは失敗します。質問に答えるのは、この統一性の欠如(静的型付けかどうか)です。いいえ、Javaにはユーザー定義の遅延評価がありません。もちろん、上記のようにエミュレートすることもできますが、これは、turing-equivalentに続く興味深い観察ではありません。

andScalaなどの言語には、名前による呼び出しの評価があり、通常の関数と同等のユーザー定義関数を使用でき&&ます(終了を考慮に入れます。[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());
   }
}

呼び出された関数colesceは、最初に指定されたnull以外の値を返します(SQLの場合と同様)。その呼び出しの2番目のパラメーターはLambda式です。メソッドveryLongMethod()は、argv [0]==nullの場合にのみ呼び出されます。この場合の唯一のペイロードは、()->オンデマンドで遅延評価される値の前に挿入することです。

于 2014-09-27T14:40:32.693 に答える
3

簡単にするために、Java8の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 DocumentationonStreamからのものです。

ストリームは怠惰です。ソースデータの計算は、端末操作が開始されたときにのみ実行され、ソース要素は必要な場合にのみ消費されます。

参照

そうは言っても、次のようにすると何も表示されません。イニシエーターを追加しない限り。

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

isBTrue()がtrueを返す場合、isATrue()は呼び出されますか?

はい、両方とも呼ばれます。

于 2013-03-03T18:33:36.003 に答える
0

いいえ、違います。isBTrue()の結果に関係なく、が呼び出されますisATrue()。各メソッドにprintステートメントを使用してこのようなプログラムを作成することにより、これを自分で確認できます。

于 2013-03-03T18:33:51.970 に答える
0

明示的に行で呼び出しているため、 YesisATrue()が呼び出されますboolean a = isATrue();

ただし、次の場合は呼び出されませisBTrue()true

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