Question

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?

Was it helpful?

Solution

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.

OTHER TIPS

Before we start, it's important to note that in java, all purely numeric literals are int values.

The key phrase regarding allowable un-cast constants is is representable in the type of the variable. That just means the constant "is in range" of the variable type, so:

  • for byte -128 to 127
  • for short -32768 to 32767
  • for char 0 to 65535

Values "in range" won't "lose information" if cast to the variable type. That explains why in-range constants are allowed.

For values outside of the range, an explicit cast is required because information would be lost if a cast was performed. When a narrowing cast is done, bits outside the scope of the variable type are simply masked off - that's what "losing information" means in these cases.

It's as if an assignment of a constant is like:

byte b = nnn & 0xFF;

If nnn is within range, then the mask will not change the value, so nothing lost, so no problem - compiles OK.

If nnn is out of range, information would be lost, so require an explicit cast to acknowledge the loss.

If you recall that all integer literals are ints, the rules are actually no different than those that apply to assigning an int to narrower variable type, except the compiler allows no cast if it knows the value will "fit",

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top