Warum erfordern Javas + =, - =, * =, / = zusammengesetzte Zuweisungsoperatoren kein Casting?

3633

Bis heute dachte ich zum Beispiel:

i += j;

War nur eine Abkürzung für:

i = i + j;

Aber wenn wir das versuchen:

int i = 5;
long j = 8;

Dann i = i + j;wird nicht kompiliert, aber gut i += j;kompiliert.

Bedeutet das, dass es sich tatsächlich i += j;um eine Abkürzung für so etwas handelt i = (type of i) (i + j)?

Honza Brabec
quelle
135
Ich bin überrascht, dass Java dies zulässt, da es eine strengere Sprache als seine Vorgänger ist. Fehler beim Casting können zu kritischen Fehlern führen, wie dies bei Ariane5 Flight 501 der Fall war, bei dem ein 64-Bit-Float-Cast auf eine 16-Bit-Ganzzahl zum Absturz führte.
SQLDiver
103
In einem in Java geschriebenen Flugsteuerungssystem wäre dies die geringste Sorge für Sie @SQLDiver
Ross Drew
10
Eigentlich wird i+=(long)j;sogar gut kompiliert.
Tharindu Sathischandra
6
Der ständige Drang einer Gruppe von Entwicklern nach Genauigkeit und einer anderen nach Benutzerfreundlichkeit ist wirklich interessant. Wir brauchen fast zwei Versionen der Sprache, eine erstaunlich präzise und eine, die einfach zu bedienen ist. Wenn Sie Java aus beiden Richtungen drücken, ist es für beide Gruppen ungeeignet.
Bill K
5
Wenn es ein Casting erfordern würde, wo würdest du es hinstellen? i += (int) f;Wirkt f vor dem Hinzufügen, daher ist es nicht gleichwertig. (int) i += f;Wirkt das Ergebnis nach der Zuweisung, auch nicht gleichwertig. Es gibt keinen Platz für eine Besetzung, die anzeigt, dass Sie den Wert nach dem Hinzufügen, aber vor der Zuweisung umwandeln möchten.
Norill Tempest

Antworten:

2441

Wie immer bei diesen Fragen hat das JLS die Antwort. In diesem Fall §15.26.2 Compound Assignment Operators . Ein Auszug:

Eine Verbindung Zuweisungsausdruck von der Form E1 op= E2entspricht E1 = (T)((E1) op (E2)), in dem Tdie Art der E1Ausnahme , dass E1nur einmal ausgewertet wird.

Ein Beispiel aus §15.26.2

[...] der folgende Code ist korrekt:

short x = 3;
x += 4.6;

und führt dazu, dass x den Wert 7 hat, weil es äquivalent ist zu:

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

Mit anderen Worten, Ihre Annahme ist richtig.

Lukas Eder
quelle
42
So i+=jcompiliert , wie ich mich selbst überprüft, aber es würde zu einem Verlust an Präzision rechts führen? Wenn dies der Fall ist, warum kann es dann nicht auch in i = i + j passieren? Warum uns dort nerven?
bad_keypoints
46
@ronnieaka: Ich vermute, dass die Sprachdesigner der Meinung waren, dass es in einem Fall ( i += j) sicherer ist anzunehmen, dass der Genauigkeitsverlust erwünscht ist, im Gegensatz zum anderen Fall ( i = i + j)
Lukas Eder
12
Nein, es ist genau dort, vor mir! Entschuldigung, ich habe es nicht früher bemerkt. Wie in Ihrer Antwort ist das E1 op= E2 is equivalent to E1 = (T)((E1) op (E2))so etwas wie implizites Down-Typecasting (von Long nach Int). Während in i = i + j wir es explizit tun müssen, dh den (T)Teil in E1 = ((E1) op (E2))Ist es nicht?
bad_keypoints
11
Ein wahrscheinlicher Grund dafür, dass der Java-Compiler einen Typecast hinzufügt, ist, dass es keine Möglichkeit gibt, einen Typecast des Ergebnisses mithilfe des Vertragsformulars durchzuführen, wenn Sie versuchen, Arithmetik für inkompatible Typen durchzuführen. Eine Typumwandlung des Ergebnisses ist im Allgemeinen genauer als eine Typumwandlung des problematischen Arguments. Kein Typecast würde die Kontraktion unbrauchbar machen, wenn inkompatible Typen verwendet werden, da dies immer dazu führen würde, dass der Compiler einen Fehler auslöst.
ThePyroEagle
6
Es ist nicht gerundet. Es ist besetzt (= abgeschnitten)
Lukas Eder
483

