Java tricks and pitfalls

Marius Tanasoiu
METRO SYSTEMS Romania
7 min readMay 21, 2018

As my Java developer took off, I came across buggy code, which gave me and my colleagues a hard time in figuring out what the issue was.
I would like to share some of them here and, hopefully, ease your work. Needless to say, feedback is welcome and heard.

Beware of loops

Let me start with an easy example from “Java Puzzlers” by Joshua Bloch.

public class Warmup {
public static void main(String[] args) {
int END = Integer.MAX_VALUE;
int START = END - 100;
int count = 0;
for (int i = START; i <= END; i++) {
count++;
}
System.out.println(count);
}
}

Taking a look at the code snippet above, one would confidently state that the output is 101.
In fact, what we have here is a never-ending loop.
Let’s take a deeper look: what happens when i is 2147483647, that is Integer.MAX_VALUE value?
As we know, each for statement iteration does the following steps:
- Evaluates termination expression i <= END, in our case Integer.MAX_VALUE <= Integer.MAX_VALUE and the result is true
- Executes for statement which is count++
- Increment expression is invoked adding 1 to Integer.MAX_VALUE
At this step, i is silently wrapped around Integer.MIN_VALUE causing our for statement to loop endlessly.

In other words, Integer values behave like a loop. The next value after the highest one is the lowest; the previous of the lowest one is the highest.
This is a known behavior of Java, and could cause lots of issues. In our example, the loop is infinite, as the exit condition is never met.

Carefully Overloading

public class Overloading {   public static void main(String[] args) {
myMethod(123);
}
public static void myMethod(Integer p) {
System.out.println("Integer");
}
public static void myMethod(int p) {
System.out.println("int");
}
public static void myMethod(long p) {
System.out.println("long");
}
public static void myMethod(Integer... p) {
System.out.println("Integer ...");
}
public static void myMethod(int... p) {
System.out.println("int ...");
}
public static void myMethod(Object p) {
System.out.println("Object");
}
}

This is a classical overloading example in Java. The question is: which method is called?
Here, we are dealing with Widening, Autoboxing and methods with variable number of arguments.

To have a clear picture regarding the Java terminology, we have:
- Widening is the operation that converts a primitive into the next bigger primitive in which it can fit into.
- Autoboxing is the automatic conversion that the Java compiler makes between the primitive types and their corresponding object wrapper classes
- Var-args is a capability that allows the method to accept zero or more arguments

To solve the challenge, run the code, identify and comment the called method, and run it again. No question about the first run. As we know, each integer literal that does not end up in L or l is a primitive int type. So, the first run will print “int”.
With myMethod(int p) removed, the problem gets a little bit tricky. You might say that the next method to be called is myMethod(Integer p) or myMethod(int… p), but they aren’t. In fact, myMethod(long p) is called, meaning that widening is preferred rather than boxing or var-args. The reason is that widening exists since Java 1, while boxing and var-args are Java 5 capabilities and backward compatibility requirements had to be taken in consideration.
Coming back to our challenge, the next question is which method is called when myMethod(long p) is removed?
As Java oracle documentation says, autoboxing beats var-args, meaning that our third run will print Integer text. From here we can say that, when talking about overloading:
- Widening beats boxing
- Boxing beats var-args
Next, we will also remove myMethod(Integer p). What is the next print result? You might think that “int …” is printed. Running again the program, one will be surprised that “Object” word is printed instead. Why is this happening? Why not called var-args? Well, the answer lies in explaining how myMethod(Object p) gets to be invoked. First of all, a Boxing is made by wrapping int argument into an Integer then a widening is done from Integer to Object (widening can be done also with objects). From here we can say that boxing+widening beats var-args.
Our last question lies in choosing between myMethod(int… p) and myMethod(Integer… p) since they are the only ones left after removing myMethod(Integer p) . If you will use Eclipse or other IDE, which does the compilation automatically, you will notice that none of them is the answer. Instead you will get a compilation error:

Exception in thread “main” java.lang.Error: Unresolved compilation problem:
The method myMethod(Integer[]) is ambiguous for the type Overloading

