Operador de atalho “ou atribuição” (| =) em Java

89

Tenho um longo conjunto de comparações para fazer em Java e gostaria de saber se uma ou mais delas são verdadeiras. A sequência de comparações era longa e difícil de ler, então eu a separei para facilitar a leitura e automaticamente passei a usar um operador de atalho em |=vez de negativeValue = negativeValue || boolean.

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

Espero negativeValueque seja verdade se algum dos valores padrão de <algo> for negativo. Isso é válido? Vai fazer o que eu espero? Eu não pude ver isso mencionado no site da Sun ou stackoverflow, mas o Eclipse não parece ter problemas com isso e o código compila e executa.


Da mesma forma, se eu quisesse realizar várias interseções lógicas, poderia usar em &=vez de &&?

David Mason
fonte
13
Por que você não tenta?
Romano
Esta é a lógica booleana geral, não apenas Java. para que você possa pesquisar em outros lugares. E por que você simplesmente não tenta?
Dykam
16
@Dykam: Não, há um comportamento específico aqui. Java poderia escolher fazer | = curto-circuito, de forma que não avaliasse o RHS se o LHS já fosse verdadeiro - mas não é verdade.
Jon Skeet,
2
@Jon Skeet: O curto-circuito seria apropriado para o ||=operador não existente , mas |=é a forma de combinação do operador bit a bit ou.
David Thornley,
1
@Jon Skeet: Claro, mas fazer um |=curto-circuito seria inconsistente com outros operadores de atribuição compostos, uma vez a |= b;que não seria o mesmo que a = a | b;, com a advertência usual sobre avaliar aduas vezes (se for importante). Parece-me que a grande decisão sobre o comportamento da linguagem não estava ocorrendo ||=, então não estou entendendo.
David Thornley,

Respostas:

204

O |=é um operador de atribuição composto ( JLS 15.26.2 ) para o operador lógico booleano |( JLS 15.22.2 ); não deve ser confundido com o condicional ou ||( JLS 15.24 ). Existem também &=e ^=correspondentes à versão de atribuição composta da lógica booleana &e ^respectivamente.

Em outras palavras, para boolean b1, b2, esses dois são equivalentes:

 b1 |= b2;
 b1 = b1 | b2;

A diferença entre os operadores lógicos (& e |) em comparação com suas contrapartes condicionais ( &&e ||) é que os primeiros não "curto-circuito"; o último faz. Isso é:

  • &e | sempre avaliar ambos os operandos
  • &&e ||avaliar o operando certo condicionalmente ; o operando certo é avaliado apenas se seu valor puder afetar o resultado da operação binária. Isso significa que o operando certo NÃO é avaliado quando:
    • O operando esquerdo de && avalia parafalse
      • (porque não importa qual seja a avaliação do operando correto, a expressão inteira é false)
    • O operando esquerdo de || avalia paratrue
      • (porque não importa qual seja a avaliação do operando correto, a expressão inteira é true)

Então, voltando à sua pergunta original, sim, essa construção é válida, e while |=não é exatamente um atalho equivalente para =e|| , ele calcula o que você deseja. Como o lado direito do |=operador em seu uso é uma operação simples de comparação de inteiros, o fato de |não causar curto-circuito é insignificante.

Há casos em que o curto-circuito é desejado, ou mesmo obrigatório, mas o seu cenário não é um deles.

É uma pena que, ao contrário de outras linguagens, Java não tenha &&=e ||=. Isso foi discutido na pergunta Por que o Java não tem versões de atribuição composta dos operadores condicional e e condicional ou? (&& =, || =) .

poligenelubrificantes
fonte
1, muito completo. parece plausível que um compilador pode muito bem se converter em um operador de curto-circuito, se puder determinar que o RHS não tem efeitos colaterais. alguma pista sobre isso?
Carl,
Eu li que quando o RHS é trivial e o SC não é necessário, os operadores SC "inteligentes" são na verdade um pouco mais lentos. Se for verdade, então é mais interessante imaginar se alguns compiladores podem converter SC em NSC sob certas circunstâncias.
poligenelubrificantes de
@polygenelubricants operadores em curto-circuito envolvem algum tipo de ramificação por baixo do capô, então, se não houver um padrão amigável de previsão de ramificação para os valores verdade usados ​​com os operadores e / ou a arquitetura em uso não tem boa / nenhuma previsão de ramificação ( e supondo que o compilador e / ou a máquina virtual não faça nenhuma otimização relacionada por conta própria), então sim, os operadores de SC apresentarão alguma lentidão em comparação com o não-curto-circuito. A melhor maneira de descobrir se o compilador faz alguma coisa seria compilar um programa usando SC vs. NSC e depois comparar o bytecode para ver se ele difere.
JAB de
Além disso, não deve ser confundido com o operador OR bit a bit que também é|
user253751
16

Não é um operador de "atalho" (ou curto-circuito) da maneira que || e && são (no sentido de que eles não avaliarão o RHS se já souberem o resultado baseado no LHS), mas fará o que você quiser em termos de trabalho .

