Operador de acceso directo "o-asignación" (| =) en Java

89

Tengo un largo conjunto de comparaciones que hacer en Java y me gustaría saber si una o más de ellas resultan verdaderas. La cadena de comparaciones era larga y difícil de leer, así que la dividí para facilitar la lectura y automáticamente usé un operador de acceso directo en |=lugar de negativeValue = negativeValue || boolean.

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

Espero que negativeValuesea ​​cierto si alguno de los valores predeterminados de <algo> es negativo. ¿Es esto válido? ¿Hará lo que espero? No pude verlo mencionado en el sitio de Sun o stackoverflow, pero Eclipse no parece tener ningún problema y el código se compila y se ejecuta.


De manera similar, si quisiera realizar varias intersecciones lógicas, ¿podría usar en &=lugar de &&?

David Mason
fuente
13
¿Por qué no lo intentas?
Roman
Esta es la lógica booleana general, no solo Java. para que puedas buscarlo en otros lugares. ¿Y por qué no lo intentas?
Dykam
16
@Dykam: No, aquí hay un comportamiento específico. Java podría optar por hacer | = cortocircuito, de modo que no evaluará el RHS si el LHS ya es verdadero, pero no lo hace.
Jon Skeet
2
@Jon Skeet: El cortocircuito sería apropiado para el ||=operador inexistente , pero |=es la forma de combinación del operador u bit a bit.
David Thornley
1
@Jon Skeet: Claro, pero hacer |=un cortocircuito sería inconsistente con otros operadores de asignación compuesta, ya a |= b;que no sería lo mismo que a = a | b;, con la advertencia habitual de evaluar ados veces (si es que importa). Me parece que la gran decisión de comportamiento lingüístico fue no tomar ||=, así que me pierdo el punto.
David Thornley

Respuestas:

204

El |=es un operador de asignación compuesto ( JLS 15.26.2 ) para el operador lógico booleano |( JLS 15.22.2 ); no debe confundirse con el condicional-or ||( JLS 15.24 ). También hay &=y ^=correspondientes a la versión de asignación compuesta de la lógica booleana &y^ respectivamente.

En otras palabras, boolean b1, b2estos dos son equivalentes:

 b1 |= b2;
 b1 = b1 | b2;

La diferencia entre los operadores lógicos ( &y |) en comparación con sus contrapartes condicionales ( &&y ||) es que los primeros no "cortocircuitan"; los últimos lo hacen. Es decir:

  • &y | siempre evalúe ambos operandos
  • &&y ||evaluar el operando correcto condicionalmente ; el operando derecho se evalúa solo si su valor podría afectar el resultado de la operación binaria. Eso significa que el operando correcto NO se evalúa cuando:
    • El operando izquierdo de se &&evalúa comofalse
      • (porque no importa a qué se evalúe el operando correcto, la expresión completa es false)
    • El operando izquierdo de se ||evalúa comotrue
      • (porque no importa a qué se evalúe el operando correcto, la expresión completa es true)

Entonces, volviendo a su pregunta original, sí, esa construcción es válida, y aunque |=no es exactamente un atajo equivalente para =y ||, calcula lo que desea. Dado que el lado derecho del|= operador en su uso es una simple operación de comparación de enteros, el hecho de que |no se cortocircuite es insignificante.

Hay casos en los que se desea, o incluso se requiere, un cortocircuito, pero su escenario no es uno de ellos.

Es lamentable que, a diferencia de otros lenguajes, Java no tenga &&=y ||=. Esto se discutió en la pregunta ¿Por qué Java no tiene versiones de asignación compuesta de los operadores condicional-and y condicional-or? (&& =, || =) .

poligenelubricantes
fuente
+1, muy completo. Parece plausible que un compilador pueda convertirse en un operador de cortocircuito, si pudiera determinar que el RHS no tiene efectos secundarios. alguna pista sobre eso?
Carl
Leí que cuando RHS es trivial y SC no es necesario, los operadores SC "inteligentes" son en realidad un poco más lentos. Si es cierto, entonces es más interesante preguntarse si algunos compiladores pueden convertir SC a NSC en determinadas circunstancias.
polygenelubricants
Los operadores en cortocircuito de @polygenelubricants involucran una rama de algún tipo bajo el capó, por lo que si no hay un patrón amigable para la predicción de rama para los valores de verdad usados ​​con los operadores y / o la arquitectura en uso no tiene una predicción buena / ninguna rama ( y asumiendo que el compilador y / o la máquina virtual no hacen ninguna optimización relacionada por sí mismos), entonces sí, los operadores SC introducirán algo de lentitud en comparación con los no cortocircuitos. La mejor manera de averiguar si el compilador hace algo sería compilar un programa usando SC vs NSC y luego comparar el código de bytes para ver si difiere.
JAB
Además, no debe confundirse con el operador OR bit a bit, que también es|
user253751
16

No es un operador de "atajo" (o cortocircuito) de la manera que || y && son (en el sentido de que no evaluarán el RHS si ya conocen el resultado basado en el LHS) pero hará lo que quieras en términos de trabajo .

