Verknüpfungsoperator "oder Zuweisung" (| =) in Java

89

Ich habe eine lange Reihe von Vergleichen in Java zu erledigen, und ich würde gerne wissen, ob einer oder mehrere davon als wahr herauskommen. Die Reihe der Vergleiche war lang und schwer zu lesen, daher habe ich sie aus Gründen der Lesbarkeit aufgelöst und automatisch einen Verknüpfungsoperator |=anstelle von verwendet negativeValue = negativeValue || boolean.

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

Ich erwarte negativeValue, dass dies zutrifft, wenn einer der Standardwerte für <something> negativ ist. Ist das gültig? Wird es tun, was ich erwarte? Ich konnte nicht sehen, dass es auf Suns Site oder im Stackoverflow erwähnt wurde, aber Eclipse scheint kein Problem damit zu haben und der Code wird kompiliert und ausgeführt.


Wenn ich mehrere logische Schnittpunkte ausführen wollte, könnte ich &=stattdessen anstelle von verwenden &&?

David Mason
quelle
13
Warum versuchst du es nicht?
Roman
Dies ist eine allgemeine boolesche Logik, nicht nur Java. so können Sie es an anderen Stellen nachschlagen. Und warum probierst du es nicht einfach aus?
Dykam
16
@Dykam: Nein, hier gibt es ein bestimmtes Verhalten. Java könnte | = kurzschließen, so dass die RHS nicht ausgewertet wird, wenn die LHS bereits wahr ist - aber nicht.
Jon Skeet
2
@ Jon Skeet: Kurzschluss wäre für den nicht existierenden ||=Operator geeignet , ist aber |=die Kombinationsform des bitweisen Operators oder Operators.
David Thornley
1
@ Jon Skeet: Sicher, aber ein |=Kurzschluss wäre mit anderen Operatoren a |= b;für die Zuweisung von Verbindungen unvereinbar, da dies nicht dasselbe wäre wie a = a | b;mit der üblichen Einschränkung, azweimal auszuwerten (wenn es darauf ankommt). Es sieht für mich so aus, als ob die große Entscheidung über das Sprachverhalten nicht getroffen wurde ||=, also vermisse ich Ihren Standpunkt.
David Thornley

Antworten:

203

Das |=ist eine Verbindung Zuweisungsoperator ( JLS 15.26.2 ) für den Booleschen logischen Operator |( JLS 15.22.2 ); nicht zu verwechseln mit der Bedingung-oder ||( JLS 15.24 ). Darüber hinaus gibt es &=und ^=entsprechend die Verbindung Zuordnung Version der boolean logischen &und ^jeweils.

Mit anderen Worten, denn boolean b1, b2diese beiden sind äquivalent:

 b1 |= b2;
 b1 = b1 | b2;

Der Unterschied zwischen den logischen Operatoren ( &und |) im Vergleich zu ihren bedingten Gegenstücken ( &&und ||) besteht darin, dass die ersteren nicht "kurzschließen"; Letztere tun. Das ist:

  • &und bewerten Sie | immer beide Operanden
  • &&und ||den richtigen Operanden bedingt bewerten ; Der richtige Operand wird nur ausgewertet, wenn sein Wert das Ergebnis der Binäroperation beeinflussen könnte. Das bedeutet, dass der richtige Operand NICHT ausgewertet wird, wenn:
    • Der linke Operand von &&wertet auf ausfalse
      • (denn egal, was der richtige Operand auswertet, der gesamte Ausdruck ist false)
    • Der linke Operand von ||wertet auf austrue
      • (denn egal, was der richtige Operand auswertet, der gesamte Ausdruck ist true)

Zurück zu Ihrer ursprünglichen Frage: Ja, dieses Konstrukt ist gültig, und obwohl |=es nicht genau eine äquivalente Verknüpfung für =und ist ||, berechnet es, was Sie wollen. Da die rechte Seite des |=Operators in Ihrer Verwendung eine einfache Ganzzahlvergleichsoperation ist, ist die Tatsache, dass |kein Kurzschluss auftritt, unerheblich.

Es gibt Fälle, in denen ein Kurzschluss gewünscht oder sogar erforderlich ist, Ihr Szenario jedoch nicht dazu gehört.

