Operatore di scelta rapida "or-assignment" (| =) in Java


89

Ho una lunga serie di confronti da fare in Java e mi piacerebbe sapere se uno o più di essi risultano veri. La serie di confronti era lunga e difficile da leggere, quindi l'ho suddivisa per leggibilità e sono passato automaticamente a utilizzare un operatore di scelta rapida |=anziché negativeValue = negativeValue || boolean.

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

Mi aspetto negativeValueche sia vero se uno qualsiasi dei valori <qualcosa> predefiniti è negativo. Questo è valido? Farà quello che mi aspetto? Non sono riuscito a vederlo menzionato sul sito di Sun o su stackoverflow, ma Eclipse non sembra avere problemi con esso e il codice viene compilato ed eseguito.


Allo stesso modo, se volessi eseguire più intersezioni logiche, potrei usare al &=posto di &&?


13
Perché non lo provi?
Roman

Questa è la logica booleana generale, non solo Java. così puoi cercarlo in altri posti. E perché non provi e basta?
Dykam

16
@Dykam: No, c'è un comportamento specifico qui. Java potrebbe scegliere di creare un cortocircuito | =, in modo tale che non valuti l'RHS se l'LHS è già vero, ma non lo fa.
Jon Skeet

2
@ Jon Skeet: il cortocircuito sarebbe appropriato per l' ||=operatore inesistente , ma |=è la forma di combinazione dell'operatore o bit per bit.
David Thornley,

1
@ Jon Skeet: certo, ma il |=cortocircuito sarebbe incoerente con altri operatori di assegnazione composta, poiché a |= b;non sarebbe lo stesso a = a | b;, con la solita avvertenza di valutare adue volte (se è importante). Mi sembra che la decisione sul grande comportamento linguistico non sia stata presa ||=, quindi mi manca il tuo punto.
David Thornley

Risposte:


204

La |=è un operatore di assegnamento composto ( JLS 15.26.2 ) per l'operatore logico booleano |( GLS 15.22.2 ); da non confondere con il condizionale-o ||( JLS 15.24 ). Ci sono anche &=e ^=corrispondenti alla versione di assegnazione composta del logico booleano &e ^rispettivamente.

In altre parole, per boolean b1, b2, questi due sono equivalenti:

 b1 |= b2;
 b1 = b1 | b2;

La differenza tra gli operatori logici ( &e |) rispetto alle loro controparti condizionali ( &&e ||) è che i primi non "cortocircuitano"; questi ultimi lo fanno. Questo è:

  • &e valuta | sempre entrambi gli operandi
  • &&e ||valutare l'operando giusto in modo condizionale ; l'operando di destra viene valutato solo se il suo valore potrebbe influenzare il risultato dell'operazione binaria. Ciò significa che l'operando giusto NON viene valutato quando:
    • L'operando sinistra &&restituiscefalse
      • (perché non importa quale sia l'operando giusto, l'intera espressione è false)
    • L'operando sinistra ||restituiscetrue
      • (perché non importa quale sia l'operando giusto, l'intera espressione è true)

Quindi, tornando alla tua domanda originale, sì, quel costrutto è valido, e sebbene |=non sia esattamente una scorciatoia equivalente per =e ||, calcola quello che vuoi. Poiché il lato destro |=dell'operatore nell'utilizzo è una semplice operazione di confronto di numeri interi, il fatto che |non cortocircuiti è irrilevante.

Ci sono casi in cui il cortocircuito è desiderato o addirittura richiesto, ma il tuo scenario non è uno di questi.

È un peccato che, a differenza di altri linguaggi, Java non abbia &&=e ||=. Questo è stato discusso nella domanda Perché Java non ha versioni di assegnazione composta degli operatori condizionale e e condizionale o? (&& =, || =) .


+1, molto approfondito. sembra plausibile che un compilatore potrebbe convertirsi in un operatore di cortocircuito, se potesse determinare che l'RHS non ha effetti collaterali. qualche indizio a riguardo?
Carl

Ho letto che quando RHS è banale e SC non è necessario, gli operatori SC "intelligenti" sono in realtà un po 'più lenti. Se è vero, è più interessante chiedersi se alcuni compilatori possono convertire SC in NSC in determinate circostanze.
lubrificanti poligenici

@polygenelubricants gli operatori in cortocircuito coinvolgono un ramo di qualche tipo sotto il cofano, quindi se non esiste un modello amichevole per la predizione dei rami per i valori di verità utilizzati con gli operatori e / o l'architettura in uso non ha una buona / nessuna previsione di ramo ( e supponendo che il compilatore e / o la macchina virtuale non eseguano personalmente le relative ottimizzazioni), allora sì, gli operatori SC introdurranno una certa lentezza rispetto al non cortocircuito. Il modo migliore per scoprire che il compilatore fa qualsiasi cosa sarebbe compilare un programma usando SC vs NSC e quindi confrontare il bytecode per vedere se è diverso.
JAB

Inoltre, da non confondere con l'operatore OR bit per bit che è anche |
user253751

16