The compiler cannot choose between two overloaded methods that use var-args as parameters since 123 literal parameter can be both either primitive int or Integer type.
What I learned from the above is to carefully overload methods in an existing code, especially when backward compatibility is a must.

Adventurous serialization

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
class Car {
public int wheelsNo;
public Car() {
wheelsNo = 4;
}
}
class Ford extends Car implements Serializable {
private static final long serialVersionUID = 4694853L;
public String color;
public Ford() {
this.wheelsNo = 5;
color = "White";
}
}
public class Serialize { public static void main(String[] args) {
Ford ford = new Ford();
ford.wheelsNo = 9;
ford.color = "Green";
serialize(ford);
Ford deserializedFord = deserialize();
System.out.println(deserializedFord.wheelsNo);
System.out.println(deserializedFord.color);
}
private static void serialize(Ford ford) {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("myObj"))) {
oos.writeObject(ford);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private static Ford deserialize() {
Ford ford = null;
try (ObjectInputStream oos = new ObjectInputStream(new FileInputStream("myObj"))) {
ford = (Ford) oos.readObject();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return ford;
}
}

At first glance, this is a classical and easy example for serializing and deserializing an object. Running the code, you notice that the answer is not as you might expected. I won’t talk about serialize and deserialize methods, since there is nothing wrong with them. But let’s have a closer look on what the code does inside main method.
To create a realistic scenario of this problem, let’s imagine a guy who wants to buy a new car. In fact, our problem includes the whole process, from production to delivery.
First, we declare ford variable of type Ford and instantiate it at the same time, much like the assembly process. Ford extends Car class whose default constructor will be called first, giving to wheelsNo class variable value 4. By default, any new car has 4 wheels. Anyway, the next code to be executed is the one inside Ford class constructor that changes wheelsNo value to 5, meaning that Ford company sell its cars equipped with a spare wheel. It also uses a default color for its cars that is white and this value is assigned to color class variable.
The car dealer has a special offer for this car — it comes with winter tires, meaning that we will have 4 more extra wheels. Now the total number of wheels is 9 and he also paints it with a special metallic Green color.
At this point, our ford object has the following values for its variables:

ford.wheelsNo = 9;
ford.color = “Green”;

The car is ready to be shipped — a trustful logistics company will to the job. The logistics company has to do it in two easy steps. Take the car from the dealer using void serialize(Ford ford) method and deliver it to the customer using Ford deserialize() method.
Everything is ok so far, but after the delivery is done, the customer realizes that the dealer did not fulfill his contractual clauses. In fact, the extra 4 winter wheels are missing, and also the spare wheel.
Our customer does not panic thinking that this is just a mistake that will be solved as soon as possible and calls the dealer letting him know about the problem.
The dealer checks his winter tires stocks and also the papers drafted by the logistics partner. Everything looks good in stocks and papers. Hmm, it seems that the problem must be a logistics one. The dealer calls the logistics company and asks them what happened.
The logistics company checks its own papers of transport and indeed they found that the car was picked up with extra 5 wheels, but delivered without them. This is for sure a shipping issue and the mystery must be solved as soon as possible in order to avoid these unfortunate events in the future.
A commission of inquiry is convened and the conclusion is that the tires simply slip off the transport track during the trip to destination. In our Java world, the transport is done using serialize and deserialize methods and the problem lies there. In fact, the object that is transported has a problem. Looking closely to Car superclass, we see that it does not implement Serializable interface, but still does not throw a runtime exception if a subclass is serialized. However, the class and its variables behave in a different way that a serializable class does.
The behavior of a serialized object during deserialization is that the constructor is not called and class variables take the values that they had before serialization. This is not valid if our serialized object class extends a class that is not serializable, in our case Car class. This means that the non-serializable class variables will be reinitialized, and the constructor is called; in fact, the Car class is silently instantiated, using the default constructor. Therefore, wheelsNo variable will be initialized with implicit int value, which is 0, and after the constructor is called, its value will be 4.

This is a happy ending story though, the customer gets all 5 extra wheels he payed for, and the logistics company will be careful when serializing and deserializing derived classes.

Java offers many corner cases and every Java developer encounters them as his/her carreer progresses.
I am looking forward to the next unknown one and, needless to say, to any input you might have.

--

--