Ответ 1
Есть некоторые предпосылки, о которых нам нужно договориться, прежде чем вы сможете понять, что здесь происходит. С пониманием следующих пунктов, остальное - простой вывод:
-
Все примитивные типы в JVM представлены как последовательность бит. Тип
int
представлен 32 битами, типыchar
иshort
- 16 бит, а типbyte
- 8 бит. -
Все номера JVM подписаны, где тип
char
является единственным беззнаковым "числом". Когда число подписано, старший бит используется для обозначения знака этого числа. Для этого наивысшего бита0
представляет неотрицательное число (положительное или нулевое), а1
обозначает отрицательное число. Кроме того, с подписанными числами отрицательное значение инвертировано (технически известно как двухкомпонентная нотация) к порядку инкремента положительных чисел. Например, положительное значение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
в то время как порядок бит для отрицательных чисел инвертируется:
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
Эта инвертированная нотация также объясняет, почему отрицательный диапазон может содержать дополнительное число по сравнению с положительным диапазоном, где последний включает представление числа
0
. Помните, что это всего лишь вопрос интерпретации битовой диаграммы. Вы можете отметить отрицательные числа по-разному, но эта инвертированная нотация для отрицательных чисел весьма удобна, потому что она допускает некоторые довольно быстрые преобразования, как мы увидим в небольшом примере позже.Как уже упоминалось, это не относится к типу
char
. Типchar
представляет символ Unicode с неотрицательным "числовым диапазоном" от0
до65535
. Каждое из этих чисел относится к значению 16-бит Unicode. -
При преобразовании между
int
,byte
,short
,char
иboolean
типам JVM необходимо либо добавить, либо усечь биты.Если целевой тип представлен больше бит, чем тип, из которого он был преобразован, тогда JVM просто заполняет дополнительные слоты значением наивысшего бита данного значения (которое представляет подпись):
| short | byte | | | 00 00 00 01 | => (byte) 1 | 00 00 00 00 | 00 00 00 01 | => (short) 1
Благодаря перевернутой нотации эта стратегия также работает для отрицательных чисел:
| short | byte | | | 11 11 11 11 | => (byte) -1 | 11 11 11 11 | 11 11 11 11 | => (short) -1
Таким образом, знак значения сохраняется. Не вдаваясь в подробности реализации этого для JVM, обратите внимание на то, что эта модель позволяет выполнить кастинг с помощью дешевой операции переключения, что очевидно выгодно.
Исключением из этого правила является расширение типа
char
, который, как мы уже говорили, беззнаковый. Преобразование из achar
всегда применяется путем заполнения дополнительных бит0
, потому что мы сказали, что нет знака и, следовательно, нет необходимости в перевернутом обозначении. Следовательно, преобразование achar
вint
выполняется как:| 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
Если исходный тип имеет больше бит, чем целевой тип, дополнительные биты просто обрезаются. Пока исходное значение будет соответствовать целевому значению, это работает отлично, например, для следующего преобразования
short
вbyte
:| 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
Однако, если значение слишком велико или слишком мало, это больше не работает:
| 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
Вот почему сужение отливок иногда приводит к странным результатам. Вы можете удивиться, почему сужение осуществляется таким образом. Вы могли бы утверждать, что было бы более интуитивно, если бы JVM проверил диапазон чисел и скорее поставил бы несовместимое число самому большому представимому значению того же знака. Однако для этого потребуется ветвление, что является дорогостоящей операцией. Это особенно важно, поскольку эта двухкомпонентная нотация позволяет выполнять дешевые арифметические операции.
Со всей этой информацией мы можем увидеть, что происходит с номером -2
в вашем примере:
| 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
Как вы можете видеть, приведение byte
избыточно, поскольку приведение к char
приведет к вырезанию одних и тех же бит.
Все это также заданное JVMS, если вы предпочитаете более формальное определение всех этих правил.
Последнее замечание: размер бит типа не обязательно представляет количество бит, зарезервированных JVM для представления этого типа в его памяти. На самом деле JVM не различает типы boolean
, byte
, short
, char
и int
. Все они представлены одним и тем же JVM-типом, где виртуальная машина просто эмулирует эти отливки. В стеке операндов метода (т.е. Любой переменной внутри метода) все значения именованных типов потребляют 32 бита. Это, однако, неверно для массивов и полей объектов, которые любой исполнитель JVM может обрабатывать по своему усмотрению.