Dlaczego operatory przypisania złożonego Java + =, - =, * =, / = nie wymagają rzutowania?

3633

Do dzisiaj myślałem, że na przykład:

i += j;

Był tylko skrótem do:

i = i + j;

Ale jeśli spróbujemy:

int i = 5;
long j = 8;

Wtedy i = i + j;nie będzie kompilować, ale i += j;skompiluje dobrze.

Czy to oznacza, że ​​tak naprawdę i += j;jest skrót do czegoś takiego i = (type of i) (i + j)?

Honza Brabec
źródło
135
Dziwi mnie, że Java na to pozwala, ponieważ jest surowszym językiem niż jego poprzednicy. Błędy w rzutowaniu mogą prowadzić do krytycznej awarii, jak miało to miejsce w przypadku Ariane5 Flight 501, gdzie 64-bitowa liczba zmiennoprzecinkowa rzutowana na 16-bitową liczbę całkowitą spowodowała awarię.
SQLDiver
103
W systemie kontroli lotów napisanym w Javie byłoby to najmniejsze z twoich zmartwień @SQLDiver
Ross Drew
10
Właściwie i+=(long)j;nawet skompiluje się dobrze.
Tharindu Sathischandra
6
Ciągłe naciskanie jednego zestawu programistów na dokładność, a drugiego na łatwość użycia jest naprawdę interesujące. Potrzebujemy prawie dwóch wersji języka, jednej niezwykle precyzyjnej i łatwej w użyciu. Przesunięcie Javy z obu kierunków powoduje, że nie nadaje się ona dla żadnej grupy.
Bill K
5
gdyby to wymagało odlewania, gdzie byś to położył? i += (int) f;rzuca f przed dodaniem, więc nie jest równoważne. (int) i += f;rzutuje wynik po przypisaniu, również nie równoważny. nie byłoby miejsca na rzutowanie, które oznaczałoby, że chcesz rzutować wartość po dodaniu, ale przed przypisaniem.
Norill Tempest

Odpowiedzi:

2441

Jak zawsze w przypadku tych pytań, JLS posiada odpowiedź. W takim przypadku §15.26.2 Operatorzy przydziału złożonych . Ekstrakt:

Wyrażenie przypisania złożonego formularza E1 op= E2jest równoważne E1 = (T)((E1) op (E2)), gdzie Tjest typem E1, z tym że E1jest oceniane tylko raz.

Przykład cytowany w § 15.26.2

[...] następujący kod jest poprawny:

short x = 3;
x += 4.6;

i powoduje, że x ma wartość 7, ponieważ jest ona równoważna:

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

Innymi słowy, twoje założenie jest prawidłowe.

Lukas Eder
źródło
42
Tak się i+=jkompiluje, jak sam sprawdzałem, ale spowodowałoby to utratę precyzji, prawda? Jeśli tak jest, dlaczego nie pozwala na to również w i = i + j? Po co nas tam wkurzać?
bad_keypoints
46
@ronnieaka: Zgaduję, że projektanci język czuł, że w jednym przypadku ( i += j), to bezpieczniej jest założyć, że pożądane jest utrata precyzji, w przeciwieństwie do drugiej sprawy ( i = i + j)
Lukas Eder
12
Nie, jest tam, przede mną! Przepraszam, nie zauważyłem tego wcześniej. Tak jak w twojej odpowiedzi, E1 op= E2 is equivalent to E1 = (T)((E1) op (E2))więc jest to coś w rodzaju niejawnego rzutowania w dół (z długiego na int). Podczas gdy w i = i + j musimy to zrobić jawnie, tj. Podać (T)część w E1 = ((E1) op (E2))Czy nie?
bad_keypoints
11
Prawdopodobnym powodem, dla którego kompilator Java dodaje rzut typu, jest to, że jeśli próbujesz wykonać arytmetykę na typach niezgodnych, nie ma możliwości wykonania rzutowania typu wyniku za pomocą zakontraktowanego formularza. Typ wyniku jest generalnie dokładniejszy niż typ problematycznego argumentu. Żadna rzutówka nie sprawi, że skurcz będzie bezużyteczny, gdy użyje się niekompatybilnych typów, ponieważ zawsze spowoduje to wyrzucenie przez kompilator błędu.
ThePyroEagle,
6
Nie jest zaokrąglony. Jest obsadzony (= obcięty)
Lukas Eder
483

