Type information is only needed at compile time to generate the correct bytecode. Bytecode instructions (like assembly instructions) can typically act on only one datatype. Thus, the instruction used reflects the type of the operands. This is true for most C-family languages.
To see in action how the bytecode would differ when using a primitive and a dynamic allocation, let's take a simple example.
public static void main (String [] args) {
int i = 0;
int j = i + 1;
}
And the bytecode generated:
public static void main(java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: iconst_1
4: iadd
5: istore_2
6: return
So we store and load the integers using istore
and iload
, and then we add them using iadd
(i
for integer).
Now take this example, using a dynamic memory allocation instead of a primitive:
public static void main (String [] args) {
Integer i = new Integer(0);
int j = i + 1;
}
And the bytecode:
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/lang/Integer
3: dup
4: iconst_0
5: invokespecial #3 // Method java/lang/Integer."<init>":(I)V
8: astore_1
9: aload_1
10: invokevirtual #4 // Method java/lang/Integer.intValue:()I
13: iconst_1
14: iadd
15: istore_2
16: return
In this version, we first have to invoke the intValue()
method of the Integer
object to retrieve the value, and then we can act on it via iadd
.
And for evidence that datatypes need not be stored after compilation (since they are encoded in the instructions themselves, like istore
for "integer store"), see the reference in jrahhali's answer.