Чому 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 дозволяє це, будучи суворішою мовою, ніж її попередники. Помилки при кастингу можуть призвести до критичного збою, як це було у випадку з Flight 501 Ariane5, коли 64-розрядний поплавчик, переданий 16-бітовому цілому, призвів до аварії.
SQLDiver

103
У системі управління польотом, написаною на Яві, це було б найменше ваших турбот @SQLDiver
Ross Drew

10
Насправді i+=(long)j;навіть складатимуть штрафи.
Tharindu Sathischandra

6
Постійний поштовх одного набору розробників для точності та іншого для зручності використання - це справді цікаво. Нам майже потрібні дві версії мови, одна, яка дивно точна і та проста у використанні. Натискання Java з обох напрямків переміщує її в бік непридатності для будь-якої групи.
Білл К

5
якби це вимагало кастингу, куди б ви його помістили? i += (int) f;кидає f перед додаванням, тому це не рівнозначно. (int) i += f;підраховує результат після призначення, не рівнозначний. не можна було б ставити акторський склад, який би означав, що ви хочете передати значення після додавання, але перед призначенням.
Norill Tempest

Відповіді:


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))так це схоже на неявне введення тексту (вниз від довгого до цілого). Тоді як в i = i + j ми повинні робити це явно, тобто надати (T)частину в E1 = ((E1) op (E2))ні?
bad_keypoints

11
Ймовірною причиною того, чому компілятор Java додає typecast, полягає в тому, що якщо ви намагаєтеся виконувати арифметику на несумісних типах, немає можливості виконати typecast результату, використовуючи контрактну форму. Введення тексту результату, як правило, більш точне, ніж набір проблемного аргументу. Жоден шрифт не зробить скорочення марним при використанні несумісних типів, оскільки це завжди призведе до того, що компілятор викине помилку.
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 -> Зрозумів?
Sajal Dutta

79
Так, але я просто бачу, як хтось із початківців приходить сюди, читаючи це, і відходить, думаючи, що ви можете перетворити будь-який символ з верхнього регістру в нижній регістр, помноживши його на 1,5.
Давуд ібн Карім

103
@DavidWallace Будь-який персонаж, поки він є A;)
Пітер Лорі

14
@PeterLawrey & @DavidWallace Я розкрию ваш секрет - ch += 32 = D
Minhas Kamal

256

Дуже гарне запитання. Специфікація мови Java підтверджує вашу пропозицію.

Наприклад, правильний наступний код:

short x = 3;
x += 4.6;

і призводить до того, що x має значення 7, оскільки воно еквівалентно:

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

15
Або веселіше: "int x = 33333333; x + = 1.0f;".
supercat

5
@supercat, що це за хитрість? Зростаюча конверсія, яка неправильно округлюється, а потім додається, що насправді не змінює результат, кидаючи до int знову, щоб отримати результат, який є найбільш несподіваним для нормальної людської свідомості.
neXus

1
@neXus: IMHO, правила перетворення повинні розглядатися double->floatяк розширення, виходячи з того, що значення типу floatідентифікують реальні числа менш конкретно, ніж типи типу double. Якщо хтось розглядає doubleяк повну поштову адресу і floatяк 5-значний поштовий індекс, можна задовольнити запит на поштовий індекс, який отримав повну адресу, але неможливо точно вказати запит на повну адресу, вказану лише поштовим індексом . Перетворення адреси вулиці на поштовий індекс - це збиткова операція, але ...
supercat

1
... хтось, кому потрібна повна адреса, зазвичай не просить просто поштовий індекс. Перетворення з float->doubleеквівалентно перетворенню американського поштового індексу 90210 у "Поштове відділення США, Беверлі-Хіллз, CA 90210".
supercat

181

так,

в основному, коли ми пишемо

i += l; 

компілятор перетворює це в

i = (int)(i + l);

Я щойно перевірив .classкод файлу.

Дійсно добре знати


3
Чи можете ви сказати мені, що це клас клас?
нанофарад

6
@hexafraction: що ви розумієте під файлом класу? якщо ви запитуєте про файл класу, про який я згадував у своєму дописі, ніж це відповідна версія вашого класу java
Umesh Awasthi

3
О, ви згадали про код файлу класу "", який змусив мене повірити, що конкретний клас файлу задіяний. Я розумію, що ти маєш на увазі зараз.
nanofarad

