Java에서 '(int) (char) (byte) -2'가 65534를 생성하는 이유는 무엇입니까?
직업에 대한 기술 테스트에서이 질문을 만났습니다. 다음 코드 예제가 제공됩니다.
public class Manager {
public static void main (String args[]) {
System.out.println((int) (char) (byte) -2);
}
}
출력은 65534로 제공됩니다.
이 동작은 음수 값에 대해서만 표시됩니다. 0과 양수는 SOP에 입력 된 값을 의미합니다. 여기서 캐스트 된 바이트는 중요하지 않습니다. 나는 그것없이 시도했다.
그래서 제 질문은 : 정확히 여기서 무슨 일이 일어나고 있습니까?
여기에서 무슨 일이 일어나고 있는지 이해하기 전에 동의해야 할 몇 가지 전제 조건이 있습니다. 다음 글 머리 기호를 이해하면 나머지는 간단한 추론입니다.
JVM 내의 모든 기본 유형은 일련의 비트로 표시됩니다.
int
타입은 32 비트에 의해 표현된다char
및short
16 비트로 유형 및byte
형태는 8 비트에 의해 표현된다.모든 JVM 번호는 서명되며
char
유형은 서명되지 않은 유일한 "번호"입니다. 숫자가 서명되면 가장 높은 비트가이 숫자의 부호를 나타내는 데 사용됩니다. 이 가장 높은 비트의 경우0
음수가 아닌 숫자 (양수 또는 0)를1
나타내고 음수를 나타냅니다. 또한 부호있는 숫자의 경우 음수 값이 양수의 증가 순서 로 반전됩니다 (기술적으로 2의 보수 표기법이라고 함 ). 예를 들어 양수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
유형은 음수가 아닌 "숫자 범위"가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에 대해이를 구현하는 방법에 대해 자세히 설명하지 않고이 모델 은 분명히 유리한 것이 저렴한 시프트 작업에 의해 수행되는 캐스팅을 허용합니다 .
이 규칙의 예외 는 이전에 말했듯이 서명되지 않은 유형을 확장 하는 것
char
입니다. A 변환 에서 a는char
항상 함께 추가 비트를 작성하여 적용됩니다0
우리는 흔적 때문에 거꾸로 표기에 대한 필요가 없다고 말했다 때문이다. 따라서 achar
에서 an으로 의 변환은 다음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
원래 유형에 대상 유형보다 많은 비트가 있으면 추가 비트가 잘립니다. 원래 값이 목표 값에 맞으면 다음과 같이 a
short
를 a로 변환하는 경우와 같이 제대로 작동합니다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
This is why narrowing castings sometimes lead to strange results. You might wonder why narrowing is implemented this way. You could argue that it would be more intuitive if the JVM checked a number's range and would rather cast an incompatible number to the biggest representable value of the same sign. However, this would require branching what is a costly operation. This is specifically important, as this two's complement notation allows for cheap arithmetic operations.
With all this information, we can see what happens with the number -2
in your example:
| 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
As you can see, the byte
cast is redundant as the cast to the char
would cut the same bits.
All this is also specified by the JVMS, if you prefer a more formal definition of all these rules.
One final remark: A type's bit size does not necessarily represent the amount of bits that are reserved by the JVM for representing this type in its memory. As a matter of fact, the JVM does not distinguish between boolean
, byte
, short
, char
and int
types. All of them are represented by the same JVM-type where the virtual machine merely emulates these castings. On a method's operand stack (i.e. any variable within a method), all values of the named types consumes 32 bits. This is however not true for arrays and object fields which any JVM implementer can handle at will.
There are two important things to note here,
- a char is unsigned, and cannot be negative
- casting a byte to a char first involves a hidden cast to an int as per the Java Language Spec.
Thus casting -2 to an int gives us 11111111111111111111111111111110. Notice how the two's complement value has been sign extended with a one; that only happens for negative values. When we then narrow it to a char, the int is truncated to
1111111111111110
Finally, casting 1111111111111110 to an int is bit extended with zero, rather than a one because the value is now considered to be positive (because chars can only be positive). Thus widening the bits leaves the value unchanged, but unlike the negative value case unchanged in value. And that binary value when printed in decimal is 65534.
A char
has a value between 0 and 65535, so when you cast a negative to char, the result is the same as subtracting that number from 65536, resulting in 65534. If you printed it as a char
, it would try to display whatever unicode character is represented by 65534, but then when you cast to int
, you actually get 65534. If you started with a number that was above 65536, you'd see similarly "confusing" results in which a big number (e.g. 65538) would end up small (2).
I think the simplest way to explain this would just be to break it down into the order of operations you are performing
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 |
- You are simply taking a 32bit signed value.
- You are then converting it to an 8bit signed value.
- When you attempt to convert it to a 16bit unsigned value, the compiler sneaks in a quick conversion to 32bit signed value,
- Then converting it to 16bit without maintaining sign.
- When the final conversion to 32bit occurs, there is no sign, so the value adds zero bits to maintain value.
So, yes, when you look at it this way, the byte cast is significant (academically speaking), though the result is insignificant (joy to programming, a significant action can have an insignificant effect). The effect of narrowing and widening while maintaining sign. Where, the conversion to char narrows, but does not widen to sign.
(Please note, I used a # to denote the Signed bit, and as noted, there is no signed bit for char, as it is an unsigned value).
I used parens to represent what is actually happening internally. The data types are actually trunked in their logical blocks, but if viewed as in int, their results would be what the parens symbolize.
Signed values always widen with value of the signed bit. Unsigned always widen with the bit off.
*So, the trick (or gotchas) to this, is that the expansion to int from byte, maintains the signed value when widened. Which is then narrowed the moment it touches the char. This then turns off the signed bit.
If the conversion to int did not occur, the value would have been 254. But, it does, so it isn't.
참고URL : https://stackoverflow.com/questions/24635977/why-does-intcharbyte-2-produce-65534-in-java
'program tip' 카테고리의 다른 글
MySQL에서 누적 합계 열 만들기 (0) | 2020.11.03 |
---|---|
장고 비밀번호 생성기 (0) | 2020.11.03 |
다음 위치 후 원격 파일 이름을 가져 오기 위해 컬 (0) | 2020.11.03 |
~ / bin을 내 경로에 어떻게 추가합니까? (0) | 2020.11.03 |
SELECT 쿼리에 대한 SQL Server 잠금 이해 (0) | 2020.11.03 |