Почему Java + +, - =, * =, / = составные операторы присваивания не требуют приведения?

3633

До сегодняшнего дня я думал что например

i += j;

Был просто ярлык для:

i = i + j;

Но если мы попробуем это:

int i = 5;
long j = 8;

Тогда i = i + j;не скомпилирует но i += j;скомпилирует нормально.

Значит ли это, что на самом деле i += j;это ярлык для чего-то подобного i = (type of i) (i + j)?

Хонза Брабек
источник
135
Я удивлен, что Java допускает это, будучи более строгим языком, чем его предшественники. Ошибки в приведении могут привести к критическому отказу, как в случае с Ariane5 Flight 501, где 64-разрядное преобразование с плавающей точкой в ​​16-разрядное целое число привело к сбою.
SQLDiver
103
В системе управления полетом, написанной на Java, это будет наименьшим количеством ваших забот @SQLDiver
Росс Дрю
10
На самом деле i+=(long)j;даже скомпилирует нормально.
Таринду Сатишчандра
6
Постоянный толчок одного набора разработчиков к точности, а другого к простоте использования действительно интересен. Нам почти нужны две версии языка: одна удивительно точная, а другая простая в использовании. Нажатие Java в обоих направлениях приводит к тому, что она не подходит ни для одной из групп.
Билл К
5
если бы это потребовало кастинга, куда бы вы его положили? i += (int) f;бросает f перед сложением, так что это не эквивалентно. (int) i += f;бросает результат после присваивания, также не эквивалентный. не было бы места для приведения, которое указывало бы на то, что вы хотите привести значение после добавления, но перед присваиванием.
Норилл Темпест

Ответы:

2441

Как всегда с этими вопросами, JLS держит ответ. В этом случае §15.26.2 Составные операторы присваивания . Выписка:

Выражение присваивания соединения формы E1 op= E2эквивалентно E1 = (T)((E1) op (E2)), где Tэто тип E1, за исключением того, что E1вычисляется только один раз.

Пример, приведенный в §15.26.2

[...] следующий код правильный:

short x = 3;
x += 4.6;

и в результате x имеет значение 7, потому что оно эквивалентно:

short x = 3;
x = (short)(x + 4.6);

Другими словами, ваше предположение верно.

Лукас Эдер
источник
42
Так что i+=jкомпилирует, как я проверял сам, но это приведет к потере точности, верно? Если это так, то почему это не происходит в i = i + j? Зачем нас там пугать?
bad_keypoints
46
@ronnieaka: Я предполагаю, что разработчики языка чувствовали, что в одном случае ( i += j) безопаснее предположить, что потеря точности желательна, а не в другом случае ( i = i + j)
Лукас Эдер,
12
Нет, прямо здесь, передо мной! Извините, я не заметил этого раньше. Как и в вашем ответе, E1 op= E2 is equivalent to E1 = (T)((E1) op (E2))это похоже на неявное приведение типа вниз (от long к int). В то время как в i = i + j мы должны сделать это явно, то есть предоставить (T)часть в E1 = ((E1) op (E2))Не так ли?
bad_keypoints
11
Вероятная причина того, почему Java-компилятор добавляет преобразование типов, заключается в том, что если вы пытаетесь выполнить арифметику с несовместимыми типами, нет способа выполнить преобразование типов результата с использованием сжатой формы. Типовое преобразование результата обычно более точное, чем типовое преобразование проблемного аргумента. Никакое приведение типов не сделало бы сокращение бесполезным при использовании несовместимых типов, поскольку это всегда заставляло бы компилятор выдавать ошибку.
ThePyroEagle
6
Это не округлено. Это произнесено (= усечено)
Лукас Эдер
483

Хорошим примером этого кастинга является использование * = или / =

byte b = 10;
b *= 5.7;
System.out.println(b); // prints 57

или

byte b = 100;
b /= 2.5;
System.out.println(b); // prints 40

или

char ch = '0';
ch *= 1.1;
System.out.println(ch); // prints '4'

или

char ch = 'A';
ch *= 1.5;
System.out.println(ch); // prints 'a'
Питер Лори
источник
11
@AkshatAgarwal ch это символ. 65 * 1,5 = 97,5 -> Понял?
Саджал Датта
79
Да, но я могу видеть, как какой-то новичок приходит сюда, читает это и уходит, думая, что вы можете преобразовать любой символ из прописных в строчные, умножив его на 1,5.
Дауд ибн Карим
103
@DavidWallace Любой персонаж, какой бы он ни был A;)
Питер Лори
14
@PeterLawrey & @DavidWallace Я раскрою ваш секрет - ch += 32 = D
Минхас Камаль
256

