Operator skrótu „lub-przypisanie” (| =) w Javie

89

Mam do zrobienia w Javie długi zestaw porównań i chciałbym wiedzieć, czy jedno lub więcej z nich okaże się prawdą. Ciąg porównań był długi i trudny do odczytania, więc podzieliłem go ze względu na czytelność i automatycznie przeszedłem do użycia operatora skrótu |=zamiast negativeValue = negativeValue || boolean.

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

Oczekuję, negativeValueże będzie prawdziwe, jeśli którakolwiek z domyślnych wartości <coś> jest ujemna. Czy to jest ważne? Czy zrobi to, czego oczekuję? Nie widziałem tego wspomnianego w witrynie Sun ani w stosie, ale wydaje się, że Eclipse nie ma z tym problemu, a kod kompiluje się i działa.


Podobnie, gdybym chciał wykonać kilka logicznych przecięć, czy mógłbym użyć &=zamiast &&?

David Mason
źródło
13
Dlaczego tego nie spróbujesz?
Roman
To jest ogólna logika boolowska, a nie tylko Java. abyś mógł sprawdzić to w innych miejscach. Dlaczego po prostu tego nie spróbujesz?
Dykam
16
@Dykam: Nie, tutaj jest określone zachowanie. Java może zdecydować się na utworzenie | = zwarcia, tak że nie będzie oceniać RHS, jeśli LHS jest już prawdziwy - ale tak nie jest.
Jon Skeet,
2
@Jon Skeet: Zwarcie byłoby odpowiednie dla nieistniejącego ||=operatora, ale |=jest to kombinacja operatora bitowego lub.
David Thornley,
1
@Jon Skeet: Jasne, ale wykonanie |=zwarcia byłoby niespójne z innymi operatorami przypisania złożonego, ponieważ a |= b;nie byłoby takie samo jak a = a | b;, ze zwykłym zastrzeżeniem dotyczącym adwukrotnego oceniania (jeśli ma to znaczenie). Wydaje mi się, że nie podjęto decyzji dotyczącej dużego języka ||=, więc nie rozumiem.
David Thornley,

Odpowiedzi:

204

|=To operatorowi przypisanie związek ( na trasę 15.26.2 ) do logicznej operator logiczny |( na trasę 15.22.2 ); nie mylić z warunkowym lub ||( JLS 15.24 ). Istnieją również &=i ^=odpowiadające złożonej wersji przypisania logiki boolowskiej &i ^odpowiednio.

Innymi słowy boolean b1, b2, te dwa są równoważne:

 b1 |= b2;
 b1 = b1 | b2;

Różnica między operatorami logicznymi ( &i |) w porównaniu z ich odpowiednikami warunkowymi ( &&i ||) polega na tym, że te pierwsze nie powodują „zwarcia”; to drugie. To jest:

  • &i | zawsze obliczaj oba operandy
  • &&i warunkowo|| oceniać właściwy operand ; prawy operand jest oceniany tylko wtedy, gdy jego wartość mogłaby wpłynąć na wynik operacji binarnej. Oznacza to, że prawy operand NIE jest oceniany, gdy:
    • Lewy operand wartości &&szacuje dofalse
      • (ponieważ bez względu na to, do jakiego wyniku zostanie obliczony właściwy operand, całe wyrażenie jest false)
    • Lewy operand wartości ||szacuje dotrue
      • (ponieważ bez względu na to, do jakiego wyniku zostanie obliczony właściwy operand, całe wyrażenie jest true)

Wracając do pierwotnego pytania, tak, ta konstrukcja jest prawidłowa i chociaż |=nie jest dokładnie równoważnym skrótem dla =i ||, oblicza to, czego chcesz. Ponieważ prawa strona |=operatora w twoim użyciu jest prostą operacją porównywania liczb całkowitych, fakt, że |nie powoduje zwarcia, jest nieistotny.

Zdarzają się przypadki, w których zwarcie jest pożądane, a nawet wymagane, ale Twój scenariusz nie jest jednym z nich.

