Unary (such as ~
) and binary operators in Java subject their operands to "unary numeric promotion" (JLS, Section 5.6.1) and "binary numeric promotion" (JLS, Section 5.6.2), respectively, fancy terms for "promote things to at least int
first".
Specifically, for unary numeric promotion, quoting the JLS section linked above:
Some operators apply unary numeric promotion to a single operand, which must produce a value of a numeric type:
and
... if the operand is of compile-time type byte, short, or char, it is promoted to a value of type int by a widening primitive conversion (§5.1.2).
(Binary numeric promotion is similar, operating on both operands.)
So, even if b
is a byte
, ~b
is an int
, because b
's value was promoted to an int
first.
The solution: cast it back to a byte
:
b = (byte) (~b);
Why, Java?
That leaves the question, why? It seems for the operators I can find, the JVM bytecode instructions for operating on byte
s, short
s, and char
s simply do not exist. For example, the unary bitwise complement operator you're using (~) is implemented as an "XOR" operation with -1
(all bits set). From that link:
tempSpock &= ~mask;
becomes
25 iload_2 // Push local variable 2 (mask).
26 iconst_m1 // Push -1.
27 ixor // Bitwise EXCLUSIVE-OR top two ints: ~mask
However, I can only find instructions for XOR (and for other unary and binary operators too) for int
s and long
s (float
and double
version exist for other operators where appropriate).
So, Java must perform those promotions because there are no bytecode instructions for performing those operations on byte
s, short
s, or char
s.
Why not, JVM?
That brings up another question: Why doesn't the JVM support such bytecode instructions? The answer appears to be, "Because there would be too many to encode them all in a one-byte instruction set." According to the JVM Specification, Section 2.11.1,
Given the Java Virtual Machine's one-byte opcode size, encoding types into opcodes places pressure on the design of its instruction set. If each typed instruction supported all of the Java Virtual Machine's run-time data types, there would be more instructions than could be represented in a byte. Instead, the instruction set of the Java Virtual Machine provides a reduced level of type support for certain operations. In other words, the instruction set is intentionally not orthogonal. Separate instructions can be used to convert between unsupported and supported data types as necessary.
(emphasis mine)
In conclusion, the JVM's one-bytecode instruction set precludes the bytecode instructions for most operations on byte
s, char
s, and short
s, necessitating unary numeric promotion and binary numeric promotion.