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?
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.
*
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).
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.
}
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/
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.
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
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.
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.
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;
});
IsATrue() est-il appelé si isBTrue() renvoie true?
Oui, les deux sont appelés.
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.
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();
}