Como ejemplo de la diferencia, este código estará bien si textes nulo:

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

mientras que esto no:

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

(Obviamente, podría hacerlo "".equals(text)para ese caso en particular, solo estoy tratando de demostrar el principio).

Jon Skeet
fuente
3

Podrías tener solo una declaración. Expresado en varias líneas, se lee casi exactamente como su código de muestra, solo que menos imperativo:

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

Para las expresiones más simples, usar |puede ser más rápido que ||porque, aunque evita hacer una comparación, significa usar una rama implícitamente y eso puede ser muchas veces más costoso.

Peter Lawrey
fuente
Empecé con solo una, pero como se indica en la pregunta original, sentí que "la cadena de comparaciones era larga y difícil de leer, así que la dividí para facilitar la lectura". Aparte de eso, en este caso estoy más interesado en aprender el comportamiento de | = que en hacer que este código en particular funcione.
David Mason
1

Aunque puede ser excesivo para su problema, la biblioteca de Guava tiene una buena sintaxis con Predicatesy realiza una evaluación de cortocircuito de o y Predicates.

Esencialmente, las comparaciones se convierten en objetos, se empaquetan en una colección y luego se repiten. Para predicados o, el primer acierto verdadero regresa de la iteración, y viceversa para y.

Carl
fuente
1

Si se trata de legibilidad, tengo el concepto de separación de datos probados de la lógica de prueba. Muestra de código:

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

El código parece más detallado y autoexplicativo. Incluso puede crear una matriz en la llamada al método, como esta:

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

Es más legible que la 'cadena de comparación' y también tiene la ventaja de rendimiento del cortocircuito (a costa de la asignación de la matriz y la llamada al método).

Editar: Se puede lograr incluso más legibilidad simplemente usando parámetros varargs:

La firma del método sería:

boolean checkIfAnyNegative(DataType ... data)

Y la llamada podría verse así:

checkIfAnyNegative( defaultStock, defaultWholesale, defaultRetail, defaultDelivery );
Krzysztof Jabłoński
fuente
1
La asignación de matrices y una llamada a un método tienen un costo bastante alto para el cortocircuito, a menos que tenga algunas operaciones costosas en las comparaciones (aunque el ejemplo de la pregunta es barato). Habiendo dicho eso, la mayoría de las veces la capacidad de mantenimiento del código prevalecerá sobre las consideraciones de rendimiento. Probablemente usaría algo como esto si estuviera haciendo la comparación de manera diferente en un montón de lugares diferentes o comparando más de 4 valores, pero para un solo caso es algo detallado para mi gusto.
David Mason
@DavidMason Estoy de acuerdo. Sin embargo, tenga en cuenta que la mayoría de las calculadoras modernas se tragarían ese tipo de gastos generales en menos de unos pocos milis. Personalmente, no me importaría los gastos generales hasta que surjan problemas de rendimiento, lo que parece razonable . Además, la verbosidad del código es una ventaja, especialmente cuando JAutodoc no proporciona ni genera javadoc.
Krzysztof Jabłoński
1

Es una publicación antigua, pero para brindar una perspectiva diferente a los principiantes, me gustaría dar un ejemplo.

Creo que el caso de uso más común para un operador compuesto similar sería +=. Estoy seguro de que todos escribimos algo como esto:

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

¿Cuál fue el punto de esto? El objetivo era evitar la repetición y eliminar el código repetitivo.

Entonces, la siguiente línea hace exactamente lo mismo, evitando escribir la variable b1dos veces en la misma línea.

b1 |= b2;
oxyt
fuente
1
Es más difícil detectar errores en la operación y los nombres de los operadores, especialmente cuando los nombres son largos. 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;
romano
fuente
Creo que preferiría, negativeValue = defaultStock < 0 || defaultWholesale < 0etc. Aparte de la ineficacia de todo el encajonamiento y envoltura que está sucediendo aquí, no me resulta tan fácil entender lo que realmente significaría su código.
Jon Skeet
Si tiene incluso más de 4 parámetros y el criterio es el mismo para todos ellos, entonces me gusta mi solución, pero en aras de la legibilidad, la separaría en varias líneas (es decir, crear Lista, encontrar minvalue, comparar minvalue con 0) .
Roman
Eso parece útil para una gran cantidad de comparaciones. En este caso, creo que la reducción en la claridad no vale la pena ahorrar al escribir, etc.
David Mason
0

|| OR lógico booleano
| O bit a bit

| = operador de asignación y OR inclusivo bit a bit

La razón por la que | = no tiene un cortocircuito es porque hace un OR bit a bit O no un OR lógico. Es decir:

C | = 2 es lo mismo que C = C | 2

Tutorial para operadores java

oneklc
fuente
¡NO hay O bit a bit con booleanos! El operador |es OR entero bit a bit, pero también es OR lógico; consulte la Especificación del lenguaje Java 15.22.2.
user85421
Tiene razón porque para un solo bit (un booleano) bit a bit y el OR lógico son equivalentes. Aunque prácticamente, el resultado es el mismo.
oneklc