5

I am mystified by the behavior of the Java compiler when assigning primitives to wrapper class references. Please see the code below. The lines with comments don't compile.

I don't understand the logic of why:

  1. a byte can be assigned to a Byte or Short, but not Integer or Long reference
  2. a short can be assigned to a Byte or Short, but not Integer or Long reference
  3. an int can be assigned to a Byte, Short, or Integer, but not Long reference
  4. a long can be assigned to a Long, but not Byte, Short or Integer reference

I cannot see the pattern. Any insight into this will be really helpful. Thanks.

Byte s5 = (byte)7;
Short s6 = (byte)7;
Integer s7 = (byte)7;   // Does not compile
Long s8 = (byte)7;      // Does not compile

Byte s9 = (short)7;
Short s10 = (short)7;
Integer s11 = (short)7; // Does not compile
Long s12 = (short)7;    // Does not compile

Byte s1 = (int)7;
Short s2 = (int)7;
Integer s3 = (int)7;
Long s4 = (int)7;       // Does not compile

Byte s13 = (long)7;     // Does not compile
Short s14 = (long)7;    // Does not compile
Integer s15 = (long)7;  // Does not compile
Long s16 = (long)7;
8
  • JLS-5.1.7. Boxing Conversion. Commented Feb 5, 2015 at 3:51
  • 1
    @ElliottFrisch, I just looked at the link you posted, but it really doesn't deal with the point of my question. Commented Feb 5, 2015 at 14:44
  • 1
    I thought your question was why are some boxing conversions allowed and some not... Please clarify the point. Commented Feb 5, 2015 at 14:46
  • 1
    And what was the point of casting, narrowing and boxing? What problem are you trying to solve, and how does this help you solve it? Commented Feb 5, 2015 at 15:05
  • 3
    It is just a question.. based on something I saw in the Sierra/Bates book. I am trying to understand the logic of this, not trying to solve any particular problem. Commented Feb 5, 2015 at 15:23

3 Answers 3

4

Let's look at the types of conversions allowed in an assignment context.

Principally:

Assignment contexts allow the use of one of the following:

  • an identity conversion

  • a widening primitive conversion

  • a widening reference conversion

  • a boxing conversion optionally followed by a widening reference conversion

  • an unboxing conversion optionally followed by a widening primitive conversion.

(Note my emphasis on one.)

Most of your examples that do not compile, for example

Integer s11 = (short)7;

require a widening primitive conversion followed by a boxing conversion. This is not a permitted conversion.

But then you might wonder why the following example does compile:

Byte s9 = (short)7;

This is a narrowing primitive conversion followed by a boxing conversion.

This is a special case:

In addition, if the expression is a constant expression 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.

This special case is necessary because there is no way to express an integer literal of a type narrower than int.

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

6 Comments

I was in a hurry, and thought 'equate' would be understood in the context of what I was saying. I understand that it is an assignment.
You have pointed out the case of Integer s11 = (short)7, where a widening followed by boxing is required. Yes, I understand that widening followed by boxing is too much for the compiler to handle. However, that doesn't explain why Short s6 = (byte)7; does compile. Here too, a widening followed by boxing is required, but the compiler seems to be able to handle that fine!
"Short s6 = (byte)7" This is covered by the clause in the 2nd part of my answer: "Short and the value of the constant expression is representable in the type short".
I am confused.. that part of your answer talks about a narrowing conversion followed by boxing. What we have in Short s6 = (byte)7; is a widening followed by boxing.. Or am I getting something wrong?
Actually I suppose you're right, the JLS doesn't address that case exactly. The possibility I can think of is that a pretty liberal interpretation of the phrase "In addition..." could imply that the compiler can also do widening primitive conversion -> narrowing primitive conversion -> boxing conversion. In other words the compiler might be allowed to widen (byte)7 to int and then perform narrowing -> boxing. It's also perfectly possible javac doesn't adhere completely to the specification or there is some clause somewhere else completely that I don't know about.
|
1

This seems to be compiler-specific behavior. When I paste your code into Eclipse, running Java 7, I do not see the compiler errors you report for short to Integer or byte to Integer.

Instead, I see byte, short, and int can all be assigned to Byte, Short, and Integer, but not Long, and long can only be assigned to Long. Interestingly, if you change the variables to primitives instead of wrapper types, the byte, short, and int behavior doesn't change, but now the assignments from the other types to long also work.

javac 1.7.0_02
           | byte | Byte || short | Short || int | Integer || long | Long |
From byte  | Yes  | Yes  || Yes   | Yes   || Yes | No      || Yes  | No   |
From short | Yes  | Yes  || Yes   | Yes   || Yes | No      || Yes  | No   |
From int   | Yes  | Yes  || Yes   | Yes   || Yes | Yes     || Yes  | No   |
From long  | No   | No   || No    | No    || No  | No      || Yes  | Yes  |

Eclipse Indigo
           | byte | Byte || short | Short || int | Integer || long | Long |
From byte  | Yes  | Yes  || Yes   | Yes   || Yes | Yes     || Yes  | No   |
From short | Yes  | Yes  || Yes   | Yes   || Yes | Yes     || Yes  | No   |
From int   | Yes  | Yes  || Yes   | Yes   || Yes | Yes     || Yes  | No   |
From long  | No   | No   || No    | No    || No  | No      || Yes  | Yes  |

Given that different compilers allow different conversions, I suspect the "correct" behavior is not actually spelled out in the JLS. It seems certain conversions are done under the covers because the compiler writers considered it convenient (e.g. byte a = (int)1 is allowed but byte a = (int)1000 is not), not because it's a documented part of the language.

4 Comments

I get the same as the OP using the Java 7 compiler. Eclipse has its own compiler I believe.
@dimo414, I am using Java 7.. I rechecked. The behavior is exactly as I wrote above in my question.
@user3516726 it seems to be compiler-specific behavior, as EJP mentioned. I've updated my answer after verifying it myself.
I gave this an upvote because I think the table is useful. I think you should also probably include char and Character even though the OP didn't ask about them. They have more corner cases that the specification is vague about.
0

From my research, I found that a byte is an 8-bit signed integer. Shorts are 16-bit signed integers. Therefore I can see why they are compatible, they are both two's complement signed integers, emphasis on signed. A long is a 64-bit integer, but it can also be unsigned (considering it has methods for comparing unsigned longs). This would probably explain why your conversions to long are causing errors - you'd be casting a signed byte to a potentially unsigned long. (Source: Reading about primitives at http://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html)

2 Comments

There's no issue with unsignedness. Java doesn't have unsigned types, just some utility methods that do arithmetic and comparisons as if unsigned.
Ah sorry, I came from C++ to Java and I keep forgetting that.

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.