Confused about the idea of implicit narrowing on primitives in Java - 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!!

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.

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.

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.

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);

Related

Why can I assign an integer literal to a short type variable but not to a short type method parameter?

Why can I do this:
short a = 5;
But not this:
void setNum(short a);
setNum(5);
It throws:
Possible lossy conversion from int to short
I understand that 5 is an integer literal and you have to cast it. I also understand that if the value is not a constant then is obvious that it needs to throw that error because maybe the value reaches the limit of a short type. But why if the compiler knows I'm passing a constant that a short can hold (as in the assignment) it doesn't let it compile? I mean, what is the difference between them?
In order to understand why the assignment type-conversion works whilst the invocation one is rejected, one has to refer to the Java Language Specification topic for both narrowing primitive conversions and the context of that conversion: assignment context and invocation context.
According to the JLS, the narrowing primitive conversion is allowed in assignment context if:
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.
... which matches your case with int constant 5 when assigned to short a.
No such narrowing primitive conversion is allowed in the invocation context, which explains why your call to setNum(short) fails when passed the int constant 5.
But why if the compiler knows I'm passing a constant that a short can hold (as in the assignment) it doesn't let it compile? I mean, what is the difference between them?
The JLS must not have wanted to burden compilers with this additional logic. In the invocation case, the argument which is type-matched to the formal parameter is an expression - the compiler already has to determine the type, but it shouldn't need to also check if the expression's value can also safely be narrowed. In this case, being a constant, it is clear to us that it can be, but in execution context the compiler is allowed to not bother with that check, and is in-fact correct to disallow it.
It should be fairly clear that when expressions are allowed, it would be much easier for bugs to creep in where narrowing cannot be done without losing precision, so the JLS and compilers just disallow it in all cases.
The same is true in numeric context, so the declaration:
short a = 5;short b = a * 5;
... is similarly not allowed, despite being clearly comprised of constants which narrow correctly.
When you type 5, this is automatically an integer. I'm not sure what IDE you are using that's giving you the error, but the reason it's warning you is because you're converting a larger storage capacity value to a smaller one, which although not in your case, could result in you losing data. This is called a narrowing conversion.
Integers can hold 32 bits of data, while shorts can only hold 16 bits of data. So, for example (in reality the numbers would be much bigger), an int had a value equal to 50, and you then cast it to a short, the data would be cut to "5" because a short doesn't have a large enough memory allocation.
That code you posted won't work because when you define the short like follows:
short a = 5;
You're directly creating a short, and the number is small enough that the short can hold it. When you type "5" alone as a method argument, it's handled as an integer, and the JVM doesn't know that it's small and safe to make a short. To make the "5" suitable as an argument for the method, you need to turn it into a short using a narrowing conversion, as follows:
setNum((short) 5);
But as stated, if you don't actually know the value of the int, and you're not sure it's small enough to be turned into a short, this can create errors in your code as some of the number will be chopped off.
(Here is some Oracle documentation on this)

short assigned (final int) works but passing (final int) as parameter to short argument doesnt work [duplicate]

Why this is giving the compile time error?
2 is constant at compile time, therefore narrowing should be allowed here since 2 is in range of byte.
public class Test {
public static void main(String[] args) {
ForTest test=new ForTest();
test.sum(1, 2); //compile time error here
}
}
class ForTest
{
public int sum(int a,byte b)
{
System.out.println("method byte");
return a+b;
}
}
The error is:
The method sum(int,byte) in the type ForTest is not applicable for the arguements (int,int).
Edit: I think the answer lies here: http://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html#jls-5.3 but I am not getting it :(
You have to distinguish between assignment conversion and method invocation conversion.
Narrowing Primitive Conversion
First of all, look at JLS §5.1.3:
22 specific conversions on primitive types are called the narrowing primitive conversions:
[...]
int to byte, short, or char
[...]
Note, that this only explains the mechanism but not the places where such conversions are allowed or not.
Assignment Conversion
Next, look at JLS §5.2:
[...]
In addition, if the expression is a constant expression (§15.28) of type byte, short, char, or int:
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.
[...]
This clearly describes that in the assignment byte b = 2 the narrowing conversion from type int to type byte is allowed.
Method Invocation Conversion
However, when reading JLS §5.3 you will not read anything about a narrowing conversion. So the compiler is doing a correct job.
This is because 2 is interpreted as an int, which cannot be implicitly converted into a byte.
Your choices are either to change the method signature to (int a, int b), or to explicitly do a casting: test.sum(1, (byte)2)
You can't type cast int to byte implicitly.
Call method as:
sum(1, (byte)2);
Revised answer:
The java language specification says that when looking for candidate matches for function calls, after the candidates have been found based upon the name of the function, and the number of actual vs. formal parameters, it eventually arrives at phase 2.
"If m is not a generic method, then m is applicable by loose invocation if, for 1 ≤ i ≤ n, either ei is compatible in a loose invocation context with Fi or ei is not pertinent to applicability."
Evidently, when it arrive at the second actual parameter, it is deciding that an 'int' is not compatible in a 'loose invocation context' with 'byte'. Therefore it rejects the one and only matching candidate it found in phase 1.
I'm unable to find a formal definition of "loose invocation" in the JLS.
Thanks to VikasMangal and KisHanarsecHaGajjar for pointing out the stupidity of my original answer which is repeated below for eternal shame.
My original answers the comments refer to is below.
Java spec section 5.1.3 says you should be able to do this.
Additionally in section 5.2 on assignment of constants it says
"In addition, if the expression is a constant expression (§15.28) of type byte, short, char, or int:
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."
However, the compiler seems to be going it's own way.
Since Narrowing Primitive Conversions(§5.1.3) says that narrowing of int is done into byte, short, or char. So you need to cast the int to byte where you calling the method. Like this :
test.sum(1, (byte)2);
You can achieve more with the primitive integer. Thus, everytime you write a value that can be an "integer" value, Java automatically wants to cast and thus treat it like an integer.
The converse of this is what happens when you try what you tried here. Java sees a value which can fit as an integer but for it to treat it as a byte, you need to explicitly tell. It's a bit like saying, OK, I know what I am doing and I do realize that casting a number to a byte has its limitations but I want you to do it anyway.