Szkoda, że ​​w przeciwieństwie do niektórych innych języków Java nie ma &&=i ||=. Zostało to omówione w pytaniu Dlaczego Java nie ma złożonych wersji przypisania operatorów warunkowych i warunkowych lub? (&& =, || =) .

smary wielogenowe
źródło
+1, bardzo dokładny. wydaje się prawdopodobne, że kompilator mógłby równie dobrze przekształcić się w operator zwarcia, gdyby mógł stwierdzić, że RHS nie ma skutków ubocznych. jakieś wskazówki na ten temat?
Carl
Czytałem, że kiedy RHS jest trywialne, a SC nie jest konieczne, „inteligentne” operatory SC są właściwie trochę wolniejsze. Jeśli to prawda, bardziej interesujące jest zastanowienie się, czy niektóre kompilatory mogą w pewnych okolicznościach przekonwertować SC na NSC.
smary wielogenowe
@polygenelubricants zwarte operatory obejmują jakąś gałąź pod maską, więc jeśli nie ma wzorca przyjaznego dla przewidywania gałęzi dla wartości prawdy używanych z operatorami i / lub używana architektura nie ma dobrej / żadnej prognozy gałęzi ( i zakładając, że kompilator i / lub maszyna wirtualna nie wykonują samodzielnie żadnych powiązanych optymalizacji), to tak, operatorzy SC wprowadzą pewne spowolnienie w porównaniu z nie powodującymi zwarć. Najlepszym sposobem, aby dowiedzieć się, czy kompilator coś robi, byłoby skompilowanie programu przy użyciu SC i NSC, a następnie porównanie kodu bajtowego, aby sprawdzić, czy różni się.
JAB
Nie należy go również mylić z operatorem bitowym OR, który również jest|
user253751
16

Nie jest to operator „skrótu” (lub zwarcia) w taki sposób, że || i && są (w tym sensie, że nie ocenią RHS, jeśli już znają wynik na podstawie LHS), ale zrobią to, co chcesz pod względem pracy .

Jako przykład różnicy, ten kod będzie dobrze, jeśli textma wartość null:

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

ale to nie będzie:

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

(Oczywiście możesz to zrobić "".equals(text)w tym konkretnym przypadku - próbuję tylko zademonstrować zasadę).

Jon Skeet
źródło
3

Możesz mieć tylko jedno stwierdzenie. Wyrażony w wielu wierszach, czyta prawie dokładnie tak, jak przykładowy kod, tylko mniej konieczne:

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

W przypadku najprostszych wyrażeń użycie |może być szybsze niż ||dlatego, że nawet jeśli pozwala uniknąć porównania, oznacza to niejawne użycie gałęzi, a to może być wielokrotnie droższe.

Peter Lawrey
źródło
Zacząłem od jednego, ale jak stwierdzono w pierwotnym pytaniu, czułem, że „ciąg porównań był długi i trudny do odczytania, więc podzieliłem go dla większej czytelności”. Poza tym, w tym przypadku bardziej interesuje mnie poznanie zachowania | = niż wykonanie tego konkretnego fragmentu kodu.
David Mason,
1

Chociaż może to być przesada dla twojego problemu, biblioteka Guava ma fajną składnię z Predicates i dokonuje oceny zwarć i / i Predicates.

Zasadniczo porównania są przekształcane w obiekty, pakowane w kolekcję, a następnie powtarzane. Dla lub predykaty, pierwsze prawdziwe trafienie wraca z iteracji i odwrotnie dla i.

Carl
źródło
1

Jeśli chodzi o czytelność, mam koncepcję oddzielenia testowanych danych od logiki testowania. Przykład kodu:

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

Kod wygląda na bardziej szczegółowy i zrozumiały. Możesz nawet utworzyć tablicę w wywołaniu metody, na przykład:

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

Jest bardziej czytelny niż „ciąg porównawczy”, a także ma przewagę wydajności w postaci zwarcia (kosztem alokacji tablicy i wywołania metody).

Edycja: Jeszcze większą czytelność można po prostu osiągnąć za pomocą parametrów varargs:

