web-dev-qa-db-fra.com

Java a-t-il une évaluation paresseuse?

Je sais que Java a une évaluation intelligente/paresseuse dans ce cas:

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

Mais qu'en est-il:

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

isATrue() est-elle appelée même si isBTrue() retourne vrai?

26
m0skit0

Dans Java (et autres langages de type C), ceci est appelé évaluation des courts-circuits .*

Et oui, dans le deuxième exemple, isATrue est toujours appelé. Autrement dit, à moins que le compilateur/JVM ne puisse déterminer qu'il n'a pas d'effets secondaires observables, auquel cas il peut choisir d'optimiser, mais dans ce cas, vous ne remarquerez pas la différence de toute façon.


Les deux sont bien distincts; la première est essentiellement une technique d'optimisation, tandis que la seconde est imposée par le langage et peut affecter le comportement du programme observable.

À l'origine, j'ai suggéré que cela était tout à fait distinct de l'évaluation paresseuse, mais comme @Ingo le souligne dans les commentaires ci-dessous, c'est une affirmation douteuse. On peut voir les opérateurs de court-circuit dans Java comme une application très limitée d'évaluation paresseuse.

Cependant, lorsque les langages fonctionnels exigent une sémantique d'évaluation paresseuse, c'est généralement pour une raison bien différente, à savoir la prévention de la récursion infinie (ou du moins excessive).

21

Eh bien, en ce qui concerne la langue - oui, les deux fonctions sont appelées.

Si vous avez réécrit la fonction à ceci:

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

alors la deuxième fonction ne sera pas appelée, si la première est vraie.


Mais c'est évaluation de court-circuit, pas évaluation paresseuse. Le cas d'évaluation paresseuse ressemblerait à ceci:

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

Non, Java n'a qu'une évaluation avide des méthodes définies par l'utilisateur. Certaines constructions de langage Java implémentent une évaluation non stricte comme vous le notez. D'autres incluent if, ?:, while.

J'ai appris une fois [1] qu'il y a une certaine confusion autour de ce que signifie "avoir une évaluation paresseuse". Premièrement, une évaluation paresseuse signifie une évaluation par appel . Java n'a rien du tout comme ça. Cependant, il existe une tendance commune (que je décourage personnellement) de desserrer la définition de l'évaluation paresseuse pour inclure également appel évaluation par nom . Les fonctions telles que && ne peuvent pas être distinguées sous évaluation par appel par rapport à l'évaluation par appel par nom; ce serait la même chose, ce qui obscurcit la question.

Compte tenu de ce relâchement, certains contre-argumentent en affirmant Java a une évaluation paresseuse par les éléments suivants:

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

Ensuite, vous pouvez écrire un && Défini par l'utilisateur comme ceci:

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

L'affirmation est ensuite avancée que cela démontre que Java a une évaluation paresseuse. Cependant, ce n'est pas une évaluation paresseuse, même au sens large. Le point distinctif ici est que le système de types de Java n'unifie pas les types boolean/Boolean et Thunk<Boolean>. Tenter d'utiliser l'un comme l'autre résultera dans une erreur de type . En l'absence d'un système de type statique, le code échouerait toujours. C'est ce manque d'unification (typage statique ou non ) qui répond à la question; non, Java n'a pas d'évaluation paresseuse définie par l'utilisateur. Bien sûr, elle peut être émulée comme ci-dessus, mais c'est une observation sans intérêt qui découle de l'équivalence de Turing.

Un langage tel que Scala a une évaluation appel par nom, qui permet une fonction and définie par l'utilisateur qui est équivalente à la fonction régulière && (En prenant (voir [1]).

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

Ceci permet:

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

Notez que cet extrait de programme court se termine en fournissant une valeur. Il unifie également les valeurs de type Boolean comme appel par nom. Par conséquent, si vous souscrivez à la définition vague de l'évaluation paresseuse (et encore une fois, je décourage cela), vous pourriez dire que Scala a une évaluation paresseuse. Je fournis Scala = ici comme un bon contraste. Je recommande de regarder Haskell pour une véritable évaluation paresseuse (appel par besoin).

J'espère que cela t'aides!

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

17
Tony Morris

SE8 (JDK1.8) a introduit expressions Lambda , ce qui peut rendre les évaluations paresseuses plus transparentes. Considérez l'instruction dans la méthode principale du code suivant:

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

La fonction appelée coalesce renvoie la première valeur non nulle donnée (comme dans SQL). Le deuxième paramètre de son appel est une expression Lambda. La méthode veryLongMethod () sera appelée uniquement lorsque argv [0] == null. La seule charge utile dans ce cas est l'insertion de ()-> avant la valeur à évaluer paresseusement sur demande.

11
Dima

Pour plus de simplicité, vous pouvez utiliser l'interface fournisseur de Java 8 comme ceci:

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

Ensuite, dans le code, vous pouvez avoir:

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

Je voulais juste ajouter en plus de ce qui a été mentionné dans ce fil de question, ci-dessous est de la documentation Oracle sur JVM

a Java peut choisir de résoudre chaque référence symbolique dans une classe ou une interface individuellement lorsqu'elle est utilisée (résolution "paresseuse" ou "tardive"), ou de les résoudre toutes en même temps lorsque la classe est en cours de vérification (résolution "désireuse" ou "statique"), ce qui signifie que le processus de résolution peut se poursuivre, dans certaines implémentations, après l'initialisation d'une classe ou d'une interface.

référence

et comme exemple des classes qui ont une implémentation paresseuse est Stream, c'est de la documentation Oracle sur Stream

Les flux sont paresseux; le calcul sur les données source n'est effectué que lorsque l'opération de terminal est lancée, et les éléments source sont consommés uniquement selon les besoins.

référence

Cela étant dit, si vous procédez comme suit, rien ne sera affiché. Sauf si vous ajoutez l'initiateur.

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

Oui isATrue() sera appelé car vous l'appelez explicitement dans la ligne boolean a = isATrue();

Mais il ne sera pas appelé dans le cas suivant si isBTrue() retourne true:

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

IsATrue () est-elle appelée si isBTrue () renvoie vrai?

Oui, les deux sont appelés.

0
NPE

Non. isBTrue() sera invoquée, quel que soit le résultat de isATrue(). Vous pouvez le vérifier vous-même en écrivant un tel programme, avec des instructions d'impression dans chacune des méthodes.

0
Jeremy Roman