What's the difference between 'short s = 1' and '(short) 1' in Java?

I meet a method invocation which the parameter type is short, such as
foo (short s){...}
When I call it, I think of the two solution:
one is:
foo((short) 1);
and another:
short s = 1;
foo(s);
What's the difference between them, and which is better?
1 will be by default treated as an int in Java.
So, when you're doing ((short)1), the you're passing 1 as a short parameter through the function. The receiving argument should be of a short type.
Whereas, when you have short s =1, then it is obviously a short integer.
Remember that in both the case, shorts will be converted to int(this is called type promotion) while performing operations. And, if operated with double operands, those int's will get promoted to double.
What's the difference between them, and which is better?
Both are doing the same operations finally(passing a short variable as an argument), and both of them are of equally better. But, you should prevent using short integers, unless extremely required as it has some shortcomings(causes compile-time errors when you accidentally try to store an int/long to a short).
short s = 1 is defining a variable as a short with a value of 1.
(short) 1 is casting an int value of 1 to the primitive datatype short.
There is no difference. They both create a short with a value of 1.
See Java: Primitive Data Types for information about a short and its value range, though, as setting a short to a value that is above or below its prescribed range will throw an exception.
Both solutions will work, and performance differences between the two will be hardly noticable.
(short) 1; uses a literal, which gets casted to a short that is passed in as a parameter for the function foo.
short s = 1; actually creates a variable in memory prior to passing the value as a parameter, in case you want to also use the variable for other things.
In java up cast from short to int automatically happens.
But for down casting you have to manually cast it.
short(integer_value)
(short) 1
If you call using casting you can send integer parameter too.

Short object creation

I had a look to this post Java instantiate Short object in Java but did not exactly respond to what I am looking for.
Does anybody know why the first line (//1) gives an error whereas the second line (//2) does not
Short s = new Short(4);//1
short s1 = 4;//2 Here I know why it works it gets
//implicitly narrow converted into a short.
As stated in the code, I understand why the second line works fine, but what about the first line? Where is the sense of writing Short s = new Short((short)4);
Bottom line: why does it not cast it implicitly? It is a constant not a variable.
Thanks in advance.
You could define a constructor taking a 'short' and another taking an 'int' and language semantics would make your call to the constructor ambiguous. So you need to use strict type. Also, since Short is final, try using Short.valueOf((short) 4) to avoid unnecessary object allocation.
This is caused because Java use "search mechanizm" to find a constructor for int type. When you assing value to varialbe its type is defined and compiler can optimize the code. With constructor (or any other method) this is not possible.
When you say
Short s = new Short(4);
you are specifying an integer literal 4, not a short literal, and this is not permitted under the Java language specification for Method Invocation Conversion.
However, when you say
short s1 = 4;
this is subject to what the Java Language Specification calls a Narrowing Primitive Conversion, which is permitted even if it would lose precision.
You should do like below :
Short s = new Short((short) 4);
Reason :
Type of 4 is Integer/int and NOT Short. So you need to do explicit typecast to ensure that Short constructor takes compatible type.
It's just not supported.
With the other number object types, there is a literal for the value, numbers are int by default, but for long you have the L suffix, double has d and so on.
If you were to have new Short(int) throw an exception, then the constructor would be inconsistent with the other number constructors.
Trying to create a short primitive that is too big is tested at compile time, the same with byte.
If you did this:
byte b = 1000000;
Your code would not compile, rather than throw an exception.
So the behaviour is consistent when you take the compiler into account.

type casting required on function calls taking short datatype variable as a parameter

Whenever I use byte or short data type as a method parameter, on method calls, I am required to explicitly cast the values I pass to those methods.
To better explain:
void foo(short x)
{}
void main() {foo((short)32);}
If I dont use short here then warning is generated.
method foo in class px cannot be applied to given types
required: byte
found: int
How can I get it better?
No way out.
Java doesn't have a way to code byte or short literals. Any number literal is an int value and converting an int to a short without casting always creates a warning.
Integer literals implicitly have the type int, and converting from an int to byte or short potentially loses information, so it requires explicit casting.
So don't use byte or short unless you really need to, which is very rarely the case.

Categories

Resources