When you learn Java or any other programming language you usually start by looking at the basics of the type system and how the arithmetic operations work. You learn how numbers are represented and what types of numbers your programming language offers to you. At the same time, one of the first rules that you learn in Java is that implicit casting only occurs when you are doing a “safe” conversion, otherwise you need to explicitly tell your compiler that you understand what you are doing. You will often read things like
"In Java, only safe implicit conversions are performed: upcasts and promotion."
Source: StackOverflow
"Implicit type casting occurs only in the case of widening conversion or up casting."
Source: Learn2Geek
So, let’s focus on a non trivial and non safe conversion. Let’s say that
we have an expression with type double and we want to assign the result
to a long variable. There is no way the compiler can figure out
automagically, in a safe way, how to convert a decimal number into a
integer number without potentially losing some information. That’s
clear, right?
"A conversion from type double to type long requires a non-trivial translation from a 64-bit floating-point value to the 64-bit integer representation. Depending on the actual run-time value, information may be lost."
Source: Java SE 8 Specification
So, the following piece of code will not compile in Java, as you will expect:
double a = 0.33;
long b = 10;
b = a * b;
The reason why this is not going to compile is because when you do
(b = a * b)
you are losing precision and that requires an explicit
cast.
The trap
We have also learnt that you can write b = a * b
in a more concise way
by writing b *= a
. The operator *=
is a Compound Assignment
Operators and most of the people (including me) understand that as a
nice “syntactic sugar”. So let’s try to rewrite our fragment using the
operator *= .
double a = 0.33;
long b = 10;
b *= a;
Did you see what happened? OMG! The code just compiles! It does not just compile, it is also not going to fail in runtime and is going to lose precision silently.
Looking for an explanation
As my good colleague Siam Rafiee pointed out to me, the Java 8 SE Specification explain why that happens. If you look at the section 15.26.2. Compound Assignment Operators you will find a clear explanation of how the operator *= is going to be evaluated…
E1 op= E2 is equivalent to E1 = (T) ((E1) op (E2)), where T is the type of E1, except that E1 is evaluated only once
That means that what we have asked the compiler to do by using the *=
operator is basically
b = ((long)(a * b));
So, if you were not yet aware, be very careful with the primitive Java type system. Happy coding!