As we all know, there is no system that works perfectly… never mind, Java is that kind of system too. Every system has his own crutches, errors and unexpected shutdowns (sometimes without explaining the reason, yeah, iPhone users, I am talking about you!).
Today we will talk about Java pitfalls, corner cases and traps.
To be honest, I was inspired by the Joshua Bloch & Neal Gafter book to write this article, Java Puzzlers: Traps, Pitfalls, and Corner Cases. I was reading it and got an idea that this can be not only interesting to read, but also worth sharing.
Oddity check
The first situation that I would like to explain, it’s about the oddity check.
How do we usually check if a number is odd? Easy, if the division by 2 gives us a remainder of 1, then the number is odd, isn’t it? Below is the method that does this.
[code language=”java” firstline=”0″]
public static boolean isOdd(int i){
return i % 2 == 1;
}
public static void main(String[] args){
System.out.println(isOdd(11));
System.out.println(isOdd(-11));
}
[/code]
After running this code, here’s what we will see in the output:
[code language=”java” firstline=”0″]
>>>> true
>>>> false
[/code]
Wait, what?
Unfortunately, it doesn’t work as we expected it to. This method returns a wrong answer one quarter of the time. Why one quarter? Because half of all int values are negative, and the isOdd
method fails for all negative odd values. It returns false when invoked on any negative value, whether even or odd.
Let’s dig deeper, how does Java remainder operator work?
a - ( (a/b) * b ) == a
Okay. How can we fix it? Let’s see…
[code language=”java” firstline=”0″]
public static boolean isOdd(int i){
return i % 2 != 0;
}
[/code]
Now, running that code again will give us the following output:
[code language=”java” firstline=”0″]
>>>> true
>>>> true
[/code]
It’s fixed, we are brilliant!
Decimal place accuracy
All right, let’s move on! Imagine you are on your way home from a volleyball game and suddenly one of your car’s headlights burned out. Sad. You stop at the closest gas station to buy a new one. Luckily they cost only $1.10 and you got $2 bill. So you expect to get your 90 cents back.
[code language=”java” firstline=”0″]
public static void main(String[] args){
System.out.println(2.00 – 1.10);
}
[/code]
After executing the code above we get this:
[code language=”java” firstline=”0″]
>>>> 0.8999999999999999
[/code]
Hmm, it doesn’t look good? You were too naive to expect 0.90 in this article. Anyway, how could it know that you wanted two digits after the decimal point?
If you would’ve read about the rules of converting double values to strings, which are specified in the documentation of Double.toString()
method, you would know that the program prints the shortest decimal fraction sufficient to distinguish the double value from its nearest neighbor, with at least one digit before and after the decimal point. The problem is that the number 1.1 can’t be represented exactly as a double, so it is represented by the closest double value. More generally, the problem is that not all decimals can be represented accurately using binary floating-point.
One of the solutions is to use printf
:
[code language=”java” firstline=”0″]
System.out.printf("%.2f%n", 2.00 – 1.10);
[/code]
Another option would be to make use of BigDecimal
s:
[code language=”java” firstline=”0″]
System.out.println(new BigDecimal("2.00").
subtract(new BigDecimal("1.10")));
[/code]
Regardless of what you will choose, you’ll get your 0.90 cents.
Division of large numbers
Next case! Have you ever had to divide two really big numbers, for instance to divide the amount of microseconds in a day by the amount of milliseconds in a day? I guess not, but imagine there is a task to code that or you are doing it just for fun.
As far as we have already covered two pitfalls, we are sure that we’ll do it right this time. At least we hope so…
We write a nice, clean and really straightforward code.
[code language=”java” firstline=”0″]
public static void main(String[] args){
final long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000;
final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;
System.out.println(MICROS_PER_DAY / MILLIS_PER_DAY);
}
[/code]
The output that we are expecting is 24 hours/day · 60 minutes/hour · 60 seconds/minute · 1,000 milliseconds/second · 1,000 microseconds/millisecond divided by amount of miliseconds. So, we are expecting 1000 but it prints…
[code language=”java” firstline=”0″]
>>>> 5
[/code]
Again???
So, what exactly went wrong? The problem is that MICROS PER DAY
does overflow. We are multiplying all int
values.
How can we fix it?
[code language=”java” firstline=”0″]
public static void main(String[] args){
final long MICROS_PER_DAY = 24L * 60 * 60 * 1000 * 1000;
final long MILLIS_PER_DAY = 24L * 60 * 60 * 1000;
System.out.println(MICROS_PER_DAY / MILLIS_PER_DAY);
}
>>>> 1000
[/code]
Yeah, got it now (notice the L next to 24).
String concatenation
Here’s another situation, tricky but not less interesting – it’s all about concatenation!
Let’s say we would like to check if the length of two strings is equal (why not?) Okay, now we feel really confident, we know about AT LEAST four Java traps. Let’s do it!
[code language=”java” firstline=”0″]
public static void main(String[] args){
final String firstString= "length: 10";
final String secondString= "length: " + firstString.length();
System.out.println("Strings are equal: " + firstString == secondString);
}
[/code]
What are we expecting?
[code language=”java” firstline=”0″]
>>>> Strings are equal: false
[/code]
Of course they are not, but wait…
[code language=”java” firstline=”0″]
>>>> false
[/code]
What’s wrong this time? The +
operator, whether used for addition or string concatenation, binds more tightly than the ==
operator.
So, the rule for string concatenation is : “always parenthesize non-trivial operands”.
How to fix it? Check it out!
[code language=”java” firstline=”0″]
System.out.println("Strings are equal: " + (firstString == secondString));
>>>> Strings are equal: false
[/code]
We did it again!
Working with Integer.MAX_VALUE
Do you remember what Integer’s max value is? Or maybe something around it? So you are being really careful right now writing a smart piece of code that will find this number, here we go:
[code language=”java” firstline=”0″]
public static final int END = Integer.MAX_VALUE;
public static final int START = END – 10;
public static void main(String[] args){
for(int i = START; i <= END; i++)
System.out.println(i);
}
[/code]
And what do we expect? Right – 2147483638, 2147483639, 2147483640 etc.!
But… it looks like this code won’t stop and will loop forever or until you won’t kill it. What’s the problem? The thing is that the loop continues as long as the loop index i
is less than or equal to Integer.MAX_VALUE
, but all int
variables are always less than or equal to Integer.MAX_VALUE
. It is, after all, defined to be the highest int
value in Java. When it reaches Integer.MAX_VALUE
and performs the incrementation, it silently wraps around to Integer.MIN_VALUE
.
How can we fix it? Take a look:
[code language=”java” firstline=”0″]
for(long i = START; i <= END; i++)
System.out.println(i);
>>>> 2147483638
2147483639
2147483640
2147483641
2147483642
2147483643
2147483644
2147483645
2147483646
2147483647
[/code]
Amazing, we are awesome!
Now you know that you have to be really careful and check your code several times before saying with certainty that it works correctly. Sometimes these little hidden “bugs” might make your program work unexpectedly and cause you trouble later on.
Hope you enjoyed it!