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 de:

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

isATrue() est-il appelé même si isBTrue() renvoie true?

22
demandé sur m0skit0 2013-03-03 22:31:58

9 réponses

En Java (et dans d'autres langages de type C), ceci est appelé évaluation de court-circuit.*

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


* Les deux sont tout à fait distincts; le premier est essentiellement une optimisation technique, tandis que la seconde est mandatée par le langage et peut affecter le comportement du programme observable.

J'ai initialement suggéré que c'était assez distinct de l'évaluation paresseuse, mais comme le souligne @Ingo dans les commentaires ci-dessous, c'est une affirmation douteuse. On peut considérer les opérateurs de court-circuit en Java comme une application très limitée de l'évaluation paresseuse.

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

19
répondu Oliver Charlesworth 2013-03-05 12:56:01

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 différée. Le cas d'évaluation paresseux 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.
}
26
répondu emesx 2013-05-13 18:29:26

Non, Java n'a qu'une évaluation avide pour les 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."Tout d'abord, l'évaluation paresseuse signifieévaluation par appel par besoin . Java n'a rien du tout comme ça. Cependant, il existe une tendance commune (que je décourage personnellement) pour desserrer la définition de paresseux évaluation de également inclure appel par nom évaluation. Des fonctions telles que && ne peuvent pas être distinguées sous l'évaluation appel par besoin par rapport à l'évaluation appel par nom; ce serait la même chose, ce qui obscurcit la question.

En tenant compte de ce relâchement, certains contre-argumentent en affirmant que Java a une évaluation paresseuse par ce qui suit:

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

Ensuite, vous pouvez écrire un && défini par l'utilisateur comme suit:

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

L'affirmation est alors avancée que cela démontre que Java a une évaluation paresseuse. Cependant, ceci est Pas évaluation paresseuse, même dans le sens lâche. Le point distinctif ici est que le système de type de Java n'unifie pas les types boolean/Boolean et Thunk<Boolean>. Tenter d'utiliser l'un comme l'autre entraînera 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. De bien sûr, il peut être émulé comme ci-dessus, mais c'est une observation inintéressante qui découle de l'équivalence de turing.

Un langage tel que Scala a une évaluation call-by-name, qui permet une fonction and définie par l'utilisateur qui est équivalente à la fonction && régulière (en tenant compte de la terminaison. Voir [1]).

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

Cela permet:

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

Notez que cet extrait de programme court termine en fournissant une valeur. Il unifie également les valeurs de type Boolean, comme en appel par nom. Par conséquent, si vous vous abonnez à la définition lâche 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 aide!

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

16
répondu Tony Morris 2013-05-31 10:37:01

SE8 (JDK1.8) a introduit Les expressions Lambda, qui peuvent 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 d'insérer ()-> avant la valeur à évaluer paresseusement demande.

11
répondu Dima 2014-09-27 14:40:32

Pour simplifier, 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
répondu George 2017-01-30 10:33:41

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

Une implémentation de machine virtuelle Java peut choisir de résoudre chaque référence symbolique dans une classe ou une interface individuellement quand il est utilisé (résolution "paresseuse" ou "tardive"), ou pour les résoudre tous à la fois lorsque la classe est vérifiée (résolution" eager "ou" statique"). Cela signifie que le processus de résolution peut se poursuivre, dans certains implémentations, après l'initialisation d'une classe ou d'une interface.

Référence

Et comme un exemple des classes qui ont une implémentation paresseuse est Stream, cela provient de la Documentation Oracle sur Stream

Les flux sont paresseux; le calcul sur les données source est uniquement effectué lorsque l'opération de terminal est initiée, et les éléments source sont consommé seulement au besoin.

Référence

Cela étant dit, si vous faites le la suite, rien ne sera affiché. Sauf si vous ajoutez un initiateur.

Steam.of(1, 2, 3, 4, 5).filter(number -> {
   System.out.println("This is not going to be logged");
   return true;
});
1
répondu Abdullah Shahin 2018-03-17 16:57:35

IsATrue() est-il appelé si isBTrue() renvoie true?

Oui, les deux sont appelés.

0
répondu NPE 2013-03-03 18:33:36

Non, ce n'est pas le cas. isBTrue() sera invoqué, 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
répondu Jeremy Roman 2013-03-03 18:33:51

Oui {[1] } sera appelé parce que 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
répondu Vishal K 2013-03-03 18:39:57