Refer to the JVM specification, §2.11.1. Types and the Java Virtual Machine:
Note that most instructions in Table 2.11.1-A do not have forms for the integral types byte
, char
, and short
. None have forms for the boolean
type. A compiler encodes loads of literal values of types byte
and short
using Java Virtual Machine instructions that sign-extend those values to values of type int
at compile-time or run-time. Loads of literal values of types boolean
and char
are encoded using instructions that zero-extend the literal to a value of type int
at compile-time or run-time. Likewise, loads from arrays of values of type boolean
, byte
, short
, and char
are encoded using Java Virtual Machine instructions that sign-extend or zero-extend the values to values of type int
. Thus, most operations on values of actual types boolean
, byte
, char
, and short
are correctly performed by instructions operating on values of computational type int
.
It’s worth recalling that in Java, any integer arithmetic not involving long
will have an int
result, regardless of whether the input is byte
, char
, short
, or int
.
So a line like
short i = 1, j = 2, k = i + j;
will not compile, but require a type cast, like
short i = 1, j = 2, k = (short)(i + j);
And this type cast will be the only indicator that short
is involved. Letting debug hints aside, there is no formal declaration of local variables in bytecode, but only assignments of values which determine their type. So local variables of type short
simply do not exist. The code above compiles to
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: i2s
8: istore_3
which is identical to the compiled form of
int i = 1, j = 2, k = (short)(i + j);
But mind that the compile-time type of variables can change which method the compiler chooses for an invocation in case of overloads. Which is especially important if the types carry different semantics, like in the case of print(boolean)
or print(char)
. While the value passed to the method has an int
type in either case, the outcome is entirely different.
Another example of differences enforced by the compiler is
{
int i = 1;
i++;
}
{
short s = 1;
s++;
}
which gets compiled to
0: iconst_1
1: istore_1
2: iinc 1, 1
5: iconst_1
6: istore_1
7: iload_1
8: iconst_1
9: iadd
10: i2s
11: istore_1
So, since the calculation is always performed in 32 bit, the compiler inserts the necessary code to truncate the result to short
for the second increment. Note again the absence of variable declarations, so the code is identical to the compiled form of
int i = 1;
i++;
i = 1;
i = (short)(i+1);
It’s also worth looking at the Verification Type System, as the verifier will check the validity of all transfers from and to local variables:
The type checker enforces a type system based upon a hierarchy of verification types, illustrated below.
Verification type hierarchy:
top
____________/\____________
/ \
/ \
oneWord twoWord
/ | \ / \
/ | \ / \
int float reference long double
/ \
/ \_____________
/ \
/ \
uninitialized +------------------+
/ \ | Java reference |
/ \ | type hierarchy |
uninitializedThis uninitialized(Offset) +------------------+
|
|
null
So the type system is simplified, compared to the Java language types, and the verifier doesn’t mind, e.g. if you pass a boolean
value to a method expecting a char
, as both are int
types.