Ich bin auf diese Frage im technischen Test für einen Job gestoßen. Gegeben das folgende Codebeispiel:
public class Manager {
public static void main (String args[]) {
System.out.println((int) (char) (byte) -2);
}
}
Es gibt die Ausgabe als 65534.
Dieses Verhalten wird nur für negative Werte angezeigt. 0 und positive Zahlen ergeben den gleichen Wert, dh den in SOP eingegebenen Wert. Das hier gegossene Byte ist unbedeutend; Ich habe es ohne versucht.
Meine Frage ist also: Was genau ist hier los?
byte
Besetzung das Ergebnis nicht ändert, bedeutet nicht, dass sie nichts tut ...System.out.println((int)(char)(byte)-130)
sehen, ob es "nur" 65536-130 ist. Dann lies @Chris K Antwort und arbeite es aus! :)byte
Besetzung!(byte)
ändert sich tatsächlich das Ergebnis, so dass es eine andere Situation ist.Antworten:
Es gibt einige Voraussetzungen, auf die wir uns einigen müssen, bevor Sie verstehen können, was hier passiert. Mit dem Verständnis der folgenden Aufzählungspunkte ist der Rest ein einfacher Abzug:
Alle primitiven Typen innerhalb der JVM werden als eine Folge von Bits dargestellt. Der
int
Typ wird durch 32 Bit dargestellt, diechar
undshort
Typen durch 16 Bit und derbyte
Typ wird durch 8 Bit dargestellt.Alle JVM-Nummern sind signiert, wobei der
char
Typ die einzige vorzeichenlose "Nummer" ist. Wenn eine Nummer signiert ist, wird das höchste Bit verwendet, um das Vorzeichen dieser Nummer darzustellen. Stellt für dieses höchste Bit0
eine nicht negative Zahl (positiv oder null) und1
eine negative Zahl dar. Außerdem wird bei vorzeichenbehafteten Zahlen ein negativer Wert (technisch als Zweierkomplementnotation bezeichnet ) in die Inkrementierungsreihenfolge positiver Zahlen invertiert . Beispielsweise wird ein positiver Wert in Bits wie folgt dargestellt:byte
00 00 00 00 => (byte) 0 00 00 00 01 => (byte) 1 00 00 00 10 => (byte) 2 ... 01 11 11 11 => (byte) Byte.MAX_VALUE
während die Bitreihenfolge für negative Zahlen invertiert ist:
11 11 11 11 => (byte) -1 11 11 11 10 => (byte) -2 11 11 11 01 => (byte) -3 ... 10 00 00 00 => (byte) Byte.MIN_VALUE
Diese invertierte Notation erklärt auch, warum der negative Bereich eine zusätzliche Zahl im Vergleich zu dem positiven Bereich enthalten kann, in dem letzterer die Darstellung der Zahl enthält
0
. Denken Sie daran, all dies ist nur eine Frage der Interpretation eines Bitmusters. Sie können negative Zahlen unterschiedlich notieren, aber diese invertierte Notation für negative Zahlen ist sehr praktisch, da sie einige ziemlich schnelle Transformationen ermöglicht, wie wir später in einem kleinen Beispiel sehen werden.Wie bereits erwähnt, gilt dies nicht für den
char
Typ. Derchar
Typ repräsentiert ein Unicode-Zeichen mit einem nicht negativen "numerischen Bereich" von0
bis65535
. Jede dieser Zahlen bezieht sich auf einen 16-Bit-Unicode- Wert.Wenn zwischen den Umwandlung
int
,byte
,short
,char
undboolean
Typen muss die JVM , um entweder mehr oder truncate Bits.Wenn der Zieltyp durch mehr Bits als der Typ dargestellt wird, von dem er konvertiert wird, füllt die JVM die zusätzlichen Slots einfach mit dem Wert des höchsten Bits des angegebenen Werts (der die Signatur darstellt):
| short | byte | | | 00 00 00 01 | => (byte) 1 | 00 00 00 00 | 00 00 00 01 | => (short) 1
Dank der invertierten Notation funktioniert diese Strategie auch für negative Zahlen:
| short | byte | | | 11 11 11 11 | => (byte) -1 | 11 11 11 11 | 11 11 11 11 | => (short) -1
Auf diese Weise bleibt das Vorzeichen des Wertes erhalten. Beachten Sie, dass dieses Modell die Ausführung eines Gusses durch einen billigen Schichtvorgang ermöglicht, was offensichtlich vorteilhaft ist, ohne auf Einzelheiten der Implementierung für eine JVM einzugehen.
Eine Ausnahme von dieser Regel ist die Erweiterung eines
char
Typs, der, wie bereits erwähnt, nicht signiert ist. Eine Konvertierung von achar
wird immer angewendet, indem die zusätzlichen Bits mit gefüllt werden,0
da wir gesagt haben, dass es kein Vorzeichen gibt und daher keine invertierte Notation erforderlich ist. Eine Umwandlung von achar
in anint
wird daher durchgeführt als:| int | char | byte | | | 11 11 11 11 | 11 11 11 11 | => (char) \uFFFF | 00 00 00 00 | 00 00 00 00 | 11 11 11 11 | 11 11 11 11 | => (int) 65535
Wenn der ursprüngliche Typ mehr Bits als der Zieltyp hat, werden die zusätzlichen Bits lediglich abgeschnitten. Solange der ursprüngliche Wert in den Zielwert gepasst hätte, funktioniert dies einwandfrei, beispielsweise für die folgende Konvertierung von a
short
in abyte
:| short | byte | | 00 00 00 00 | 00 00 00 01 | => (short) 1 | | 00 00 00 01 | => (byte) 1 | 11 11 11 11 | 11 11 11 11 | => (short) -1 | | 11 11 11 11 | => (byte) -1
Wenn der Wert jedoch zu groß oder zu klein ist , funktioniert dies nicht mehr:
| short | byte | | 00 00 00 01 | 00 00 00 01 | => (short) 257 | | 00 00 00 01 | => (byte) 1 | 11 11 11 11 | 00 00 00 00 | => (short) -32512 | | 00 00 00 00 | => (byte) 0
Aus diesem Grund führen verengte Gussteile manchmal zu seltsamen Ergebnissen. Sie fragen sich vielleicht, warum die Verengung auf diese Weise implementiert wird. Sie könnten argumentieren, dass es intuitiver wäre, wenn die JVM den Bereich einer Zahl überprüfen und lieber eine inkompatible Zahl auf den größten darstellbaren Wert desselben Zeichens setzen würde. Dies würde jedoch eine Verzweigung erfordern, was eine kostspielige Operation ist. Dies ist besonders wichtig, da die Komplementnotation dieser beiden billige arithmetische Operationen ermöglicht.
Mit all diesen Informationen können wir sehen, was mit der Nummer
-2
in Ihrem Beispiel passiert :| int | char | byte | | 11 11 11 11 11 11 11 11 | 11 11 11 11 | 11 11 11 10 | => (int) -2 | | | 11 11 11 10 | => (byte) -2 | | 11 11 11 11 | 11 11 11 10 | => (char) \uFFFE | 00 00 00 00 00 00 00 00 | 11 11 11 11 | 11 11 11 10 | => (int) 65534
Wie Sie sehen können, ist die
byte
Umwandlung redundant, da die Umwandlung inchar
die gleichen Bits schneiden würde.All dies wird auch von der JVMS festgelegt , wenn Sie eine formellere Definition all dieser Regeln bevorzugen.
Eine letzte Bemerkung: Die Bitgröße eines Typs repräsentiert nicht unbedingt die Anzahl der Bits, die von der JVM für die Darstellung dieses Typs in ihrem Speicher reserviert werden. Als Tatsächlich unterscheiden die JVM nicht zwischen
boolean
,byte
,short
,char
undint
Typen. Alle von ihnen werden durch denselben JVM-Typ dargestellt, bei dem die virtuelle Maschine lediglich diese Castings emuliert. Auf dem Operandenstapel einer Methode (dh einer Variablen innerhalb einer Methode) verbrauchen alle Werte der genannten Typen 32 Bit. Dies gilt jedoch nicht für Arrays und Objektfelder, die jeder JVM-Implementierer nach Belieben verarbeiten kann.quelle
a - b = a + (-b)
) eine Subtraktion durchführen können . Das Hinzufügen funktioniert genauso wie bei vorzeichenlosen Ganzzahlen.(char) 65534
oder(char) 0xFFFE
statt(char) 0x65534
in der letzten Tabelle?00 00 00 00 | => (byte) -1
Hier sind zwei wichtige Dinge zu beachten:
Wenn Sie also -2 in ein int umwandeln, erhalten Sie 111111111111111111111111111110. Beachten Sie, wie der Komplementwert der beiden mit einer Eins vorzeichenerweitert wurde. das passiert nur bei negativen werten. Wenn wir es dann auf ein Zeichen eingrenzen, wird das int auf abgeschnitten
1111111111111110
Schließlich wird das Umwandeln von 1111111111111110 in ein Int mit Null und nicht mit Eins etwas erweitert, da der Wert jetzt als positiv betrachtet wird (da Zeichen nur positiv sein können). Durch Verbreitern der Bits bleibt der Wert unverändert, im Gegensatz zum Fall mit negativem Wert jedoch unverändert. Und dieser Binärwert, wenn er dezimal gedruckt wird, ist 65534.
quelle
byte
auf ein 16-Bitchar
ein 16-Bit-Zwei-Komplement von -2, das in einem 65534 aufgelöst wirdint
? Bezieht sich das alles auf zwei Komplemente? Ich meine, die Füllung von 1 in derchar
Besetzung wie geht das?A
char
hat einen Wert zwischen 0 und 65535. Wenn Sie also ein Negativ in char umwandeln, entspricht das Ergebnis dem Subtrahieren dieser Zahl von 65536, was zu 65534 führt. Wenn Sie es als a druckenchar
, wird versucht, das Unicode-Zeichen anzuzeigen dargestellt durch 65534, aber wenn Sie auf werfenint
, erhalten Sie tatsächlich 65534. Wenn Sie mit einer Zahl beginnen, die über 65536 liegt, sehen Sie ähnlich "verwirrende" Ergebnisse, bei denen eine große Zahl (z. B. 65538) klein wird ( 2).quelle
Ich denke, der einfachste Weg, dies zu erklären, wäre, es in die Reihenfolge der Operationen aufzuteilen, die Sie ausführen
Instance | # int | char | # byte | result | Source | 11 11 11 11 | 11 11 11 11 | 11 11 11 11 | 11 11 11 10 | -2 | byte |(11 11 11 11)|(11 11 11 11)|(11 11 11 11)| 11 11 11 10 | -2 | int | 11 11 11 11 | 11 11 11 11 | 11 11 11 11 | 11 11 11 10 | -2 | char |(00 00 00 00)|(00 00 00 00)| 11 11 11 11 | 11 11 11 10 | 65534 | int | 00 00 00 00 | 00 00 00 00 | 11 11 11 11 | 11 11 11 10 | 65534 |
Also, ja, wenn Sie es so betrachten, ist die Byte-Besetzung signifikant (akademisch gesehen), obwohl das Ergebnis unbedeutend ist (Freude an der Programmierung, eine signifikante Aktion kann einen unbedeutenden Effekt haben). Der Effekt der Verengung und Verbreiterung unter Beibehaltung des Zeichens. Wobei sich die Umwandlung in char verengt, sich aber nicht erweitert, um zu unterschreiben.
(Bitte beachten Sie, dass ich ein # verwendet habe, um das vorzeichenbehaftete Bit zu kennzeichnen. Wie bereits erwähnt, gibt es kein vorzeichenbehaftetes Bit für char, da es sich um einen vorzeichenlosen Wert handelt.)
Ich habe Parens verwendet, um darzustellen, was tatsächlich intern passiert. Die Datentypen sind tatsächlich in ihren logischen Blöcken zusammengefasst, aber wenn sie wie in int betrachtet werden, sind ihre Ergebnisse das, was die Parens symbolisieren.
Vorzeichenbehaftete Werte erweitern sich immer mit dem Wert des vorzeichenbehafteten Bits. Unsigned erweitert sich immer mit dem Bit aus.
* Der Trick (oder die Fallstricke) besteht darin, dass die Erweiterung von Byte auf int den vorzeichenbehafteten Wert beibehält, wenn sie erweitert wird. Was sich dann verengt, sobald es den Saibling berührt. Dies schaltet dann das vorzeichenbehaftete Bit aus.
Wenn die Konvertierung in int nicht stattgefunden hätte, wäre der Wert 254 gewesen. Aber das tut es, also ist es nicht.
quelle