Es ist bedauerlich, dass Java im Gegensatz zu einigen anderen Sprachen kein &&=und hat ||=. Dies wurde in der Frage diskutiert, warum Java keine zusammengesetzten Zuweisungsversionen der Bedingungs- und und Bedingungs- oder Operatoren hat. (&& =, || =) .

Polygenschmierstoffe
quelle
+1, sehr gründlich. Es erscheint plausibel, dass ein Compiler möglicherweise in einen Kurzschlussoperator konvertiert, wenn er feststellen kann, dass die RHS keine Nebenwirkungen hat. Hast du einen Hinweis darauf?
Carl
Ich habe gelesen, dass die "intelligenten" SC-Operatoren tatsächlich etwas langsamer sind, wenn RHS trivial ist und SC nicht erforderlich ist. Wenn dies zutrifft, ist es interessanter, sich zu fragen, ob einige Compiler SC unter bestimmten Umständen in NSC konvertieren können.
Polygenschmierstoffe
Bei kurzgeschlossenen Operatoren von @polygenelubricants handelt es sich um eine Art Verzweigung unter der Haube. Wenn also kein verzweigungsvorhersagefreundliches Muster für die mit den Operatoren verwendeten Wahrheitswerte vorliegt und / oder die verwendete Architektur keine gute / keine Verzweigungsvorhersage aufweist ( und unter der Annahme, dass der Compiler und / oder die virtuelle Maschine selbst keine entsprechenden Optimierungen vornehmen), führen SC-Operatoren im Vergleich zu Nicht-Kurzschlüssen eine gewisse Langsamkeit ein. Der beste Weg, um herauszufinden, ob der Compiler etwas tut, besteht darin, ein Programm mit SC vs. NSC zu kompilieren und dann den Bytecode zu vergleichen, um festzustellen, ob er sich unterscheidet.
JAB
Nicht zu verwechseln mit dem bitweisen ODER-Operator, der auch |
user253751
16

Es ist kein "Shortcut" -Operator (oder Kurzschlussoperator) in der Weise, wie || und && sind (insofern werden sie die RHS nicht bewerten, wenn sie das Ergebnis basierend auf der LHS bereits kennen), aber es wird tun, was Sie in Bezug auf die Arbeit wollen .

Als Beispiel für den Unterschied ist dieser Code in Ordnung, wenn er textnull ist:

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

während dies nicht wird:

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

(Natürlich könnten Sie dies "".equals(text)für diesen speziellen Fall tun - ich versuche nur, das Prinzip zu demonstrieren.)

Jon Skeet
quelle
3

Sie könnten nur eine Aussage haben. Über mehrere Zeilen ausgedrückt, liest es sich fast genau wie Ihr Beispielcode, nur weniger wichtig:

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

Für einfachste Ausdrücke kann die Verwendung |schneller sein als ||weil, obwohl ein Vergleich vermieden wird, die implizite Verwendung einer Verzweigung bedeutet und dies um ein Vielfaches teurer sein kann.

Peter Lawrey
quelle
Ich habe mit nur einem angefangen, aber wie in der ursprünglichen Frage angegeben, hatte ich das Gefühl, dass "die Reihe der Vergleiche lang und schwer zu lesen war, also habe ich sie aus Gründen der Lesbarkeit aufgelöst". Abgesehen davon bin ich in diesem Fall mehr daran interessiert, das Verhalten von | = zu lernen, als daran, dass dieser bestimmte Code funktioniert.
David Mason
1

Obwohl es für Ihr Problem übertrieben sein könnte, ist das Guava- Bibliothek eine nette Syntax mit Predicates und führt eine Kurzschlussauswertung von oder / und Predicates durch.

Im Wesentlichen werden die Vergleiche in Objekte umgewandelt, in eine Sammlung gepackt und dann wiederholt. Für oder Prädikate kehrt der erste echte Treffer aus der Iteration zurück und umgekehrt für und.

Carl
quelle
1

Wenn es um Lesbarkeit geht, habe ich das Konzept der Trennung von getesteten Daten aus der Testlogik. Codebeispiel:

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

Der Code sieht ausführlicher und selbsterklärender aus. Sie können sogar ein Array im Methodenaufruf erstellen, wie folgt:

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

