Friday, 19 April 2013

Infamous Java bugs and pitfalls

In year 2000, I was in university and was on the verge of picking a language to build my career on. Java was not yet the mainstream but highly popular and on the spots. Applets were (not yet broken) fancy, shiny when compared to static html pages. Swing was not a bad choice to build desktop apps. J2EE was rising and getting attention.

It has been 13 years since then and Java went mainstream although applets failed miserably, not really considered for desktop apps and J2EE was too complicated to even build simple stuff but still nothing stopped Java from being the most popular programming language.

It was no surprise, Java is beatiful, type safe and easy to learn language. There were many very good implemantation details in Java such the garbage collector, strings (finalized class), collections which offer great implemantations of merge and quicksort, built in hashcode methods and many more. However, still Java is far from being perfect and may introduce some unexpected behaviour.

The abs bug:
Well this is a very minor flaw but there is a probability that Math.abs() function may return negative value. Weird? actually simple, Java integers can get a value between -2,147,483,648 to 2,147,483,647 which clearly shows -2,147,483,648 can not be represented in positive.
So is this a bug? Well the expected value is positive so yes definitely but in the end this is actually an overflow. So how to fix it? One way would be checking Integer.MIN_VALUE before using abs function or using bit operators to manipulate negative sign instead.

Autoboxing the primitives pitfalls:
Autoboxing makes it easy to work with primitive types and their object counterpart. However moving between them may introduce some unexpected behavior. For example Integer i1=6 can not be compared to Integer i2=6 with == operator where int i3=6 can be compared to those with ==. However using equals may not work as expected too. For example Long x=0L; returns true when x.equals(0L) but returns false when x.equals(0). Weird? Not really since x is long where 0 (without L) is int. So those are not even same object types. Also using primitive types with collections may result unexpected behavior. Finally Autoboxing may result problems in overloading. Lets say we have Integer i=6 and call method sum(i); and we have two sum methods like; sum(long val) and sum(Long val). Which one do you think will be called? Again reasonable but not expected to see at first look and may lead problems in your app.

BigDecimal constructor bug:
If you haven't already check Java Puzzlers from Joshua Bloch. If you create two Big decimals using double constructor (x1=new BigDecimal(2.00) and x2=new BigDecimal (1.10)) and use subtract (x1.subtract(x2)) you will end up with 0.8999999999. The double constructor of BigDecimal doesn't work as expected and string constructor needs to be used instead (new BigDecimal("2.00")). This might be a serious problem since BigDecimal is widely used for money calculations!

System.out.println pitfall:
println() is one of the first functions that is tought to cs students. It is easy and used often. Usually quite ok to use when you are trying some logic or debugging some values. However System.out is synchronized so acquires a lock when accessed. So using println can cause your app to run in synchronized context which actually means threads will be blocked when accessing println. Imagine a webserver and an app logging with println and you will end up with thread locks and each request waiting for other. So println is ok and useful but not for real apps and logging!

Map bug:
Again take a look at Java Puzzlers from Joshua Bloch, the fifth puzzle(size matters) introduces a strange behavior between an HashMap and an EnumMap where with same values, one map has a size of 2 where the other is 1. Several Map implementations such as IdentityHashMap, EnumMap may introduce this behavior.
Is this a bug? Well certainly we expect same principles from map implementations but Bloch describes that as the spec was not clear at the time.

Cpu Number bug:
This may not be a huge problem unless you really rely on hardware. To get available processor count Java offers Runtime.getRuntime().availableProcessors() method which returns an int number as the number of the processors available. However you may end up getting unexpected numbers if you give a try. For example on my quad-core i7, I get 8. So this method does not return the number of hardware cpus nor the number of cores but the number of execution engines (virtual cores). In my case because quad-core i7s support hyper treading it actually acts like it has eight cores.
So is this a bug? Definitely not since the hardware and the OS acts as if they have that number of physical cpus but still be careful counting if you rely on solid hardware.

Generic arrays
In Java arrays are created as follows, int[] arr=new int[5]; so if you have a generic type of T, you would expect to create a generic array this way: T[]=new T[5]; but simply you can't. Java does not allow generic array creation and this is actually because generics are implemented in Java using Erasure. Generics are implemented purely in compiler level and actually only one class file is generated for each class. So to create the array we need an ugly cast as follows, T[]=(T[]) new Object[5]; and when you try to compile, the compiler will issue a warning that you are doing an unsafe cast!
Of course this is not bug, it is just an implementation problem given for the sake of simplicity and compability when generics were implemented. But raising a compiler warning on a design issue may confuse someone who faced it for the first time.

So this is definitely not the end of the list but still Java offers a beatiful syntax, type safety and a realiable easy to learn language. And finally no language or implementation is perfect!

2 comments:

  1. I didn't understand why the Map bug happens. It's not clearly explained in the slides.

    I kind of expected the BigDecimal problem. It's not BigDecimal's constructor fault, since when it starts running all it gets is a double with its intrinsic lack of precision. Passing a String allows the constructor to "see" exactly what digits you wanted.

    And the someLong.equals(0)... Ouch! That could bite us, since we are using lots of Longs in our database layer... I guess I will adopt the convention of writing always someLong.longValue() == 0L

    ReplyDelete
  2. Oh, one more thing:
    java.util.logging.SimpleFormatter.format and StreamHandler.publish are also synchronized so you would need something else if you want to avoid synchronization when outputting your log messages. Anyway, there's got to be synchronization somewhere unless you like to have your log messa like this ges. Or am I missing something?

    ReplyDelete