Opérateur de raccourci «or-assignment» (| =) en Java

89

J'ai une longue série de comparaisons à faire en Java et j'aimerais savoir si une ou plusieurs d'entre elles s'avèrent vraies. La chaîne de comparaisons était longue et difficile à lire, alors je l'ai interrompue pour plus de lisibilité et je suis automatiquement allée utiliser un opérateur de raccourci |=plutôt que negativeValue = negativeValue || boolean.

boolean negativeValue = false;
negativeValue |= (defaultStock < 0);
negativeValue |= (defaultWholesale < 0);
negativeValue |= (defaultRetail < 0);
negativeValue |= (defaultDelivery < 0);

Je m'attends negativeValueà être vrai si l'une des valeurs par défaut <quelque chose> est négative. Est-ce valable? Fera-t-il ce que j'attends? Je n'ai pas pu le voir mentionné sur le site de Sun ou sur stackoverflow, mais Eclipse ne semble pas avoir de problème avec cela et le code se compile et s'exécute.


De même, si je voulais effectuer plusieurs intersections logiques, pourrais-je utiliser à la &=place de &&?

David Mason
la source
13
Pourquoi ne pas l'essayer?
Roman
C'est une logique booléenne générale, pas seulement Java. afin que vous puissiez le rechercher ailleurs. Et pourquoi ne pas l'essayer?
Dykam
16
@Dykam: Non, il y a un comportement spécifique ici. Java pourrait choisir de faire | = un court-circuit, de sorte qu'il n'évalue pas le RHS si le LHS est déjà vrai - mais ce n'est pas le cas.
Jon Skeet
2
@Jon Skeet: Le court-circuit serait approprié pour l' ||=opérateur inexistant , mais il |=s'agit de la forme de combinaison de l'opérateur ou du bit.
David Thornley
1
@Jon Skeet: Bien sûr, mais en faisant |= court-circuit serait incompatible avec d'autres opérateurs d'assignation composée, car a |= b;ce ne serait pas la même chose que a = a | b;, avec la mise en garde habituelle sur l'évaluation adeux fois (si cela compte). Il me semble que la grande décision de comportement linguistique n'a pas été prise ||=, donc je rate votre point.
David Thornley

Réponses:

204

le |= est un opérateur d'affectation de composé ( JLS 15.26.2 ) pour l'opérateur logique booléen |( JLS 15.22.2 ); à ne pas confondre avec le conditionnel-or ||( JLS 15.24 ). Il existe également &=et ^=correspondant à la version d'affectation composée de la logique booléenne &et ^respectivement.

En d'autres termes, pour boolean b1, b2, ces deux sont équivalents:

 b1 |= b2;
 b1 = b1 | b2;

