1

Why second line throws ClassCastException in runtime?

Object obj = new Integer(2);
Byte b2 = (byte) obj; // at Runtime: ClassCastException "Integer cannot be cast to Byte"

I thought that Integer (to which reference obj points) is unboxed into int, then cast to byte, then boxed into Byte and successfully assigned. Eclipse compiler warning also says that (correcting me just a bit):

  • The expression of type Object is unboxed into byte
  • The expression of type byte is boxed into Byte

So why it cannot assign this Byte in RHS into Byte reference in LHS?

P.S. Same ClassCastException on Oracle javac compiler

If Object obj = new Integer(2);, then Long b2 = (long) obj; works but Long b2 = 7; fails. While Byte b2 = (byte) obj; fails, but Byte b2 = 7; is fine! Maybe there some clue in those reciprocal differences?

Empirically, I would say that narrowing of primitive type is prohibited (even with explicit cast) after unboxing (while widening is allowed) - it could explain this behavour.

Got it finally:

5.2. Assignment Contexts (JLS): variable = expression

Assignment contexts allow the use of one of the following:

an identity conversion (§5.1.1)

a widening primitive conversion (§5.1.2)

a widening reference conversion (§5.1.5)

a boxing conversion (§5.1.7) optionally followed by a widening reference conversion

an unboxing conversion (§5.1.8) optionally followed by a widening primitive conversion.

In addition, if the expression is a constant expression (§15.28) of type byte, short, char, or int:

A narrowing primitive conversion followed by a boxing conversion may be used if the type of the variable is:

Byte and the value of the constant expression is representable in the type byte.

Short and the value of the constant expression is representable in the type short.

Character and the value of the constant expression is representable in the type char.

But this is also fine in Runtime:

 Object o = new Integer(2);
 Integer i2 = (Integer) o; 
 Integer i3 = (int) o; 

We need to further expore "assignment context" vs "assignment context" - how they work together.

1
  • There is a typo: I believe, you want to write ... = (Byte) obj; Byte and byte are different types. Commented Dec 22, 2017 at 15:36

2 Answers 2

3

Original code

Object obj = new Integer(2);
Byte b2 = (byte) obj;

Boxing/unboxing is determined statically, i.e. at compile time. You've cast to Object, so the compiler doesn't know that obj is actually of type Integer. Instead, it generates bytecode that assumes an instance of Byte, along with an explicit check (which is what fails at runtime):

ALOAD 1
CHECKCAST java/lang/Byte                                // Oh dear
INVOKEVIRTUAL java/lang/Byte.byteValue ()B              // Unbox as a Byte
INVOKESTATIC java/lang/Byte.valueOf (B)Ljava/lang/Byte; // Box as a Byte
ASTORE 2


So let's use an Integer reference instead

Integer obj = new Integer(2);
Byte b2 = (byte) obj;

The second line doesn't even compile. (byte) obj is a casting context, and there are a bunch of rules that define what's allowed here.1 An unboxing conversion followed by a narrowing conversion is not allowed.


So let's use a widening conversion instead

The rules do allow an unboxing conversion followed by a widening conversion, so this code compiles and runs without error:

Integer obj = new Integer(2);
Long b2 = (long) obj;

Let's look at the corresponding bytecode:

ALOAD 1
INVOKEVIRTUAL java/lang/Integer.intValue ()I            // Unbox as an Integer
I2L                                                     // Convert to long
INVOKESTATIC java/lang/Long.valueOf (J)Ljava/lang/Long; // Box as a Long
ASTORE 2

We see two differences:

  1. It correctly unboxes as an Integer rather than as a Byte.
  2. There's no CHECKCAST, because the compiler knows for sure what type is present.

So it's actually more efficient!


But I want the original intended behaviour!

Well, then you have no choice but to perform the relevant chain of casts:

Object obj = new Integer(2);
Byte b2 = (byte)(int)(Integer) obj;

And for completeness, here's the corresponding bytecode:

ALOAD 1
CHECKCAST java/lang/Integer                             // This is now ok
INVOKEVIRTUAL java/lang/Integer.intValue ()I            // Unbox as an Integer
I2B                                                     // Convert to byte
INVOKESTATIC java/lang/Byte.valueOf (B)Ljava/lang/Byte; // Box as a Byte
ASTORE 2

1. Note that there's also an assignment context here due to the =, which has its own set of rules. Amongst other things, one can't do Long b2 = 7; because there's nothing that permits a widening conversion followed by a boxing conversion.

Sign up to request clarification or add additional context in comments.

2 Comments

"Long b2 = (long) obj;" works but "Long b2 = 7;" fails. While "Byte b2 = (byte) obj;" fails, but "Byte b2 = 7;" is fine! Maybe there some clue in those reciprocal differences?
@CodeComplete - Those are assignment contexts, so the rules are slightly different. (It's the list that starts with "In addition, ..." that's relevant.)
1

Casts don't work that way in Java. A cast between two wrapper classes would just fail, as you've seen - it won't go through unboxing, widening and re-boxing. If you want to achieve this behavior, you'd have to go through those steps yourself:

Object obj = new Integer(2);
Byte b2 = (byte) ((Integer)obj).intValue();

1 Comment

But the proximate issue is the fact that obj is an Object. Also, the OP isn't "casting between two wrapper classes". Also, the compiler would allow Integer obj = new Integer(2); Long b2 = (long)obj;.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.