Como um exemplo da diferença, este código ficará bem se textfor nulo:

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

enquanto isso não:

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

(Obviamente, você poderia servir "".equals(text)nesse caso específico - estou apenas tentando demonstrar o princípio.)

Jon Skeet
fonte
3

Você poderia ter apenas uma declaração. Expresso em várias linhas, ele é lido quase exatamente como seu código de amostra, mas menos imperativo:

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

Para expressões mais simples, usar |pode ser mais rápido do que ||porque, embora evite fazer uma comparação, significa usar uma ramificação implicitamente e isso pode ser muito mais caro.

Peter Lawrey
fonte
Comecei com apenas um, mas, conforme declarado na pergunta original, achei que "A sequência de comparações era longa e difícil de ler, por isso quebrei para facilitar a leitura". Deixando isso de lado, neste caso estou mais interessado em aprender o comportamento de | = do que em fazer esta parte específica do código funcionar.
David Mason,
1

Embora possa ser um exagero para o seu problema, a biblioteca Guava tem uma boa sintaxe com se Predicatefaz avaliação de curto-circuito de or / e Predicates.

Essencialmente, as comparações são transformadas em objetos, empacotadas em uma coleção e, em seguida, iteradas. Para ou predicados, o primeiro hit verdadeiro retorna da iteração e vice-versa para e.

Carl
fonte
1

Se for sobre legibilidade, tenho o conceito de separação de dados testados da lógica de teste. Amostra 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;
}

O código parece mais detalhado e autoexplicativo. Você pode até criar uma matriz na chamada de método, assim:

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

É mais legível do que 'string de comparação' e também tem a vantagem de desempenho de curto-circuito (ao custo de alocação de array e chamada de método).

Editar: ainda mais legibilidade pode ser alcançada simplesmente usando parâmetros varargs:

A assinatura do método seria:

boolean checkIfAnyNegative(DataType ... data)

E a chamada pode ser assim:

checkIfAnyNegative( defaultStock, defaultWholesale, defaultRetail, defaultDelivery );
Krzysztof Jabłoński
fonte
1
A alocação de matriz e uma chamada de método são um custo muito grande para curto-circuito, a menos que você tenha algumas operações caras nas comparações (o exemplo em questão é barato). Dito isso, na maioria das vezes a facilidade de manutenção do código vai superar as considerações de desempenho. Eu provavelmente usaria algo assim se estivesse fazendo a comparação de maneira diferente em vários lugares diferentes ou comparando mais de 4 valores, mas para um único caso é um tanto prolixo para o meu gosto.
David Mason,
@DavidMason eu concordo. No entanto, tenha em mente que a maioria das calculadoras modernas engoliria esse tipo de sobrecarga em menos de alguns milis. Pessoalmente, não me importaria com a sobrecarga até problemas de desempenho, o que parece ser razoável . Além disso, o detalhamento do código é uma vantagem, especialmente quando o javadoc não é fornecido ou gerado pelo JAutodoc.
Krzysztof Jabłoński
1

É um post antigo, mas para dar uma perspectiva diferente para iniciantes, gostaria de dar um exemplo.

Acho que o caso de uso mais comum para um operador composto semelhante seria +=. Tenho certeza de que todos nós escrevemos algo assim:

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

Qual foi o objetivo disto? O objetivo era evitar boilerplate e eliminar o código repetitivo.

Assim, a próxima linha faz exatamente o mesmo, evitando digitar a variável b1duas vezes na mesma linha.

b1 |= b2;
oxyt
fonte
1
É mais difícil detectar erros na operação e nos nomes dos operadores, especialmente quando os nomes são longos. 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
fonte
Acho que preferiria negativeValue = defaultStock < 0 || defaultWholesale < 0etc. Além da ineficiência de todo o encaixotamento e embalagem acontecendo aqui, não acho tão fácil entender o que seu código realmente significaria.
Jon Skeet,
Se ele tem ainda mais de 4 parâmetros e o critério é o mesmo para todos eles, eu gosto da minha solução, mas por uma questão de legibilidade, eu separaria em várias linhas (ou seja, criar lista, encontrar minvalue, comparar minvalue com 0) .
Romano
Isso parece útil para um grande número de comparações. Nesse caso, acho que a redução na clareza não vale a pena economizar na digitação etc.
David Mason,
0

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

| = bit a bit OR inclusivo e operador de atribuição

A razão pela qual | = não curto-circuito é porque ele faz um OR bit a bit e não um OR lógico. Quer dizer:

C | = 2 é igual a C = C | 2

Tutorial para operadores java

oneklc
fonte
não há nenhum bit a bit OR com booleanos! O operador |é um número inteiro OR bit a bit, mas também é OR lógico - consulte a Especificação de linguagem Java 15.22.2.
user85421
Você está correto porque para um único bit (um booleano) bit a bit e OR lógico são equivalentes. Embora praticamente, o resultado é o mesmo.
oneklc de