Почему '(int)(char) (byte) -2' производит 65534 в Java?
я столкнулся с этим вопросом в техническом тесте для работы. Приведен следующий пример кода:
public class Manager {
public static void main (String args[]) {
System.out.println((int) (char) (byte) -2);
}
}
Это дает выход как 65534.
это поведение показано только для отрицательных значений; 0 и положительные числа дают одно и то же значение, то есть введенное в SOP. Байт, брошенный здесь, незначителен; я пробовал без него.
Итак, мой вопрос: что именно здесь происходит?
4 ответов:
есть некоторые предпосылки, которые мы должны согласовать, прежде чем вы сможете понять, что здесь происходит. С пониманием следующих пунктов маркера, остальное-простой вывод:
все примитивные типы в 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-бит Юникод значение.когда преобразование между
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, обратите внимание, что эта модель позволяет выполнять кастинг дешевым операции сдвига что, очевидно, выгоднее.
исключением из этого правила является расширение a
charтип, который, как мы уже говорили, без знака. А конверсия с acharвсегда применяется путем заполнения дополнительного биты с0потому что мы сказали, что нет никаких признаков и, следовательно, нет необходимости в перевернутом формате. Преобразование acharдо выполняется так:| 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если исходный тип имеет больше битов, чем целевой тип, дополнительные биты просто отрезать. До тех пор, пока исходное значение будет соответствовать целевому значению, это отлично работает, как, например, для следующего преобразования a
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однако, если значение слишком большая или слишком маленький, это больше не работает:
ветвление что такое a дорогостоящая операция. Это особенно важно, так как это два дополнения нотации позволяет выполнять дешевые арифметические операции.| 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со всей этой информацией, мы можем видеть, что происходит с номером
-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отрезал бы такие же биты.и указанные виртуальные машины, если вы предпочитаете более формальное определение всех этих правил.
последнее замечание: размер бита типа не обязательно представляет количество битов, зарезервированных JVM для представления этого типа в его памяти. На самом деле, JVM не различает
boolean,byte,short,charиintтипы. Все они представлены одним и тем же JVM-типом, где виртуальная машина просто эмулирует эти отливки. На стеке операндов метода (т. е. любая переменная в пределах метода) все значения именованных типов потребляют 32 бита. Однако это неверно для массивов и полей объектов, которые любой JVM-разработчик может обрабатывать по своему усмотрению.
здесь нужно отметить две важные вещи,
литье -2 в int дает нам 11111111111111111111111111111110. Обратите внимание, как значение дополнения двух было расширено знаком с единицей; это происходит только для отрицательных значений. Когда мы сужаем его чар, int усекается до
- символ без знака и не может быть отрицательным
- приведение байта к символу сначала включает скрытое приведение к int согласно Спецификация Языка Java.
1111111111111110наконец, приведение 1111111111111110 к int растягивается на ноль, а не на единицу, потому что значение теперь считается положительным (потому что символы могут быть только положительными). Таким образом, расширение битов оставляет значение неизменным, но в отличие от отрицательного значения случай не меняется в значении. И это двоичное значение при печати в десятичном формате составляет 65534.
A
charимеет значение от 0 до 65535, поэтому при приведении отрицательного значения к char результат будет таким же, как вычитание этого числа из 65536, что приведет к 65534. Если вы напечатали его какchar, Он попытается отобразить любой символ Юникода, представленный 65534, но затем, когда вы приведете кint, вы на самом деле получаете 65534. Если вы начали с числа, которое было выше 65536, вы увидите аналогичные "запутанные" результаты, в которых большое число (например, 65538) будет в конечном итоге мало (2).
Я думаю, что самый простой способ объяснить это было бы просто разбить его на порядок при выполнении операций
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 |
- вы просто принимаете 32-битное значение со знаком.
- затем вы преобразуете его в 8-битное значение со знаком.
- при попытке преобразовать его в 16-битное беззнаковое значение компилятор быстро преобразует его в 32-битное знаковое значение,
- затем преобразование его в 16 бит без поддержания знак.
- когда происходит окончательное преобразование в 32 бит, нет никакого знака, поэтому значение добавляет нулевые биты для поддержания значения.
Итак, да, когда вы смотрите на это таким образом, приведение байтов является значительным (академически говоря), хотя результат незначителен (радость для программирования, значительное действие может иметь незначительный эффект). Эффект сужения и расширения при сохранении знака. Где преобразование в char сужается, но не расширяется до знак.
(обратите внимание, что я использовал # для обозначения подписанного бита, и, как уже отмечалось, нет подписанного бита для char, так как это беззнаковое значение).
я использовал parens для представления того, что на самом деле происходит внутри. Типы данных фактически транкируются в своих логических блоках, но если рассматривать их как int, их результаты будут тем, что символизируют parens.
подписанные значения всегда расширяются со значением подписанного бита. Unsigned всегда расширяется с помощью бита выключено.
*Итак, трюк (или gotchas) к этому заключается в том, что расширение до int из байта поддерживает знаковое значение при расширении. Который затем сужается в тот момент, когда он касается char. Это затем отключает подписанный бит.
Если бы преобразование в int не произошло, значение было бы 254. Но, это так, так что это не так.
Comments