Ein gutes Beispiel für dieses Casting ist die Verwendung von * = oder / =

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

oder

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

oder

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

oder

char ch = 'A';
ch *= 1.5;
System.out.println(ch); // prints 'a'
Peter Lawrey
quelle
11
@AkshatAgarwal ch ist ein Zeichen. 65 * 1,5 = 97,5 -> Verstanden?
Sajal Dutta
79
Ja, aber ich kann nur einige Anfänger sehen, die hierher kommen, dies lesen und weggehen und denken, dass Sie jedes Zeichen von Groß- in Kleinbuchstaben umwandeln können, indem Sie es mit 1,5 multiplizieren.
Dawood ibn Kareem
103
@ DavidWallace Jeder Charakter, solange er ist A;)
Peter Lawrey
14
@ PeterLawrey & @ DavidWallace Ich werde Ihr Geheimnis enthüllen - ch += 32 = D
Minhas Kamal
256

Sehr gute Frage. Die Java-Sprachspezifikation bestätigt Ihren Vorschlag.

Der folgende Code ist beispielsweise korrekt:

short x = 3;
x += 4.6;

und führt dazu, dass x den Wert 7 hat, weil es äquivalent ist zu:

short x = 3;
x = (short)(x + 4.6);
Thirler
quelle
15
Oder mehr Spaß: "int x = 33333333; x + = 1.0f;".
Supercat
5
@supercat, was für ein Trick ist das? Eine erweiterte Konvertierung, die falsch gerundet ist, gefolgt von einer Addition, die das Ergebnis nicht wirklich ändert, und erneut in int umgewandelt wird, um ein Ergebnis zu erzielen, das für normale menschliche Köpfe am unerwartetsten ist.
neXus
1
@neXus: IMHO sollten die Konvertierungsregeln double->floatals Erweiterung behandelt werden, auf der Grundlage, dass Werte vom Typ floatreelle Zahlen weniger spezifisch identifizieren als solche vom Typ double. Wenn man doubleals vollständige Postanschrift und floatals 5-stellige Postleitzahl betrachtet, ist es möglich, eine Anfrage nach einer Postleitzahl mit einer vollständigen Adresse zu erfüllen, aber es ist nicht möglich, eine Anfrage nach einer vollständigen Adresse mit nur einer Postleitzahl genau anzugeben . Das Umwandeln einer Adresse in eine Postleitzahl ist eine verlustbehaftete Operation, aber ...
Supercat
1
... jemand, der eine vollständige Adresse benötigt, würde im Allgemeinen nicht nur nach einer Postleitzahl fragen. Die Konvertierung von float->doubleentspricht der Konvertierung der US-Postleitzahl 90210 in "US Post Office, Beverly Hills CA 90210".
Supercat
181

Ja,

im Grunde, wenn wir schreiben

i += l; 

Der Compiler konvertiert dies in

i = (int)(i + l);

Ich habe gerade den .classDateicode überprüft .

Wirklich gut zu wissen

Umesh Awasthi
quelle
3
Kannst du mir sagen, um welche Klassendatei es sich handelt?
Nanofarad
6
@hexafraction: Was meinst du mit Klassendatei? Wenn Sie nach der Klassendatei fragen, die ich in meinem Beitrag erwähnt habe, dann ist es die
konforme
3
Oh, Sie haben "den" Klassendateicode erwähnt, was mich zu der Annahme veranlasste, dass eine bestimmte Klassendatei beteiligt war. Ich verstehe, was du jetzt meinst.
Nanofarad
@ Bogdan Das sollte kein Problem mit richtig verwendeten Schriftarten sein. Ein Programmierer, der die falsche Schriftart für die Programmierung auswählt, sollte sich klar
überlegen,
7
@glglgl Ich bin nicht der Meinung, dass man sich in diesen Fällen auf die Schrift verlassen sollte, um zu unterscheiden ... aber jeder hat die Freiheit zu wählen, was er für das Beste hält.
Bogdan Alexandru
92