Dobrym przykładem tego rzutowania jest użycie * = lub / =

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

lub

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

lub

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

lub

char ch = 'A';
ch *= 1.5;
System.out.println(ch); // prints 'a'
Peter Lawrey
źródło
11
@AkshatAgarwal ch jest char. 65 * 1,5 = 97,5 -> Rozumiesz?
Sajal Dutta
79
Tak, ale widzę tylko początkujących, którzy przychodzą tutaj, czytają to i odchodzą, myśląc, że możesz przekształcić dowolną postać z wielkich na małe, mnożąc ją przez 1,5.
Dawood ibn Kareem
103
@DavidWallace Każda postać, o ile jest A;)
Peter Lawrey
14
@PeterLawrey & @DavidWallace Ujawnię twój sekret - ch += 32 = D
Minhas Kamal
256

Bardzo dobre pytanie. Specyfikacja języka Java potwierdza Twoją sugestię.

Na przykład następujący kod jest poprawny:

short x = 3;
x += 4.6;

i powoduje, że x ma wartość 7, ponieważ jest ona równoważna:

short x = 3;
x = (short)(x + 4.6);
Thirler
źródło
15
Lub więcej zabawy: „int x = 33333333; x + = 1.0f;”.
supercat
5
@ supercat, co to za podstęp? Poszerzająca się konwersja, która jest niepoprawnie zaokrąglona, ​​po której następuje dodanie, które tak naprawdę nie zmienia wyniku, rzucając ponownie int, aby uzyskać wynik, który jest najbardziej nieoczekiwany dla normalnych ludzkich umysłów.
neXus
1
@neXus: IMHO, reguły konwersji powinny być traktowane double->floatjako rozszerzenie, na podstawie tego, że wartości typu floatidentyfikują liczby rzeczywiste mniej konkretnie niż wartości typu double. Jeśli ktoś widzi doublejako pełny adres pocztowy i floatjako 5-cyfrowy kod pocztowy, możliwe jest spełnienie żądania podania kodu pocztowego podanego pełnego adresu, ale nie jest możliwe dokładne określenie żądania pełnego adresu podanego tylko kodem pocztowym . Konwersja adresu ulicy na kod pocztowy jest operacją
przynoszącą
1
... ktoś, kto potrzebuje pełnego adresu, zazwyczaj nie poprosiłby o kod pocztowy. Konwersja z float->doublejest równoznaczna z konwersją amerykańskiego kodu pocztowego 90210 na „US Post Office, Beverly Hills CA 90210”.
supercat
181

Tak,

w zasadzie kiedy piszemy

i += l; 

kompilator konwertuje to na

i = (int)(i + l);

Właśnie sprawdziłem .classkod pliku.

Naprawdę dobrze wiedzieć

Umesh Awasthi
źródło
3
Czy możesz mi powiedzieć, który to plik klasy?
nanofarad,
6
@hexafraction: co rozumiesz przez plik klasy? jeśli pytasz o plik klasy, o którym wspomniałem w moim poście, to jest to zgodna wersja twojej klasy Java
Umesh Awasthi
3
Wspomniałeś o „kodzie pliku klasowego”, który doprowadził mnie do wniosku, że w grę wchodzi konkretny plik klasy. Rozumiem teraz, co masz na myśli.
nanofarad,
@Bogdan To nie powinno być problemem z prawidłowo używanymi czcionkami. Programista, który wybierze niewłaściwą czcionkę do programowania, powinien wyraźnie pomyśleć o tym, jak postępować ...
glglgl
7
@glglgl Nie zgadzam się, że w tych przypadkach należy polegać na czcionce ... ale każdy ma swobodę wyboru tego, co uważa za najlepsze.
Bogdan Alexandru
92

