Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Confused about the idea of implicit narrowing on primitives in Java

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!!

like image 739
Mohammad Ali Asgar Avatar asked Jan 12 '15 05:01

Mohammad Ali Asgar


2 Answers

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.

like image 104
Powerlord Avatar answered Sep 30 '22 11:09

Powerlord


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.

like image 39
Nathan Avatar answered Sep 30 '22 11:09

Nathan