Sie müssen von bis umwandeln long, int explicitlyfalls dies i = i + l dann kompiliert und die korrekte Ausgabe liefert. mögen

i = i + (int)l;

oder

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

In diesem Fall +=funktioniert es jedoch einwandfrei, da der Operator implizit die Typumwandlung vom Typ der rechten Variablen zum Typ der linken Variablen durchführt und daher nicht explizit umgewandelt werden muss.

dku.rajkumar
quelle
7
In diesem Fall könnte die "implizite Besetzung" verlustbehaftet sein. In Wirklichkeit als @LukasEder Staaten in seiner Antwort, die Besetzung zu intdurchgeführt wird, nachdem die +. Der Compiler würde (sollte?) Wirft eine Warnung , wenn es wirklich hergegeben das longzu int.
Romain
63

Das Problem hierbei ist das Typgießen.

Wenn Sie int und long hinzufügen,

  1. Das int-Objekt wird in long umgewandelt und beide werden hinzugefügt, und Sie erhalten ein long-Objekt.
  2. Ein langes Objekt kann jedoch nicht implizit in int umgewandelt werden. Das muss man also explizit machen.

Ist +=aber so codiert, dass es Typ Casting macht.i=(int)(i+m)

Dinesh Sachdev 108
quelle
54

In Java werden Typkonvertierungen automatisch durchgeführt, wenn der Typ des Ausdrucks auf der rechten Seite einer Zuweisungsoperation sicher zum Typ der Variablen auf der linken Seite der Zuweisung heraufgestuft werden kann. So können wir sicher zuordnen:

 Byte -> kurz -> int -> lang -> float -> doppelt. 

Das gleiche funktioniert nicht umgekehrt. Zum Beispiel können wir ein Long nicht automatisch in ein Int konvertieren, da das erste mehr Speicher benötigt als das zweite und folglich Informationen verloren gehen können. Um eine solche Konvertierung zu erzwingen, müssen wir eine explizite Konvertierung durchführen.
Typ - Konvertierung

tinker_fairy
quelle
2
Hey, ist aber long2 mal größer als float.
Anzeigename
11
A floatkann nicht jeden möglichen intWert enthalten, und a doublekann nicht jeden möglichen longWert enthalten.
Alex MDC
2
Was meinst du mit "sicher konvertiert"? Aus dem letzten Teil der Antwort kann ich schließen, dass Sie eine automatische Konvertierung (implizite Umwandlung) gemeint haben, was im Fall von float -> long natürlich nicht zutrifft. float pi = 3,14f; lang b = pi; führt zu einem Compilerfehler.
Luke
1
Es ist besser, Gleitkomma-Primitivtypen von ganzzahligen Primitivtypen zu unterscheiden. Sie sind nicht dasselbe.
ThePyroEagle
Java verfügt über vereinfachte Konvertierungsregeln, die die Verwendung von Casts in vielen Mustern erfordern, wobei das Verhalten ohne Casts ansonsten den Erwartungen entsprechen würde, jedoch keine Casts in vielen Mustern erfordert, die normalerweise fehlerhaft sind. Zum Beispiel akzeptiert ein Compiler double d=33333333+1.0f;ohne Beanstandung, obwohl das Ergebnis 33333332.0 wahrscheinlich nicht das ist, was beabsichtigt war (im Übrigen wäre die arithmetisch korrekte Antwort von 33333334.0f entweder floatoder darstellbar int).
Supercat
46

Manchmal kann eine solche Frage bei einem Interview gestellt werden.

Zum Beispiel, wenn Sie schreiben:

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

Es gibt keine automatische Typumwandlung. In C ++ wird es keinen Fehler beim Kompilieren des obigen Codes geben, aber in Java erhalten Sie so etwas wie Incompatible type exception.

Um dies zu vermeiden, müssen Sie Ihren Code folgendermaßen schreiben:

int a = 2;
long b = 3;
a += b;// No compilation error or any exception due to the auto typecasting
Stopfan
quelle
6
Vielen Dank für den Einblick in den Vergleich der opVerwendung in C ++ mit der Verwendung in Java. Ich mag es immer, diese Kleinigkeiten zu sehen, und ich denke, sie tragen etwas zu dem Gespräch bei, das oft ausgelassen wird.
Thomas
2
Die Frage selbst ist jedoch interessant, dies in einem Interview zu stellen, ist dumm. Es beweist nicht, dass die Person einen Code von guter Qualität erstellen kann - es beweist nur, dass sie genug Geduld hatte, um sich auf eine Oracle-Zertifikatsprüfung vorzubereiten. Und das "Vermeiden" der inkompatiblen Typen durch Verwendung einer gefährlichen automatischen Konvertierung und damit das Ausblenden des möglichen Überlauffehlers beweist wahrscheinlich sogar, dass die Person wahrscheinlich keinen Code von guter Qualität erstellen kann. Verdammt die Java-Autoren für all diese Auto-Conversions und Auto-Boxing und alles!
Honza Zidek
25

Der Hauptunterschied besteht darin, dass bei a = a + bkeine Typumwandlung stattfindet und der Compiler sich über Sie ärgert, weil Sie keine Typumwandlung durchgeführt haben. Aber mit a += b, was es wirklich tut, ist Typografie bauf einen Typ, der mit kompatibel ist a. Also wenn du es tust

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

Was Sie wirklich tun, ist:

int a=5;
long b=10;
a=a+(int)b;
System.out.println(a);
Takra
quelle
5
Zusammengesetzte Zuweisungsoperatoren führen eine Verengungskonvertierung des Ergebnisses der Binäroperation durch, nicht des rechten Operanden. In Ihrem Beispiel entspricht 'a + = b' nicht 'a = a + (int) b', sondern, wie in anderen Antworten hier erläutert, 'a = (int) (a + b)'.
Lew Bloch
13

Subtiler Punkt hier ...

Es gibt eine implizite Typumwandlung für i+jwhen jist ein Double und iist ein int. Java konvertiert IMMER eine Ganzzahl in ein Double, wenn zwischen ihnen eine Operation besteht.

Um zu klären, i+=jwo ieine ganze Zahl und jein Doppel ist, kann beschrieben werden als

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

Siehe: diese Beschreibung des impliziten Castings

Sie könnten typisieren wollen , jum (int)für Klarheit in diesem Fall.

Gabe Nones
quelle
1
Ich denke, ein interessanterer Fall könnte sein int someInt = 16777217; float someFloat = 0.0f; someInt += someFloat;. Das Hinzufügen von Null zu someIntsollte den Wert nicht beeinflussen, aber das Heraufstufen someIntzu floatkann den Wert ändern.
Supercat
5

Java Language Specification definiert E1 op= E2äquivalent zu sein , E1 = (T) ((E1) op (E2))wo Tist eine Art E1und E1wird einmal ausgewertet .

Das ist eine technische Antwort, aber Sie fragen sich vielleicht, warum das so ist. Betrachten wir das folgende Programm.

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

Was druckt dieses Programm?

Hast du 3 erraten? Schade, dieses Programm wird nicht kompiliert. Warum? Nun, es kommt vor, dass das Hinzufügen von Bytes in Java definiert wird, um ein zurückzugebenint . Ich glaube, dies lag daran, dass die Java Virtual Machine keine Byte-Operationen zum Speichern von Bytecodes definiert (es gibt schließlich nur eine begrenzte Anzahl davon). Die Verwendung von Integer-Operationen ist stattdessen ein Implementierungsdetail, das in einer Sprache verfügbar gemacht wird.

Aber wenn a = a + bes nicht funktioniert, würde das bedeuten, dass es a += bniemals für Bytes funktionieren würde, wenn es E1 += E2so definiert wäre E1 = E1 + E2. Wie das vorige Beispiel zeigt, wäre dies tatsächlich der Fall. Als Hack, um den +=Operator für Bytes und Shorts arbeiten zu lassen, ist eine implizite Umwandlung beteiligt. Es ist kein so großer Hack, aber während der Arbeit mit Java 1.0 lag der Schwerpunkt darauf, die Sprache zunächst freizugeben. Aufgrund der Abwärtskompatibilität konnte dieser in Java 1.0 eingeführte Hack nicht entfernt werden.

Konrad Borowski
quelle