Es ist besser lesbar als 'Vergleichszeichenfolge' und hat auch den Leistungsvorteil des Kurzschlusses (auf Kosten der Arrayzuweisung und des Methodenaufrufs).

Bearbeiten: Noch mehr Lesbarkeit kann einfach durch Verwendung von varargs-Parametern erreicht werden:

Methodensignatur wäre:

boolean checkIfAnyNegative(DataType ... data)

Und der Anruf könnte so aussehen:

checkIfAnyNegative( defaultStock, defaultWholesale, defaultRetail, defaultDelivery );
Krzysztof Jabłoński
quelle
1
Array-Zuweisung und ein Methodenaufruf sind ziemlich hohe Kosten für das Kurzschließen, es sei denn, Sie haben einige teure Operationen in den Vergleichen (das Beispiel in der Frage ist jedoch billig). Allerdings wird die Wartbarkeit des Codes die Leistungsüberlegungen meistens übertreffen. Ich würde wahrscheinlich so etwas verwenden, wenn ich den Vergleich an verschiedenen Orten anders durchführen oder mehr als 4 Werte vergleichen würde, aber für einen einzelnen Fall ist es für meinen Geschmack etwas ausführlich.
David Mason
@ DavidMason Ich stimme zu. Beachten Sie jedoch, dass die meisten modernen Taschenrechner diese Art von Overhead in weniger als ein paar Millis verschlucken würden. Persönlich würde ich mich nicht um Overhead kümmern, bis Leistungsprobleme auftreten, was vernünftig erscheint . Auch die Code-Ausführlichkeit ist von Vorteil, insbesondere wenn Javadoc nicht von JAutodoc bereitgestellt oder generiert wird.
Krzysztof Jabłoński
1

Es ist ein alter Beitrag, aber um Anfängern eine andere Perspektive zu bieten, möchte ich ein Beispiel geben.

Ich denke, der häufigste Anwendungsfall für einen ähnlichen zusammengesetzten Operator wäre +=. Ich bin sicher, wir haben alle so etwas geschrieben:

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

Was war der Sinn davon? Es ging darum, Boilerplate zu vermeiden und den sich wiederholenden Code zu eliminieren.

Die nächste Zeile macht also genau das Gleiche und vermeidet es, die Variable b1zweimal in dieselbe Zeile einzugeben.

b1 |= b2;
Oxyt
quelle
1
Es ist schwieriger, Fehler in der Operation und den Operatornamen zu erkennen, insbesondere wenn die Namen lang sind. 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;
römisch
quelle
Ich denke, ich würde es vorziehen negativeValue = defaultStock < 0 || defaultWholesale < 0usw. Abgesehen von der Ineffizienz all des Boxens und Verpackens, das hier stattfindet, finde ich es nicht annähernd so einfach zu verstehen, was Ihr Code wirklich bedeuten würde.
Jon Skeet
Wenn er noch mehr als 4 Parameter hat und das Kriterium für alle gleich ist, dann mag ich meine Lösung, aber aus Gründen der Lesbarkeit würde ich sie in mehrere Zeilen aufteilen (dh Liste erstellen, Minimalwert finden, Minimalwert mit 0 vergleichen). .
Roman
Das sieht für eine Vielzahl von Vergleichen nützlich aus. In diesem Fall denke ich, dass die Verringerung der Klarheit die Einsparung beim Tippen usw. nicht wert ist.
David Mason
0

|| logisches boolesches ODER
| bitweise ODER

| = bitweises Inklusiv-ODER und Zuweisungsoperator

Der Grund, warum | = nicht kurzschließt, ist, dass es ein bitweises ODER kein logisches ODER ausführt. Das heißt:

C | = 2 ist dasselbe wie C = C | 2

Tutorial für Java-Operatoren

oneklc
quelle
es gibt KEIN bitweises ODER mit Booleschen Werten! Der Operator |ist ein ganzzahliges bitweises ODER, aber auch ein logisches ODER - siehe Java-Sprachspezifikation 15.22.2.
user85421
Sie sind richtig, weil für ein einzelnes Bit (ein Boolescher Wert) bitweise und logisches ODER äquivalent sind. Obwohl praktisch, ist das Ergebnis das gleiche.
Oneklc