musisz rzutować od longdo, int explicitlyw przeciwnym razie i = i + l skompiluje się i da poprawny wynik. lubić

i = i + (int)l;

lub

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

ale w przypadku +=tego działa po prostu dobrze, ponieważ operator domyślnie wykonuje rzutowanie typu z typu prawej zmiennej na typ lewej zmiennej, więc nie trzeba jawnie rzutować.

dku.rajkumar
źródło
7
W takim przypadku „niejawna obsada” może być stratna. W rzeczywistości, jak w stanach @LukasEder jego odpowiedź, oddanych do intwykonywana jest po+ . Kompilator rzuciłby (powinien?) Ostrzeżenie, jeśli naprawdę rzuciłoby to longna int.
Romain
63

Problem dotyczy tutaj rzutowania na typy.

Kiedy dodasz int i long,

  1. Obiekt int jest rzutowany na długi i oba są dodawane i otrzymujesz długi obiekt.
  2. ale długi obiekt nie może być niejawnie rzutowany na int. Musisz to zrobić wyraźnie.

Ale +=jest zakodowany w taki sposób, że wykonuje rzutowanie.i=(int)(i+m)

Dinesh Sachdev 108
źródło
54

W Javie konwersje typu są wykonywane automatycznie, gdy typ wyrażenia po prawej stronie operacji przypisania można bezpiecznie awansować do typu zmiennej po lewej stronie przypisania. W ten sposób możemy bezpiecznie przypisać:

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

To samo nie zadziała na odwrót. Na przykład nie możemy automatycznie przekonwertować długiego na int, ponieważ pierwszy wymaga więcej pamięci niż drugi, w wyniku czego informacje mogą zostać utracone. Aby wymusić taką konwersję, musimy przeprowadzić jawną konwersję.
Typ - konwersja

Tinker_fairy
źródło
2
Hej, ale longjest 2 razy większy niż float.
Nazwa wyświetlana
11
Nie floatmoże pomieścić każdej możliwej intwartości i doublenie może pomieścić każdej możliwej longwartości.
Alex MDC
2
Co rozumiesz przez „bezpiecznie przekonwertowany”? Z drugiej części odpowiedzi mogę wywnioskować, że miałeś na myśli automatyczną konwersję (niejawną obsadę), co oczywiście nie jest prawdą w przypadku liczby zmiennoprzecinkowej -> długiej. zmiennoprzecinkowa pi = 3,14f; długie b = pi; spowoduje błąd kompilatora.
Łukasz
1
Lepiej byłoby odróżnić typy pierwotne liczb zmiennoprzecinkowych od typów pierwotnych liczb całkowitych. To nie to samo.
ThePyroEagle,
Java ma uproszczone reguły konwersji, które wymagają użycia rzutowań w wielu wzorach, w których zachowanie bez rzutowań w innym przypadku byłoby zgodne z oczekiwaniami, ale nie wymaga rzutowań w wielu wzorach, które zwykle są błędne. Na przykład, kompilator zaakceptuje double d=33333333+1.0f;bez zarzutu, choć wynik +33333332,0 nie może być to, co miało (nawiasem mówiąc, arytmetycznie-poprawna odpowiedź 33333334.0f byłoby reprezentowalna jako albo floatalbo int).
supercat
46

Czasami takie pytanie można zadać podczas wywiadu.

Na przykład podczas pisania:

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

nie ma automatycznego rzutowania tekstu. W C ++ nie będzie żadnego błędu przy kompilowaniu powyższego kodu, ale w Javie otrzymasz coś takiego Incompatible type exception.

Aby tego uniknąć, musisz napisać swój kod w następujący sposób:

int a = 2;
long b = 3;
a += b;// No compilation error or any exception due to the auto typecasting
Stopfan
źródło
6
Dziękujemy za wgląd w porównanie opużycia w C ++ z użyciem w Javie. Zawsze lubię oglądać te drobiazgi i myślę, że wnoszą coś do rozmowy, co często można pominąć.
Thomas
2
Jednak samo pytanie jest interesujące, zadawanie tego w wywiadzie jest głupie. Nie dowodzi to, że dana osoba jest w stanie wytworzyć kod dobrej jakości - to po prostu dowodzi, że miał wystarczająco dużo cierpliwości, aby przygotować się do egzaminu certyfikacyjnego Oracle. A „unikanie” niekompatybilnych typów przez użycie niebezpiecznej automatycznej konwersji, a tym samym ukrycie możliwego błędu przepełnienia, prawdopodobnie nawet dowodzi, że dana osoba nie jest w stanie wyprodukować prawdopodobnego kodu dobrej jakości. Niech to szlag dla autorów Javy za te wszystkie automatyczne konwersje i auto-boksowanie!
Honza Zidek
25

Główną różnicą jest to, że z a = a + bnie odbywa się rzutowanie typu, a więc kompilator jest zły na ciebie za to, że nie masz rzutowania. Ale z a += btym, co naprawdę robi, to rzutowanie bna typ zgodny z a. Jeśli tak

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

To, co naprawdę robisz, to:

int a=5;
long b=10;
a=a+(int)b;
System.out.println(a);
takra
źródło
5
Operatory przypisania złożonego wykonują zawężającą się konwersję wyniku operacji binarnej, a nie operandu po prawej stronie. Zatem w twoim przykładzie „a + = b” nie jest równoważne z „a = a + (int) b”, ale, jak wyjaśniono w innych odpowiedziach tutaj, do „a = (int) (a + b)”.
Lew Bloch,
13

Subtelny punkt tutaj ...

Istnieje domyślna typografia dla i+jkiedy jjest podwójnym i iint. Java ZAWSZE konwertuje liczbę całkowitą na podwójną, gdy między nimi jest operacja.

Aby wyjaśnić, i+=jgdzie ijest liczba całkowita i liczba jpodwójna, można opisać jako

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

Zobacz: ten opis niejawnego castingu

Może chcesz typecast jaby (int)w tym przypadku dla jasności.

Gabe Nones
źródło
1
Myślę, że może być bardziej interesujący przypadek int someInt = 16777217; float someFloat = 0.0f; someInt += someFloat;. Dodanie zera do someIntnie powinno wpływać na jego wartość, ale promowanie someIntdo floatmoże zmienić jego wartość.
supercat
4

Specyfikacja języka Java określa, E1 op= E2że jest równoważna z tym, E1 = (T) ((E1) op (E2))gdzie Tjest typem, E1i E1jest oceniana raz .

To techniczna odpowiedź, ale możesz się zastanawiać, dlaczego tak jest. Rozważmy następujący program.

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

Co drukuje ten program?

Zgadłeś 3? Szkoda, że ​​ten program się nie skompiluje. Dlaczego? Tak się składa, że ​​dodanie bajtów w Javie jest zdefiniowane w celu zwróceniaint . Wydaje mi się, że było tak dlatego, że wirtualna maszyna Java nie definiuje operacji bajtów, które mają być zapisywane na kodach bajtów (w końcu jest ich ograniczona liczba), użycie operacji na liczbach całkowitych jest szczegółem implementacji ujawnionym w języku.

Ale jeśli a = a + bnie działa, to znaczyłoby, a += bże nigdy by nie działało dla bajtów, gdyby E1 += E2zostało zdefiniowane jako E1 = E1 + E2. Jak pokazuje poprzedni przykład, rzeczywiście tak by było. W celu włamania się +=operatora do bajtów i skrótów zaangażowana jest ukryta obsada. Nie jest to świetny hack, ale już podczas pracy nad Javą 1.0 skupiono się na wydaniu języka na początek. Teraz, ze względu na kompatybilność wsteczną, tego hacka wprowadzonego w Javie 1.0 nie można było usunąć.

Konrad Borowski
źródło