K.Sierra and B.Bates in their book "SCJP Study Guide" write
"The following is legal byte b = 27;
but only because the compiler automatically narrows the literal value to a byte. In other words, the compiler puts in the cast. The preceding code is identical to the following: byte b = (byte) 27;
"
In my opinion this explanation is incorrect. Are these two lines of code identical?
In fact
byte b = 27;
is simply a constant. And the compile-time narrowing of constants is the only reason why this code is valid. So no cast is needed. When narrowing the compiler just checks whether the specified value fits in the type of the variable. The specification says:
A narrowing primitive conversion may be used if the type of the variable is byte, short, or char, and the value of the constant expression is representable in the type of the variable.
In the second case
byte b = (byte) 27;
casting does occur during at runtime and the primitive value is computed according to specific rules. The compiler doesn't care about the compatibility of primitive types. For example
byte b = 5.0; // compile error
byte b = 277777777; // compile error
byte b = (byte) 5.0; // valid!
byte b = (byte) 277777777; // valid!!
This makes me think that widening/narrowing conversion and casting are fundamentally different. But in various sources they are often used interchangeably. Is this correct? Does casting occur under the covers in case of an implicit narrowing conversion?
Can anyone explain the real behavior of the compiler in the situation described in the above book?
It's easy enough to test it out. Put the following in Temp.java:
class Temp {
public static void main(String[] argv) {
byte b = 27;
System.out.println(b);
}
}
Now compile it with your favorite compiler:
$ javac Temp.java
Now dump the bytecode with javap:
$ javap -c Temp.class
Compiled from "Temp.java"
class Temp {
Temp();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: bipush 27
2: istore_1
3: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
6: iload_1
7: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
10: return
}
Now replace 27
with (byte)27
and run again. You'll see there is no difference. In fact the two classfiles will have the same md5sum.
There is no runtime cast in the bytecode because the compiler figured out it wouldn't be needed, and optimized it away.
I believe you're correct that syntactically the line byte b = 27
differs from the line byte b = (byte) 27
, but they are semantically the same, because all standard compilers are smart enough to optimize the line into a single bytecode.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With