I have a question about the promotion of primitive types in Java. As we can see in the following example, one of the methods does not compile due to an error of type mismatch. Each method returns the same value but in different types.
The version of primitive long method works without error while the version of wrapper class Long fails. This is because the int literal in the return statement will be first promoted to a broader primitive type (e.g. long) and then to the corresponding wrapper class Integer and so on. Since Integer is not a subclass of Long the compiler gives an error.
But why does the version of wrapper class Byte works without any error? What exactly does the compiler do at this point?
long getPrimitiveLong() {
return 12; // valid
}
Long getWrapperLong() {
return 12; // Error: type mismatch
}
Byte getWrapperByte() {
return 12; // valid
}
The version with Byte works through some compiler magic.
Unlike long numeric literals which can be constructed with a L suffix, e.g. 12L, there is no such thing as a byte literal. That is why Java compiler treats numeric literals that fit in a byte as byte literals. Hence, 12 in your last example is considered a constant of type byte.
Java Language Specification offers a description of this conversion in section 5.2:
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 is because Java allows 1 conversion or Autoboxing, not more.
Java can do all these:
int i = 5;
double d = i; // int to double
long l = i; // int to long
long l = d; // double to long
Or autobox:
Integer i = 5; // int to Integer
Double d = 5.0; // double to Double
Long l = 5L; // long to Long
Converting twice, say int to Double, gives Java a hard time.
number like 12 consider as int by default by the compiler that is why the error
To fix that you can use casting for byte and place L after the value of long variable.
Read following post for more details
http://javaseeeedu.blogspot.com/2015/12/casting-part-1.html
As a short answer - try to replace 12 with 128 (byte is in range -128 to 127).
It won't compile, right?
The outcome here is that the compiler knows about byte boundaries.
For the in-depth answer you can do a deep dive into OpenJDK.
Related
If I write something like this
System.out.println(18);
Which type has the '18'?
Is it int or byte?
Or doesn't it have a type yet?
It can't be int, because something like this is correct:
byte b = 3;
And this is incorrect:
int i = 3;
byte bb = i; //error!
EDIT:
I think I found the right part in the spec at Assignment Conversion :
The compile-time narrowing of constants means that code such as:
byte theAnswer = 42;
is allowed. Without the narrowing, the fact that the integer literal 42 has type int would mean that a cast to byte would be required:
byte theAnswer = (byte) 42; // cast is permitted but not required
This
18
is known as an integer literal. There are all sorts of literals, floating point, String, character, etc.
In the following,
byte b = 3;
the literal 3 is an integer literal. It's also a constant expression. And since Java can tell that 3 fits in a byte, it can safely apply a narrowing primitive conversion and store the result in a byte variable.
In this
int i = 3;
byte bb = i; //error!
the literal 3 is a constant expression, but the variable i is not. The compiler simply decides that i is not a constant expression and therefore doesn't go out of its way to figure out its value, a conversion to byte may lose information (how to convert 12345 to a byte?) and should therefore not be allowed. You can override this behavior by making i a constant variable
final int i = 3;
byte bb = i; // no error!
or by specifying an explicit cast
int i = 3;
byte bb = (byte) i; // no error!
The JLS-4.2.1 - Integral Types and Values
The values of the integral types are integers in the following ranges:
For byte, from -128 to 127, inclusive
For short, from -32768 to 32767, inclusive
For int, from -2147483648 to 2147483647, inclusive
For long, from -9223372036854775808 to 9223372036854775807, inclusive
For char, from '\u0000' to '\uffff' inclusive, that is, from 0 to 65535
And JLS-3.10.1 - Integer Literals
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).
Finally, JLS-3.10.2 - Floating-Point Literals includes
A floating-point literal is of type float if it is suffixed with an ASCII letter F or f; otherwise its type is double and it can optionally be suffixed with an ASCII letter D or d (§4.2.3).
As for byte b = 3; it is a Narrowing Conversion from int to byte.
As stated by others, it's an integer literal.
Under the hood, it appears Java was written for 32 bit processors.
An integer is between +/- ~2.147 million, which is 2^31 with a signage bit.
So whether you're writing a bit or a full int, it's a single processor function, therefore a single memory allocation.
Looking at the Java Doc for Long, it only has two constructors:
1) primitive long as param --> new Long(10L);
2) String as param --> new Long("23");
But this works
new Long(23);
But if the literal is more than the int MAX VALUE (2147483647), the L suffix becomes mandatory so this: (
new Long(2147483648) will now require an L after the value
However:
new Long(Integer.MAX_VALUE + 1) is OK
Who can explain this?
The first part of your question is straightforward: passing int where long is required is allowed because any number that can be represented as an int can also be represented as a long. This is a so-called widening conversion.
The second part, about new Long(Integer.MAX_VALUE + 1), is trickier. Although the widening conversion occurs here as well, it is not of the value that one might think: if you run this program
Long x = new Long(Integer.MAX_VALUE + 1);
System.out.println(x);
you get -2147483648, not the expected 2147483648, because of int overflowing on addition (demo).
The compiler is not smart enough to promote parts of the expression to long before performing addition. Widening conversion occurs after the addition has been performed, on the result of the addition with an overflow.
You can find the answer in javadocs:
A widening primitive conversion from an integral type to another integral type, or from float to double in a strictfp expression (§15.4), does not lose any information at all; the numeric value is preserved exactly.
The integer value 23 can be converted to long 23L without loss of any information at all. Thus with the use of through widening primitive conversions, JVM implicitly converts it to 23L.
So when you call
new Long(23)
It becomes
new Long(23L)
When we write new Long(23) , you can think of it as something like the following happening :
int i = 23;
long l = i; // widening primitive conversion
Long newLong = new Long(l);
Widening conversions are allowed as per the JLS 5.1.2 , so there are no issues with it.
When we try instead , new Long(2147483648) , the first step itself fails, as the value is more than what can fit in an int
int i = 2147483648; // doesn't fit in int
When we try new Long(Integer.MAX_VALUE + 1), you can think of it as something like the following happening:
int i = Integer.MAX_VALUE + 1; // This gives the INTEGER.MIN_VALUE -2147483648
long l = i ; // widening primitive conversion
Long newLong = new Long(l);
So, this is allowed. Hope this helps.
The differences come from using literal or not.
By using a literal,
long1 = new Long(2147483648)
The code will not compile as the compiler checks the validity of the literal again the type of the variable that receives the literal value and 2147483648 is out of range for an int. So the compiler emits an error.
It is valid for any literal.
As the compiler expects to have a specific type as value of the literals, so it does the check :
The type of a literal is determined as follows:
The type of an integer literal (§3.10.1) that ends with L or l is long
(§4.2.1).
The type of any other integer literal is int (§4.2.1).
...
By using a computation :
long1 = new Long(Integer.MAX_VALUE + 1);
the compiler doesn't check if the size of the Long argument constructor is in the int range.
It is not a literal declaration.
At runtime, an int to long widening primitive conversion occurs.
It produces so a int with Integer.MAX_VALUE + 1 that is -2147483648 (overflow of int) that is converted to a long value.
You cannot convert from int to char, so this would be illegal
int i = 88; char c = i;,
However this is allowed char c = 88;.
Isn't a plain number and int literal? How is this allowed?
char is effectively an unsigned 16-bit integer type in Java.
Like other integer types, you can perform an assignment conversion from an integer constant to any integer type so long as it's in the appropriate range. That's why
byte b = 10;
works too.
From the JLS, section 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.
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.
Actually, converting from int to char is legal, it just requires an explicit cast because it can potentially lose data:
int i = 88;
char c = (char) i;
However, with the literal, the compiler knows whether it will fit into a char without losing data and only complains when you use a literal that is too big to fit into a char:
char c = 70000; // compiler error
Its because the literals for integer or smaller than int as byte ,short and char is int. Understand the following in this way.
code:
byte a = 10;//compile fine
byte b= 11;//compile fine
byte c = a+b;//compiler error[says that result of **a+b** is **int**]
the same happens for any mathematical operations as of 'Divide', 'multiply', and other arithmetic operation. so cast the result to get the literal in desired data type
byte c = (byte)(a+b);
So that the same reason why the value int need to have primitive cast to change the value in char.
Hope this make some sense.
I have the following code snippet.
public static void main(String[] args) {
short a = 4;
short b = 5;
short c = 5 + 4;
short d = a;
short e = a + b; // does not compile (expression treated as int)
short z = 32767;
short z_ = 32768; // does not compile (out of range)
test(a);
test(7); // does not compile (not applicable for arg int)
}
public static void test(short x) { }
Is the following summary correct (with regard to only the example above using short)?
direct initializations without casting is only possible using literals or single variables (as long as the value is in the range of the declared type)
if the rhs of an assignment deals with expressions using variables, casting is necessary
But why exactly do I need to cast the argument of the second method call taking into account the previous summary?
These are the relevant JLS sections:
JLS 5.1.1 Identity Conversion
A conversion from a type to that same type is permitted for any type.
JLS 5.2 Assignment Conversion
Assignment conversion occurs when the value of an expression is assigned to a variable: the type of the expression must be converted to the type of the variable. Assignment contexts allow the use of one of the following:
Identity conversion
[...]
In addition, if the expression is a constant expression 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.
The above rules explain all of the following:
short a = 4; // representable constant
short b = 5; // representable constant
short c = 5 + 4; // representable constant
short d = a; // identity conversion
short e = a + b; // DOES NOT COMPILE! Result of addition is int
short z = 32767; // representable constant
short z_ = 32768; // DOES NOT COMPILE! Unrepresentable constant
As to why this doesn't compile:
test(7); // DOES NOT COMPILE! There's no test(int) method!
It's because the narrowing conversion with constant is only defined for assignments; not for method invocation, which has entirely different rules.
JLS 5.3. Method Invocation Conversion
Method invocation conversions specifically do not include the implicit narrowing of integer constants which is part of assignment conversion. The designers of the Java programming language felt that including these implicit narrowing conversions would add additional complexity to the overloaded method matching resolution process.
Instead of explaining how method resolution works precisely, I will just quote Effective Java 2nd Edition, Item 41: Use overloading judiciously:
The rules that determine which overloading is selected are extremely complex. They take up thirty-three pages in the language specification, and few programmers understand all of their subtleties.
See also
Varying behavior for possible loss of precision
short x = 3; x += 4.6; compiles because of semantics of compound assignment
The result of an arithmetic operation on short values is always int. test(7) doesn't work, since you haven't said that 7 is of type short. The compiler should be a bit smarter here.
The '7' in the call test(7); is an int and will not be automatically converted to a short.
It works when you declare and initialize short values, but that's a special case for the compiler. This special case doesn't exist for method calls.
char c='c';
int i=10;
double d =50;
long l=30;
String s="Goodbye";
Are these statement valid?
s+=i;
i+=s;
c+=s;
c=c+i;
Can someone explain the logic of converting data types
Why don't you give it a try:
bash-3.2$ cat ConveraionTest.java
public class ConvertsonTest {
public static void main( String [] args ) {
char c='c';
int i=10;
double d =50;
long l=30;
String s="Goodbye";
//Are these statement valid?
s+=i;
i+=s;
c+=s;
c=c+i;
}
}
bash-3.2$ javac ConversionTest.java
ConversionTest.java:12: incompatible types
found : int
required: java.lang.String
i+=s;
^
ConversionTest.java:13: incompatible types
found : char
required: java.lang.String
c+=s;
^
ConversionTest.java:14: possible loss of precision
found : int
required: char
c=c+i;
^
3 errors
EDIT
Long history
Basically, all the types in java have a "shape" if you want to call it like that ( well I'm going to call it like that for this answer )
For the primitives data types ( boolean, byte, short, char, int, float, long, double ) the "shape" is the size in bytes it uses ( or in bits, here 1 byte = 8 bits ) :
boolean = true or false
byte = 8 bits
short = 16 bits
char = 16 bits
int = 32 bits
float = 32 bits
long = 64 bits
double = 64 bits
The "shape" of objects varies according to it class.
So, basically you can assign anything to anything as long as they fit in the "shape"
So you can assign an int to a long ( you can thing 32 bits fits into 64 bits ) a short(16) into a int(32) etc.
What you can't do is to assign something that doesn't fit in the shape.
So
ConversionTest.java:12: incompatible types
found : int
required: java.lang.String
i+=s;
^
You can't assign a String into an int. How would you? Where would the contents go? They are not of the same "shape", nor even a compatible one.
Same goes for String to char
ConversionTest.java:13: incompatible types
found : char
required: java.lang.String
c+=s;
^
Now, you might assign an int(32 bits) to a char(16 bits) or to a short(16 bits) The problem would be, that if the value holds > than 16 bits ( 131 071 for instance )
You would lose the bits that do not fit into 16 bits. That's why you get this error:
ConversionTest.java:14: possible loss of precision
found : int
required: char
c=c+i;
However if you are sure that it fits ( for instance int i = 65; which certainly fits into 16 bits ) you can cast it, like this:
int i = 65;
char c = ( char ) i;
Casting it the way you tell the compiler:
Hey I'm the programmer here, I know what I'm doing.
Yes, no, no, no (unless you explicitly perform a typecast). If you were to write up a simple main method, compile it, and execute it, you could have seen this - these problems should be identified by the compiler.
This page on Java primitive data types explains it pretty well.
char c='c';
int i=10;
double d =50;
long l=30;
String s="Goodbye";
s+=i; // legal :)
i+=s; // not legal :( The operator += is undefined for the argument types int, String
c+=s; // not legal :( The operator += is undefined for the argument types char, String
c=c+i; // not legal :( Type Mismatch: cannot convert from int to char
The complete explanation of Java data type conversions is long and detailed.
There are two types of conversions:widening conversions and narrowing conversions. Widening conversions are allowed and Java will handle it for you, but narrowing conversions are not allowed. Widening conversions mean that you are converting a "smaller" value such as int (32 bits) to a "larger" value such as long (62 bits). Narrowing conversions which go the other way will have to be done explicitly.
s+=i;
will require an int to be converted to a String which is allowed.
i+=s;
Will require a String to be converted to an int which is not allowed. The += operator will translate to
i = i + s;
and i + s will return a String which cannot be assigned to an int.
c+=s;
This cannot be allowed for a similar reason that c + s returns a String which you are trying to assign to a char.
c=c+i;
will also give an error because c + i will result in an int (32 bits) and assigning it to a char (16 bits) may cause loss of precision.
Each of the operations you try are actually possible but you have to explicitly tell Java that you want to do themn and will accept the consequences. Having said that mixed type operations are frowned upon in the totally pure hard nosed programming arena since there are edge cases that potentially cause problems.
s += i will concatenate s and string "10", this is equal to s += ((Integer)i).toString();
i += s don't think this will work, types are incompatible
c += s also shouldn't compile, same, incompatiple types.
c = c + i should add 10 to ascii value of c, to c will become 10th letter after 'c' => 'm', i guess
EDIT. So in last case you have to cast i to char to make it compile.