New Java programmers are often confused by compilation error messages like:
\"incompatible types: possible lossy conversion from double to int\"
<
First of all, this is a compilation error. If you ever see it in an exception message at runtime, it is because you have have run a program with compilation errors1.
The general form of the message is this:
"incompatible types: possible lossy conversion from
to
"
where
and
are both primitive numeric types; i.e. one of byte
, char
, short
, int
, long
, float
or double
.
This error happens when your code attempts to do an implicit conversion from
to
but the conversion could be lossy.
In the example in the question:
int squareRoot = Math.sqrt(i);
the sqrt
method produces a double
, but a conversion from double
to int
is potentially lossy.
Well lets look at a couple of examples.
A conversion of a long
to an int
is a potentially lossy conversion because there are long
values that do not have a corresponding int
value. For example, any long
value that is greater than 2^31 - 1 is too large to be represented as an int
. Similarly, any number less than -2^31 is too small.
A conversion of an int
to a long
is NOT lossy conversion because every int
value has a corresponding long
value.
A conversion of a float
to an long
is a potentially lossy conversion because there float
values that are too large or too small to represent as long
values.
A conversion of an long
to a float
is NOT lossy conversion because every long
value has a corresponding float
value. (The converted value may be less precise, but "lossiness" doesn't mean that ... in this context.)
These are all the conversions that are potentially lossy:
short
to byte
or char
char
to byte
or short
int
to byte
, short
or char
long
to byte
, short
, char
or int
float
to byte
, short
, char
, int
or long
double
to byte
, short
, char
, int
, long
or float
.The way to make the compilation error go away is to add a typecast. For example;
int i = 47;
int squareRoot = Math.sqrt(i); // compilation error!
becomes
int i = 47;
int squareRoot = (int) Math.sqrt(i); // no compilation error
But is that really a fix? Consider that the square root of 47
is 6.8556546004
... but squareRoot
will get the value 6
. (The conversion will truncate, not round.)
And what about this?
byte b = (int) 512;
That results in b
getting the value 0
. Converting from a larger int type to a smaller int type is done by masking out the high order bits, and the low-order 8 bits of 512
are all zero.
In short, you should not simply add a typecast, because it might not do the correct thing for your application.
Instead, you need to understand why your code needs to do a conversion:
be a different type, so that a lossy conversion isn't needed here?First example:
for (double d = 0; d < 10.0; d += 1.0) {
System.out.println(array[d]); // <<-- possible lossy conversion
}
The problem here is that array index value must be int
. So d
has to be converted from double
to int
. In general, using a floating point value as an index doesn't make sense. Either someone is under the impression that Java arrays work like (say) Python dictionaries, or they have overlooked the fact that floating-point arithmetic is often inexact.
The solution is to rewrite the code to avoid using a floating point value as an array index. (Adding a type cast is probably an incorrect solution.)
Second example:
for (long l = 0; l < 10; l++) {
System.out.println(array[l]); // <<-- possible lossy conversion
}
This is a variation of the previous problem, and the solution is the same. The difference is that the root cause is that Java arrays are limited to 32 bit indexes. If you want an "array like" data structure which has more than 231 - 1 elements, you need to define or find a class to do it.
Consider this:
public class User {
String name;
short age;
int height;
public User(String name, short age, int height) {
this.name = name;
this.age = age;
this.height = height;
}
public static void main(String[] args) {
User user1 = new User("Dan", 20, 190);
}
}
Compiling the above with Java 11 gives the following:
$ javac -Xdiags:verbose User.java
User.java:20: error: constructor User in class User cannot be applied to given types;
User user1 = new User("Dan", 20, 190);
^
required: String,short,int
found: String,int,int
reason: argument mismatch; possible lossy conversion from int to short
1 error
The problem is that the literal 20
is an int
, and the corresponding parameter in the constructor is declared as a short
. Converting an int
to a short
is lossy.
Consider this:
int a = 21;
byte b1 = a; // <<-- possible lossy conversion
byte b2 = 21; // OK
What is going on? Why is one version allowed but the other one isn't? (After all they "do" the same thing!)
First of all, the JLS states that 21
is an numeric literal whose type is int
. (There are no byte
or short
literals.) So in both cases we are assigning an int
to a byte
.
In the first case, the reason for the error is that not all int
values will fit into a byte
.
In the second case, the compiler knows that 21
is a value that will always fit into a byte
.
The technical explanation is that in an assignment context, it is permissible to perform a primitive narrowing conversion to a byte
, char
or short
if the following are all true:
byte
, short
, char
or int
.Note that this only applies with assignment statements, or more technically in assignment contexts. Thus:
Byte b4 = new Byte(21); // incorrect
gives a compilation error.
1 - For instance, the Eclipse IDE has an option which allows you to ignore compilation errors and run the code anyway. If you select this, the IDE's compiler will create a .class
file where the method with the error will throw an unchecked exception if it is called. The exception message will mention the compilation error message.