Podpis metody wyglądałby następująco:

boolean checkIfAnyNegative(DataType ... data)

A wezwanie mogłoby wyglądać tak:

checkIfAnyNegative( defaultStock, defaultWholesale, defaultRetail, defaultDelivery );
Krzysztof Jabłoński
źródło
1
Alokacja tablicy i wywołanie metody są dość dużym kosztem zwarcia, chyba że w porównaniach masz jakieś kosztowne operacje (przykład w pytaniu jest jednak tani). To powiedziawszy, przez większość czasu łatwość utrzymania kodu ma pierwszeństwo przed kwestiami wydajności. Prawdopodobnie użyłbym czegoś takiego, gdybym robił porównanie inaczej w kilku różnych miejscach lub porównując więcej niż 4 wartości, ale dla jednego przypadku jest to nieco rozwlekłe dla moich upodobań.
David Mason
@DavidMason Zgadzam się. Należy jednak pamiętać, że większość nowoczesnych kalkulatorów połknęłaby tego rodzaju obciążenie w mniej niż kilka milisekund. Osobiście nie przejmowałbym się narzutami do czasu wystąpienia problemów z wydajnością, które wydają się rozsądne . Ponadto szczegółowość kodu jest zaletą, zwłaszcza gdy javadoc nie jest udostępniany ani generowany przez JAutodoc.
Krzysztof Jabłoński
1

To stary post, ale aby przedstawić inną perspektywę początkującym, chciałbym podać przykład.

Myślę, że najczęstszym przypadkiem użycia dla podobnego operatora złożonego byłoby +=. Jestem pewien, że wszyscy napisaliśmy coś takiego:

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

Jaki był tego cel? Chodziło o uniknięcie schematu i wyeliminowanie powtarzającego się kodu.

Tak więc następna linia robi dokładnie to samo, unikając wpisywania zmiennej b1dwa razy w tym samym wierszu.

b1 |= b2;
oxyt
źródło
1
Trudniej jest dostrzec błędy w operacji i nazwach operatorów, zwłaszcza gdy nazwy są długie. longNameOfAccumulatorAVariable += 5; kontra longNameOfAccumulatorAVariable = longNameOfAccumulatorVVariable + 5;
Andrei
0
List<Integer> params = Arrays.asList (defaultStock, defaultWholesale, 
                                       defaultRetail, defaultDelivery);
int minParam = Collections.min (params);
negativeValue = minParam < 0;
rzymski
źródło
Myślę, że wolałbym negativeValue = defaultStock < 0 || defaultWholesale < 0itd. Pomijając nieefektywność całego pakowania i pakowania, które tu się dzieje, nie jest mi tak łatwo zrozumieć, co naprawdę oznaczałby twój kod.
Jon Skeet,
Jeśli ma nawet więcej niż 4 parametry i kryterium jest takie samo dla nich wszystkich to podoba mi się moje rozwiązanie, ale ze względu na czytelność rozdzieliłbym je na kilka wierszy (czyli stwórz Listę, znajdź minwartość, porównaj minwartość z 0) .
Roman
Wydaje się to przydatne w przypadku wielu porównań. W tym przypadku myślę, że zmniejszenie jasności nie jest warte oszczędzania w pisaniu itp.
David Mason
0

|| logiczne boolowskie OR
| bitowe OR

| = bitowe operator OR i przypisanie

Powodem, dla którego | = nie shortcircit jest to, że wykonuje bitowe OR, a nie logiczne OR. To jest do powiedzenia:

C | = 2 to to samo, co C = C | 2

Samouczek dla operatorów Java

oneklc
źródło
nie ma bitowego LUB z logicznymi! Operator |jest liczbą całkowitą bitową OR, ale jest też logicznym OR - zobacz specyfikację języka Java 15.22.2.
user85421
Masz rację, ponieważ dla pojedynczego bitu (wartość logiczna) bitowe i logiczne OR są równoważne. Chociaż praktycznie wynik jest taki sam.
oneklc