Non è un operatore di "scorciatoia" (o cortocircuito) nel modo in cui || e && sono (in quanto non valuteranno l'RHS se conoscono già il risultato in base all'LHS) ma farà quello che vuoi in termini di lavoro .

Come esempio della differenza, questo codice andrà bene se textè nullo:

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

mentre questo non:

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

(Ovviamente potresti farlo "".equals(text)per quel caso particolare - sto solo cercando di dimostrare il principio.)


3

Potresti avere solo una dichiarazione. Espresso su più righe, si legge quasi esattamente come il codice di esempio, solo meno imperativo:

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

Per le espressioni più semplici, usare |può essere più veloce di ||perché, anche se evita di fare un confronto, significa usare implicitamente un ramo e questo può essere molte volte più costoso.


Ho iniziato con uno solo, ma come affermato nella domanda originale ho sentito che "La serie di confronti era lunga e difficile da leggere, quindi l'ho suddivisa per leggibilità". A parte questo, in questo caso sono più interessato ad apprendere il comportamento di | = che a far funzionare questo particolare pezzo di codice.
David Mason

1

Anche se potrebbe essere eccessivo per il tuo problema, la libreria Guava ha una buona sintassi con se Predicatefa una valutazione in cortocircuito di o / e Predicates.

In sostanza, i confronti vengono trasformati in oggetti, confezionati in una raccolta e quindi ripetuti. Per i predicati o, il primo vero risultato restituito dall'iterazione e viceversa per e.


1

Se si tratta di leggibilità, ho il concetto di separazione dei dati testati dalla logica di test. Esempio di codice:

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

Il codice sembra più dettagliato e autoesplicativo. Puoi anche creare un array nella chiamata al metodo, come questo:

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

È più leggibile della "stringa di confronto" e offre anche un vantaggio in termini di prestazioni del cortocircuito (al costo dell'allocazione dell'array e della chiamata al metodo).

Modifica: ancora più leggibilità può essere ottenuta semplicemente utilizzando i parametri varargs:

La firma del metodo sarebbe:

boolean checkIfAnyNegative(DataType ... data)

E la chiamata potrebbe assomigliare a questa:

checkIfAnyNegative( defaultStock, defaultWholesale, defaultRetail, defaultDelivery );

1
L'allocazione di array e una chiamata al metodo sono un costo piuttosto elevato per il cortocircuito, a meno che tu non abbia alcune operazioni costose nei confronti (l'esempio nella domanda è però economico). Detto questo, la maggior parte delle volte la manutenibilità del codice trionferà sulle considerazioni sulle prestazioni. Probabilmente userei qualcosa di simile se stessi facendo il confronto in modo diverso in un gruppo di luoghi diversi o confrontando più di 4 valori, ma per un singolo caso è un po 'prolisso per i miei gusti.
David Mason

@DavidMason Sono d'accordo. Tuttavia, tieni presente che la maggior parte dei calcolatori moderni inghiottirebbe quel tipo di sovraccarico in meno di pochi millisecondi. Personalmente non mi preoccuperei del sovraccarico fino a problemi di prestazioni, il che sembra essere ragionevole . Inoltre, la verbosità del codice è un vantaggio, specialmente quando javadoc non è fornito o generato da JAutodoc.
Krzysztof Jabłoński

1

È un vecchio post ma per fornire una prospettiva diversa ai principianti, vorrei fare un esempio.

Penso che il caso d'uso più comune per un operatore composto simile sarebbe +=. Sono sicuro che abbiamo scritto tutti qualcosa del genere:

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

Qual era il punto di tutto questo? Il punto era evitare il boilerplate ed eliminare il codice ripetitivo.

Quindi, la riga successiva fa esattamente lo stesso, evitando di digitare la variabile b1due volte nella stessa riga.

b1 |= b2;

1
È più difficile individuare errori nell'operazione e nei nomi degli operatori, soprattutto quando i nomi sono lunghi. 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;

Penso che preferirei negativeValue = defaultStock < 0 || defaultWholesale < 0ecc. A parte l'inefficienza di tutta la boxe e il confezionamento in corso qui, non trovo così facile capire cosa significherebbe veramente il tuo codice.
Jon Skeet

Ho anche più di 4 parametri e il criterio è lo stesso per tutti, quindi mi piace la mia soluzione, ma per motivi di leggibilità la separerei in più righe (es. Crea List, trova minvalue, confronta minvalue con 0) .
Roman

Sembra utile per un gran numero di confronti. In questo caso penso che la riduzione della chiarezza non valga il risparmio nella digitazione ecc.
David Mason

0

|| booleano logico OR
| bit per bit OR

| = OR inclusivo bit per bit e operatore di assegnazione

Il motivo per cui | = non è in cortocircuito è perché esegue un OR bit per bit non un OR logico. Vale a dire:

C | = 2 è uguale a C = C | 2

Tutorial per operatori Java


non c'è OR bit per bit con booleani! L'operatore |è un OR bit per bit intero ma è anche OR logico - vedere Java Language Specification 15.22.2.
user85421

Hai ragione perché per un singolo bit (un booleano) bit per bit e OR logico sono equivalenti. Anche se praticamente, il risultato è lo stesso.
oneklc
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.