@Bogdan Це не повинно бути проблемою з правильно використаними шрифтами. Програміст, який обирає неправильний шрифт для програмування, повинен чітко продумати, як діяти ...
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.

але у випадку +=це просто працює нормально, оскільки оператор неявно робить тип кастингу від типу правої змінної до типу лівої змінної, тому не потрібно викладати явно.


7
У цьому випадку "неявна роль" може бути втратою. Насправді, як @LukasEder держави в своїй відповіді, відлитий на intвиконуються після+ . Компілятор (повинен?) Кинути попередження , якщо це дійсно кастовать longдо int.
Ромен

63

Проблема тут стосується лиття типів.

Коли ви додаєте int і long,

  1. Об'єкт int передається на довгий, і обидва додаються, і ви отримуєте довгий об'єкт.
  2. але довгий об'єкт не може бути неявно закинутий на int. Отже, ви повинні це робити явно.

Але +=кодується таким чином, що він робить кастинг типу.i=(int)(i+m)


54

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

 байт -> короткий -> int -> long -> float -> double. 

Це ж не буде працювати навпаки. Наприклад, ми не можемо автоматично перетворити довгі в int, оскільки для першого потрібне більше місця, ніж друге, і, отже, інформація може бути втрачена. Щоб примусити таке перетворення, ми повинні здійснити явне перетворення.
Тип - конверсія


2
Гей, але longв 2 рази більше, ніж float.
Відображати ім'я

11
Не floatможе містити всі можливі intзначення, і doubleне може містити всі можливі longзначення.
Alex MDC

2
Що ви маєте на увазі під "безпечно перетвореним"? З останньої частини відповіді я можу зробити висновок про те, що ви мали на увазі автоматичне перетворення (неявний каст), що, звичайно, не вірно у випадку float -> long. float pi = 3,14f; довгий b = pi; призведе до помилки компілятора.
Лука

1
Вам буде краще диференціювати примітивні типи з плаваючою комою з цілими примітивними типами. Вони не те саме.
ThePyroEagle

У Java є спрощені правила перетворення, які вимагають використання роликів у багатьох моделях, коли поведінка без закидів інакше відповідатиме очікуванням, але не вимагає відступів у багатьох моделях, які зазвичай є помилковими. Наприклад, компілятор прийме double d=33333333+1.0f;без нарікань, навіть якщо результат 33333332.0, швидше за все, не буде таким, як було призначено (до речі, арифметично правильна відповідь 33333334.0f була б представленою як floatабо int).
supercat

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

6
Дякуємо за розуміння щодо порівняння opвикористання в C ++ та його використання на Java. Мені завжди подобається бачити ці шматочки дрібниць, і я думаю, що вони щось сприяють розмові, яка може часто залишатися поза увагою.
Томас

2
Однак саме питання є цікавим, запитуючи це в інтерв'ю нерозумно. Це не доводить, що людина може створити код хорошої якості - це просто доводить, що він мав достатньо терпіння, щоб підготуватися до іспиту на сертифікат Oracle. І "уникнення" несумісних типів за допомогою небезпечного автоматичного перетворення і, таким чином, приховування можливої ​​помилки переповнення, ймовірно, навіть доводить, що людина не в змозі створити ймовірний код хорошої якості. Прокляті автори Java за всі ці автоматичні перетворення та автоматичні бокси та все!
Honza Zidek

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);

5
Оператори присвоєння складових виконують звуження перетворення результату бінарної операції, а не правого операнда. Отже, у вашому прикладі "a + = b" не є еквівалентом "a = a + (int) b", але, як пояснено іншими відповідями тут, "a = (int) (a + b)".
Lew Bloch

13

Тут тонка точка ...

Існує неявна typecast для того, i+jколи jє подвійним і iє int. ЯВИ ЗАВЖДИ перетворює ціле число в подвійне, коли між ними відбувається операція.

Для уточнення, i+=jде iє ціле число і jє подвійним, можна описати як

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

Подивитися: цей опис неявного кастингу

Ви можете jввести (int)в цьому випадку для ясності.


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

5

Специфікація мови Java визначає E1 op= E2, що еквівалентно , E1 = (T) ((E1) op (E2))де Tце тип E1і E1обчислюється один раз .

Це технічна відповідь, але вам може бути цікаво, чому це так. Що ж, давайте розглянемо наступну програму.

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, не вдалося видалити.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.