Очень хороший вопрос Спецификация языка Java подтверждает ваше предложение.

Например, следующий код является правильным:

short x = 3;
x += 4.6;

и в результате x имеет значение 7, потому что оно эквивалентно:

short x = 3;
x = (short)(x + 4.6);
Thirler
источник
15
Или веселее: "int x = 33333333; x + = 1.0f;".
суперкат
5
@supercat, что это за обман? Расширяющееся преобразование, которое неправильно округляется, сопровождаемое добавлением, которое фактически не меняет результат, снова приводит к int, чтобы получить результат, который является наиболее неожиданным для нормального человеческого разума.
neXus
1
@neXus: ИМХО, правила преобразования должны были бы рассматриваться double->floatкак расширяющиеся, исходя из того, что значения типа floatидентифицируют действительные числа менее конкретно, чем значения типа double. Если рассматривать doubleполный почтовый адрес и floatпятизначный почтовый индекс, можно удовлетворить запрос на почтовый индекс с полным адресом, но невозможно точно указать запрос на полный адрес, используя только почтовый индекс , Преобразование адреса улицы в почтовый индекс - операция с потерями, но ...
суперкат
1
... тот, кому нужен полный адрес, обычно не спрашивает просто почтовый индекс. Преобразование из float->doubleэквивалентно преобразованию почтового индекса США 90210 в «Почтовое отделение США, Беверли-Хиллз, Калифорния 90210».
суперкат
181

Да,

в основном, когда мы пишем

i += l; 

компилятор преобразует это в

i = (int)(i + l);

Я только что проверил .classкод файла.

Действительно хорошая вещь, чтобы знать

Умеш Авастхи
источник
3
Можете ли вы сказать мне, что это за файл?
нанофарад
6
@hexafraction: что вы подразумеваете под файлом класса? если вы спрашиваете о файле класса, о котором я упоминал в своем посте, то это версия вашего java-класса с соблюдением требований
Umesh Awasthi
3
О, вы упомянули «код» файла класса, что заставило меня поверить, что был задействован конкретный файл класса. Теперь я понимаю, что вы имеете в виду.
нанофарад
@ Богдан Это не должно быть проблемой с правильно используемыми шрифтами. Программист, который выбирает неправильный шрифт для программирования, должен четко подумать, как поступить ...
glglgl
7
@glglgl Я не согласен с тем, что в этих случаях нужно полагаться на шрифт, но каждый может выбирать, что лучше.
Богдан Александру
92

вам нужно привести из longк int explicitlyв случае, если i = i + l он скомпилируется и даст правильный вывод. подобно

i = i + (int)l;

или

i = (int)((long)i + l); // this is what happens in case of += , dont need (long) casting since upper casting is done implicitly.

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

dku.rajkumar
источник
7
В этом случае «неявное приведение» может привести к потерям. В действительности, как @LukasEder государства в своем ответе, отлитый на intвыполняются после+ . Компилятор будет (должен?) Выдавать предупреждение, если он действительно приведёт longк int.
Ромен
63

Проблема здесь заключается в приведении типов.

Когда вы добавляете int и long,

  1. Объект int приводится к long, и оба добавляются, и вы получаете long объект.
  2. но длинный объект не может быть неявно приведен к int. Итак, вы должны сделать это явно.

Но +=закодирован таким образом, что он выполняет приведение типов.i=(int)(i+m)

Динеш Сачдев 108
источник
54

В Java преобразования типов выполняются автоматически, когда тип выражения в правой части операции присваивания можно безопасно преобразовать в тип переменной в левой части присваивания. Таким образом мы можем смело назначать:

 byte -> short -> int -> long -> float -> double. 

То же самое не будет работать наоборот. Например, мы не можем автоматически преобразовать long в int, потому что первое требует больше памяти, чем второе, и, следовательно, информация может быть потеряна. Чтобы вызвать такое преобразование, мы должны выполнить явное преобразование.
Тип - Конверсия

