Ярлык оператора «или-присваивание» (| =) в Java

89

У меня есть длинный набор сравнений на Java, и я хотел бы знать, верны ли одно или несколько из них. Строка сравнений была длинной и сложной для чтения, поэтому я разбил ее для удобства чтения и автоматически перешел к использованию ярлыка оператора |=вместо negativeValue = negativeValue || boolean.

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

Я ожидаю, что negativeValueэто будет правда, если любое из значений <something> по умолчанию отрицательно. Это действительно так? Будет ли оно делать то, что я ожидаю? Я не видел, чтобы это упоминалось на сайте Sun или stackoverflow, но у Eclipse, похоже, нет проблем с этим, и код компилируется и запускается.


Точно так же, если бы я хотел выполнить несколько логических пересечений, мог бы я использовать &=вместо &&?

Дэвид Мейсон
источник
13
Почему бы тебе не попробовать?
Роман
Это общая логическая логика, а не только Java. так что вы можете найти его в других местах. А почему бы тебе просто не попробовать?
Dykam
16
@Dykam: Нет, здесь есть особое поведение. Java может выбрать выполнение | = короткого замыкания, чтобы не оценивать RHS, если LHS уже истинно, но это не так.
Джон Скит
2
@Jon Skeet: Короткое замыкание подходит для несуществующего ||=оператора, но |=представляет собой комбинацию побитового оператора или.
Дэвид Торнли
1
@Jon Skeet: Конечно, но выполнение |=короткого замыкания будет несовместимо с другими операторами составного присваивания, поскольку это a |= b;будет не то же самое, что a = a | b;и с обычным предупреждением об оценке aдважды (если это имеет значение). Мне кажется, что большого решения о языковом поведении не было ||=, поэтому я упускаю вашу точку зрения.
Дэвид Торнли,

Ответы:

204

Это |=составной оператор присваивания ( JLS 15.26.2 ) для логического оператора |( JLS 15.22.2 ); не следует путать с условным-или ||( JLS 15.24 ). Также существуют &=и ^=соответствующие версии составного присваивания логического логического значения &и ^соответственно.

Другими словами, для boolean b1, b2этих двух эквивалентов:

 b1 |= b2;
 b1 = b1 | b2;

Разница между логическими операторами ( &и |) по сравнению с их условными аналогами ( &&и ||) заключается в том, что первые не «закорачивают»; последние делают. То есть:

  • &и | всегда оценивать оба операнда
  • &&и условно|| оценить правый операнд ; правый операнд оценивается только в том случае, если его значение может повлиять на результат двоичной операции. Это означает, что правильный операнд НЕ оценивается, когда:
    • Левый операнд &&оценивается какfalse
      • (потому что независимо от того, что оценивает правый операнд, все выражение равно false)
    • Левый операнд ||оценивается какtrue
      • (потому что независимо от того, что оценивает правый операнд, все выражение равно true)

Итак, возвращаясь к исходному вопросу, да, эта конструкция действительна, и хотя |=это не совсем эквивалентный ярлык для =и ||, она вычисляет то, что вы хотите. Поскольку правая часть |=оператора в вашем использовании представляет собой простую операцию целочисленного сравнения, тот факт, что |не происходит короткого замыкания, не имеет значения.

Бывают случаи, когда короткое замыкание желательно или даже необходимо, но ваш сценарий не входит в их число.

К сожалению, в отличие от некоторых других языков, в Java нет &&=и ||=. Это обсуждалось в вопросе Почему в Java нет составных версий присваивания для операторов условного и и условного или операторов? (&& =, || =) .

полигенные смазочные материалы
источник
+1, очень основательно. кажется правдоподобным, что компилятор вполне мог бы преобразовать в оператор короткого замыкания, если бы он мог определить, что RHS не имеет побочных эффектов. любая подсказка об этом?
Карл,
Я читал, что когда RHS тривиален и SC не нужен, "умные" операторы SC на самом деле немного медленнее. Если это так, то интереснее задаться вопросом, могут ли некоторые компиляторы конвертировать SC в NSC при определенных обстоятельствах.
polygenelubricants
Короткозамкнутые операторы @polygenelubricants включают в себя какую-то ветвь под капотом, поэтому, если нет удобного для предсказания ветвления шаблона для значений истинности, используемых с операторами и / или используемой архитектурой, нет хорошего / любого предсказания ветвления ( и предполагая, что компилятор и / или виртуальная машина сами не выполняют никаких связанных оптимизаций), тогда да, операторы SC внесут некоторую медлительность по сравнению с отсутствием короткого замыкания. Лучший способ узнать, что компилятор делает что-либо, - это скомпилировать программу с использованием SC и NSC, а затем сравнить байт-код, чтобы увидеть, отличается ли он.
JAB
Кроме того, не следует путать с побитовым оператором ИЛИ, который также является|
user253751
16

Это не «сокращающий» (или сокращающий) оператор в том смысле, в котором || и && есть (в том смысле, что они не будут оценивать RHS, если они уже знают результат на основе LHS), но он будет делать то, что вы хотите, с точки зрения работы .

В качестве примера разницы этот код подойдет, если он textравен нулю:

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