La différence entre les opérateurs logiques ( &et |) et leurs équivalents conditionnels ( &&et ||) est que les premiers ne "court-circuitent" pas; ce dernier le font. C'est:

  • &et | toujours évaluer les deux opérandes
  • &&et ||évaluer le bon opérande de manière conditionnelle ; l'opérande de droite n'est évalué que si sa valeur peut affecter le résultat de l'opération binaire. Cela signifie que l'opérande correct n'est PAS évalué lorsque:
    • L'opérande gauche de &&évalue àfalse
      • (car peu importe ce que le bon opérande évalue, l'expression entière est false)
    • L'opérande gauche de ||évalue àtrue
      • (car peu importe ce que le bon opérande évalue, l'expression entière est true)

Donc, pour revenir à votre question initiale, oui, cette construction est valide, et bien que |= n'est pas exactement un raccourci équivalent pour =et ||, elle calcule ce que vous voulez. Comme le côté droit de l' |=opérateur dans votre utilisation est une simple opération de comparaison d'entiers, le fait que| ne court-circuite pas est insignifiant.

Il existe des cas où un court-circuit est souhaité, voire nécessaire, mais votre scénario n'en fait pas partie.

Il est regrettable que contrairement à certains autres langages, Java n'ait pas &&=et ||=. Cela a été discuté dans la question Pourquoi Java n'a-t-il pas de versions d'assignation composée des opérateurs conditionnel et et conditionnel ou? (&& =, || =) .

lubrifiants polygènes
la source
+1, très complet. il semble plausible qu'un compilateur puisse très bien se convertir en opérateur de court-circuit, s'il pouvait déterminer que le RHS n'a pas d'effets secondaires. un indice à ce sujet?
Carl
J'ai lu que lorsque RHS est trivial et que SC n'est pas nécessaire, les opérateurs SC «intelligents» sont en fait un peu plus lents. Si c'est vrai, alors il est plus intéressant de se demander si certains compilateurs peuvent convertir SC en NSC dans certaines circonstances.
polygenelubricants
Les opérateurs en court-circuit @polygenelubricants impliquent une branche quelconque sous le capot, donc s'il n'y a pas de modèle convivial pour la prédiction de branche pour les valeurs de vérité utilisées avec les opérateurs et / ou l'architecture utilisée n'a pas de bonne / aucune prédiction de branche ( et en supposant que le compilateur et / ou la machine virtuelle n'effectuent aucune optimisation associée), alors oui, les opérateurs SC introduiront une certaine lenteur par rapport au non-court-circuit. La meilleure façon de découvrir que le compilateur fait n'importe quoi serait de compiler un programme utilisant SC vs NSC, puis de comparer le bytecode pour voir s'il diffère.
JAB
Aussi, à ne pas confondre avec l'opérateur OR au niveau du bit qui est également |
user253751
16

Ce n'est pas un opérateur de "raccourci" (ou de court-circuit) de la façon dont || et && sont (en ce sens qu'ils n'évalueront pas le RHS s'ils connaissent déjà le résultat basé sur le LHS) mais il fera ce que vous voulez en termes de travail .

À titre d'exemple de la différence, ce code conviendra s'il textest nul:

boolean nullOrEmpty = text == null || text.equals("")

alors que ce ne sera pas:

boolean nullOrEmpty = false;
nullOrEmpty |= text == null;
nullOrEmpty |= text.equals(""); // Throws exception if text is null

(Évidemment, vous pouvez le faire "".equals(text)pour ce cas particulier - j'essaie simplement de démontrer le principe.)

Jon Skeet
la source
3

Vous pourriez juste avoir une déclaration. Exprimé sur plusieurs lignes, il se lit presque exactement comme votre exemple de code, mais moins impératif:

boolean negativeValue
    = defaultStock < 0 
    | defaultWholesale < 0
    | defaultRetail < 0
    | defaultDelivery < 0;

Pour les expressions les plus simples, utiliser |peut être plus rapide que ||parce que même si cela évite de faire une comparaison, cela signifie utiliser une branche implicitement et cela peut être beaucoup plus cher.

Peter Lawrey
la source
J'ai commencé avec un seul, mais comme indiqué dans la question originale, j'ai senti que "la chaîne de comparaisons était longue et difficile à lire, donc je l'ai interrompue pour plus de lisibilité". Cela mis à part, dans ce cas, je suis plus intéressé à apprendre le comportement de | = qu'à faire fonctionner ce morceau de code particulier.
David Mason
1

Bien que cela puisse être excessif pour votre problème, la bibliothèque Guava a une bonne syntaxe avec Predicates et évalue en court-circuit ou / etPredicate s.

Essentiellement, les comparaisons sont transformées en objets, emballées dans une collection, puis répétées. Pour ou prédicats, le premier vrai hit revient de l'itération, et vice versa pour et.

Carl
la source
1

S'il s'agit de lisibilité, j'ai le concept de séparation des données testées à partir de la logique de test. Exemple de code:

// declare data
DataType [] dataToTest = new DataType[] {
    defaultStock,
    defaultWholesale,
    defaultRetail,
    defaultDelivery
}

// define logic
boolean checkIfAnyNegative(DataType [] data) {
    boolean negativeValue = false;
    int i = 0;
    while (!negativeValue && i < data.length) {
        negativeValue = data[i++] < 0;
    }
    return negativeValue;
}

Le code semble plus détaillé et explicite. Vous pouvez même créer un tableau dans l'appel de méthode, comme ceci:

checkIfAnyNegative(new DataType[] {
    defaultStock,
    defaultWholesale,
    defaultRetail,
    defaultDelivery
});

Elle est plus lisible que la «chaîne de comparaison» et présente également un avantage en termes de performances de court-circuit (au prix de l'allocation de tableau et de l'appel de méthode).

Edit: Encore plus de lisibilité peut être simplement obtenue en utilisant les paramètres varargs:

La signature de la méthode serait:

boolean checkIfAnyNegative(DataType ... data)

Et l'appel pourrait ressembler à ceci:

checkIfAnyNegative( defaultStock, defaultWholesale, defaultRetail, defaultDelivery );
Krzysztof Jabłoński
la source
1
L'allocation de tableaux et un appel de méthode sont un coût assez élevé pour le court-circuit, à moins que vous n'ayez des opérations coûteuses dans les comparaisons (l'exemple de la question est bon marché cependant). Cela dit, la plupart du temps, la maintenabilité du code l'emporte sur les considérations de performances. J'utiliserais probablement quelque chose comme ça si je faisais la comparaison différemment dans un tas d'endroits différents ou si je comparais plus de 4 valeurs, mais pour un seul cas, c'est un peu verbeux à mon goût.
David Mason
@DavidMason Je suis d'accord. Cependant, gardez à l'esprit que la plupart des calculatrices modernes absorberaient ce genre de frais généraux en moins de quelques millis. Personnellement, je ne me soucierais pas des frais généraux avant les problèmes de performance, ce qui semble raisonnable . De plus, la verbosité du code est un avantage, en particulier lorsque javadoc n'est pas fourni ou généré par JAutodoc.
Krzysztof Jabłoński
1

C'est un ancien post, mais afin de donner une perspective différente aux débutants, je voudrais donner un exemple.

Je pense que le cas d'utilisation le plus courant pour un opérateur composé similaire serait +=. Je suis sûr que nous avons tous écrit quelque chose comme ceci:

int a = 10;   // a = 10
a += 5;   // a = 15

Quel était le but de cela? Le but était d'éviter le passe-partout et d'éliminer le code répétitif.

Donc, la ligne suivante fait exactement la même chose, évitant de taper la variable b1deux fois dans la même ligne.

b1 |= b2;
oxyt
la source
1
Il est plus difficile de repérer les erreurs dans l'opération et les noms d'opérateurs, en particulier lorsque les noms sont longs. longNameOfAccumulatorAVariable += 5; vs. longNameOfAccumulatorAVariable = longNameOfAccumulatorVVariable + 5;
Andrei
0
List<Integer> params = Arrays.asList (defaultStock, defaultWholesale, 
                                       defaultRetail, defaultDelivery);
int minParam = Collections.min (params);
negativeValue = minParam < 0;
romain
la source
Je pense que je préférerais negativeValue = defaultStock < 0 || defaultWholesale < 0etc. Mis à part l'inefficacité de tous les emballages et emballages qui se déroulent ici, je ne trouve pas aussi facile de comprendre ce que votre code signifierait vraiment.
Jon Skeet
S'il a encore plus de 4 paramètres et le critère est le même pour tous, alors j'aime ma solution, mais pour des raisons de lisibilité, je la séparerais en plusieurs lignes (c'est-à-dire créer une liste, trouver minvalue, comparer minvalue à 0) .
Roman
Cela semble utile pour un grand nombre de comparaisons. Dans ce cas, je pense que la réduction de la clarté ne vaut pas l'économie de frappe, etc.
David Mason
0

|| logique booléenne OU
| OU au niveau du bit

| = Opérateur OU et d'affectation inclusif au niveau du bit

La raison pour laquelle | = ne court-circuite pas est parce qu'il fait un OU au niveau du bit OU pas un OU logique. C'est-à-dire:

C | = 2 est identique à C = C | 2

Tutoriel pour les opérateurs Java

oneklc
la source
il n'y a PAS de OU bit à bit avec des booléens! L'opérateur |est un OU au niveau du bit entier mais est également un OU logique - voir Spécification du langage Java 15.22.2.
user85421
Vous avez raison car pour un bit (un booléen) bit à bit et OR logique sont équivalents. Quoique pratiquement, le résultat est le même.
oneklc