tinker_fairy
источник
2
Эй, но longв 2 раза больше, чем float.
Отображаемое имя
11
Не floatможет содержать все возможные intзначения, а doubleне может содержать все возможные longзначения.
Алекс MDC
2
Что вы подразумеваете под "благополучно преобразованным"? Из последней части ответа я могу сделать вывод, что вы имели в виду автоматическое преобразование (неявное приведение), что, конечно, неверно в случае с плавающей точкой -> long. float pi = 3,14f; длинный б = пи; приведет к ошибке компилятора.
Люк
1
Было бы лучше различать примитивные типы с плавающей точкой с целочисленными примитивными типами. Они не одно и то же.
ThePyroEagle
Java имеет упрощенные правила преобразования, которые требуют использования приведений во многих шаблонах, где поведение без приведений в противном случае соответствовало бы ожиданиям, но не требует приведений во многих шаблонах, которые обычно ошибочны. Например, компилятор примет double d=33333333+1.0f;без жалоб, даже если результат 33333332.0, скорее всего, не будет тем, что предполагалось (кстати, арифметически правильный ответ 33333334.0f будет представлен как либо, так floatи int).
суперкат
46

Иногда такой вопрос можно задать на собеседовании.

Например, когда вы пишете:

int a = 2;
long b = 3;
a = a + b;

нет автоматической типизации. В C ++ не будет ошибок при компиляции приведенного выше кода, но в Java вы получите что-то подобное Incompatible type exception.

Поэтому, чтобы избежать этого, вы должны написать свой код следующим образом:

int a = 2;
long b = 3;
a += b;// No compilation error or any exception due to the auto typecasting
Stopfan
источник
6
Спасибо за понимание относительно сравнения opиспользования в C ++ с его использованием в Java. Мне всегда нравится видеть эти мелочи, и я думаю, что они вносят свой вклад в разговор, который часто может быть опущен.
Томас
2
Однако сам вопрос является интересным, спрашивая это в интервью глупо. Это не доказывает, что человек может создать код хорошего качества - это просто доказывает, что у него было достаточно терпения, чтобы подготовиться к экзамену на получение сертификата Oracle. И «избегание» несовместимых типов с помощью опасного автоконвертации и, таким образом, сокрытие возможной ошибки переполнения, вероятно, даже доказывает, что человек не способен произвести, вероятно, код хорошего качества. Будь прокляты авторы Java для всех этих автоконверсий и авто-бокса и все!
Хонза Зидек
25

Основное различие заключается в том a = a + b, что при не происходит типизирование, и поэтому компилятор злится на вас за отсутствие типизации. Но с тем a += b, что он действительно делает, является приведением типов bк типу, совместимому с a. Так что если вы делаете

int a=5;
long b=10;
a+=b;
System.out.println(a);

Что вы на самом деле делаете:

int a=5;
long b=10;
a=a+(int)b;
System.out.println(a);
takra
источник
5
Сложные операторы присваивания выполняют сужающее преобразование результата бинарной операции, а не правого операнда. Таким образом, в вашем примере «a + = b» не эквивалентно «a = a + (int) b», а, как объясняется другими ответами здесь, «a = (int) (a + b)».
Лью Блох
13

Тонкий момент здесь ...

Существует неявное приведение типов для аргумента, i+jкогда jзначение double и iint. Java ВСЕГДА преобразует целое число в двойное, когда между ними есть операция.

Чтобы уточнить, i+=jгде iнаходится целое число и jдвойное, можно описать как

i = <int>(<double>i + j)

Смотрите: это описание неявного приведения

Вы можете типаж , jчтобы (int)в этом случае для ясности.

Гейб Нонс
источник
1
Я думаю, что более интересный случай может быть int someInt = 16777217; float someFloat = 0.0f; someInt += someFloat;. Добавление к нулю someIntне должно влиять на его стоимость, но продвижение someIntк floatможет изменить его значение.
суперкат
5

Спецификация языка Java определяет, E1 op= E2что она эквивалентна типу E1 = (T) ((E1) op (E2))where и оценивается один разTE1E1 .

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

public class PlusEquals {
    public static void main(String[] args) {
        byte a = 1;
        byte b = 2;
        a = a + b;
        System.out.println(a);
    }
}

Что печатает эта программа?

Вы догадались 3? Жаль, что эта программа не скомпилируется. Почему? Что ж, бывает так, что добавление байтов в Java определено как возвращающееint . Я полагаю, это произошло потому, что виртуальная машина Java не определяет байтовые операции для сохранения в байтовых кодах (в конце концов, их число ограничено), а использование целочисленных операций - это деталь реализации, представленная на языке.

Но если a = a + bне работает, это означало a += bбы, что никогда не будет работать для байтов, если это E1 += E2было определено как E1 = E1 + E2. Как показывает предыдущий пример, это действительно так. В качестве хака, заставляющего +=оператора работать для байтов и шортов, подразумевается неявное приведение. Это не так здорово, но во время работы над Java 1.0 основное внимание уделялось выпуску языка с самого начала. Теперь, из-за обратной совместимости, этот хак, введенный в Java 1.0, не может быть удален.

Конрад Боровски
источник