тогда как это не будет:

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

(Очевидно, вы могли бы сделать это "".equals(text)в этом конкретном случае - я просто пытаюсь продемонстрировать принцип.)

Джон Скит
источник
3

У вас может быть только одно заявление. Выраженный в нескольких строках, он читается почти так же, как ваш пример кода, только менее обязательно:

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

Для простейших выражений использование |может быть быстрее, чем ||потому, что даже если оно избегает сравнения, оно означает неявное использование ветки, а это может быть во много раз дороже.

Питер Лоури
источник
Я действительно начал с одного, но, как было сказано в исходном вопросе, я чувствовал, что «Строка сравнений была длинной и трудной для чтения, поэтому я разбил ее для удобства чтения». Помимо этого, в данном случае меня больше интересует поведение | =, чем заставить работать этот конкретный фрагмент кода.
Дэвид Мейсон
1

Хотя это может быть излишним для вашей проблемы, библиотека Guava имеет приятный синтаксис с Predicates и выполняет короткую оценку or / and Predicates.

По сути, сравнения превращаются в объекты, упаковываются в коллекцию, а затем повторяются. Для предикатов или первое истинное попадание возвращается с итерации, и наоборот для и.

Карл
источник
1

Если речь идет о читабельности, у меня есть концепция отделения проверенных данных от логики тестирования. Пример кода:

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

Код выглядит более подробным и понятным. Вы даже можете создать массив при вызове метода, например:

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

Он более читабелен, чем «строка сравнения», а также имеет преимущество в производительности за счет короткого замыкания (за счет выделения массива и вызова метода).

Изменить: еще большей читабельности можно просто добиться, используя параметры varargs:

Сигнатура метода будет:

boolean checkIfAnyNegative(DataType ... data)

И звонок мог выглядеть так:

checkIfAnyNegative( defaultStock, defaultWholesale, defaultRetail, defaultDelivery );
Кшиштоф Яблоньски
источник
1
Распределение массива и вызов метода - довольно большие затраты для короткого замыкания, если у вас нет дорогостоящих операций при сравнении (хотя пример в вопросе дешевый). При этом в большинстве случаев ремонтопригодность кода важнее соображений производительности. Я бы, вероятно, использовал что-то подобное, если бы проводил сравнение по-разному в кучу разных мест или сравнивал более 4 значений, но для одного случая это несколько многословно на мой вкус.
Дэвид Мейсон
@DavidMason Я согласен. Однако имейте в виду, что большинство современных калькуляторов поглотят такие накладные расходы менее чем за несколько миллисекунд. Лично меня не волнуют накладные расходы, пока не возникнут проблемы с производительностью, что кажется разумным . Кроме того, многословие кода является преимуществом, особенно когда javadoc не предоставляется или не генерируется JAutodoc.
Krzysztof Jabłoński
1

Это старый пост, но чтобы дать новичкам другую перспективу, я хотел бы привести пример.

Я думаю, что наиболее распространенным вариантом использования подобного составного оператора будет +=. Я уверен, что все мы писали что-то вроде этого:

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

Какой в ​​этом был смысл? Суть заключалась в том, чтобы избежать шаблонов и устранить повторяющийся код.

Итак, следующая строка делает то же самое, избегая b1двойного ввода переменной в одной строке.

b1 |= b2;
оксит
источник
1
Труднее обнаружить ошибки в операциях и именах операторов, особенно если имена длинные. longNameOfAccumulatorAVariable += 5; vs. longNameOfAccumulatorAVariable = longNameOfAccumulatorVVariable + 5;
Андрей
0
List<Integer> params = Arrays.asList (defaultStock, defaultWholesale, 
                                       defaultRetail, defaultDelivery);
int minParam = Collections.min (params);
negativeValue = minParam < 0;
Римский
источник
Думаю, я бы предпочел negativeValue = defaultStock < 0 || defaultWholesale < 0и т. Д. Помимо неэффективности всего происходящего здесь упаковки и упаковки, мне не так легко понять, что на самом деле означает ваш код.
Джон Скит
Если у него даже больше 4 параметров, и критерий для всех один и тот же, тогда мне нравится мое решение, но для удобства чтения я бы разделил его на несколько строк (т.е. создать список, найти minvalue, сравнить minvalue с 0) .
Роман
Это действительно полезно для большого количества сравнений. В этом случае я думаю, что снижение четкости не стоит экономии на печатании и т. Д.
Дэвид Мейсон
0

|| логическое логическое ИЛИ
| побитовое ИЛИ

| = побитовое включающее ИЛИ и оператор присваивания

Причина, по которой | = не сокращается, заключается в том, что он выполняет побитовое ИЛИ, а не логическое ИЛИ. То есть:

C | = 2 совпадает с C = C | 2

Учебник для операторов Java

Oneklc
источник
НЕТ побитового ИЛИ с логическими значениями! Оператор |представляет собой целочисленное побитовое ИЛИ, но также является логическим ИЛИ - см. Спецификацию языка Java 15.22.2.
user85421
Вы правы, потому что для одного бита (логического) побитовое и логическое ИЛИ эквивалентны. Хотя практически результат тот же.
oneklc