Confused about the idea of implicit narrowing on primitives in Java
Asked Answered
B

4

6

The following seemingly trivial problem has shaken the core of my understanding on how primitives work in Java.

I have come across the term "implicit narrowing" whereby a variable of a smaller-range type is allowed to hold a literal value of a larger-range type, if that value falls within that smaller-range.
As I know, Java permits that only among byte, char, short, and int.

For example, a byte variable CAN take an int if that value is small enough to fit the range of the byte type.

byte b1 = 3; // allowed even though 3 is an int literal
byte b2 = 350; // compilation error because a byte cannot go beyond positive 127

So, this works fine:

byte k = 3;

But I don't know why the line below does not work!!

Byte k = new Byte(3);

Unless I change the latter to Byte k = new Byte((byte)3), I get this compilation error:

error: no suitable constructor found for Byte(int)
        Byte k = new Byte(3);                                        
                 ^             
constructor Byte.Byte(byte) is not applicable                    
(actual argument int cannot be converted to byte by method invocation conversion)

The last portion of the error message seems to have a clue, which says:

"... actual argument int cannot be converted to 
 byte by method invocation conversion"

Then my question about the clue becomes:
why?! I mean, what is the difference between assigning a small int literal to a byte and passing a small int literal to a method to be captured by a method parameter of type byte?

I understand that casting would have to be used if I passed an int variable. But, I am NOT passing a variable. Rather, I am passing a small literal which the compiler should realize that it is small enough for a byte!!

Bechtold answered 12/1, 2015 at 5:22 Comment(1)
My understanding is that the literal '3' is not an integer. It's a byte, char, int etc. depending on context. So there's no real casting involved, it's just syntactic sugar the compiler allows to avoid having to designate the type of literals. But the compilers don't allow the same flexibility in method invocation. They could and possibly should but they don't. I speculate that the reason is that determining the type of an argument is already very heavily overloaded with boxing, polymorphism etc. Adding another layer of sugar would confusing things further.Transpacific
E
12

The rules are different for assignment and method calls (including constructors) for literals.

According to the Java Language Specification (JLS) 8 §3.10, Java only has 6 literal types: IntegerLiteral, FloatingPointLiteral, BooleanLiteral, CharacterLiteral, StringLiteral, and NullLiteral.

3.10.1 further specifies:

An integer literal is of type long if it is suffixed with an ASCII letter L or l (ell); otherwise it is of type int (§4.2.1).

(§4.2.1 is just a specification of the ranges of types)

There are nearly 7 pages about IntegerLiteral, so I'm not going to go through the whole thing. Suffice it to say that int literals are downcast to byte and short as appropriate during assignment.

However, its usage in a constructor is entirely different. Since a constructor is a method, the normal rules for its arguments apply.

I tried quickly sorting through the rules for matching in the JLS, but its a very long and complicated section. Needless to say, only widening conversions will happen automatically when choosing a method to run. i.e. you can pass an int to a method expecting a long without an explicit cast, but you can't pass an int to a method expecting a byte unless you explicitly cast it.

Empennage answered 12/1, 2015 at 5:42 Comment(0)
C
3

I would have to agree that it does seem similar, but to the compiler, these are two very different things. The first is, as you said, using implicit narrowing to cast the value. In the second however, you're using a constructor with a particular signature. That signature requires you to provide a byte. Think of it this way: what happens if they added a constructor, public Byte(int i)? Now all of the sudden, you're changing your old code's meaning if they were to allow this. I think this particular instance is why this is not allowed, although there may be other additional compilations.

Chabot answered 12/1, 2015 at 5:42 Comment(1)
That's brilliant. I think this issue you have pointed out is not limited to only constructors but also any other methods.Bechtold
T
2

Integer literals are implicitly downcast to byte, char, short and int as appropriate during ASSIGNMENTS.

Therefore byte b1 = 3; compiles fine.

Note that integer literals are by default of type int, so unless you either assign an integer literal to a variable of type byte or explicitly cast it to a byte, compiler consider the literal itself to be an int.

That is the reason why you get a compilation error on Byte k = new Byte(3); Here you are using an int in the constructor which requires a byte.

Theme answered 1/2, 2016 at 4:22 Comment(1)
Good explanation. Can add that even static Byte.valueOf(1) will also fail to compile.Clite
T
0

Byte k=new Byte(3);

1) The above statement will create a object of type "Byte", remember that implicit narrowing can only occur for primitive's.

2) While creating object of type Byte, the constructor will consider the argument as Integer, for that reason we have to explicitly cast the argument to byte as:

Byte k=new Byte((byte)3);

Tillie answered 27/7, 2015 at 6:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.