Skip to content

Pitfalls

The formal definition, given in the first Java Pitfalls(Wiley, 2000) book, is as follows:

A pitfall is code that compiles fine but when executed produces unintended and some-times disastrous results.

in the following we will see the most common pitfalls of Java programming languages:

String is an immutable class

The class Java String is immutable (unchangeable). This is because String objects are stored in a String pool. The object to which a string refers may shift, but the String objects themselves are not able to.

public class Main { 
    public static void main(String[] args) { 
        String str = "MyString"; 
        // A new string will be returned, but the actual String will remain the same 
        str.concat("ABC"); 
        // Prints inital value "MyString" 
        System.out.println(str); 
        // Now we change the reference of "str" to point to the new String returned 
        str = str.concat("ABC"); 
        // Prints the new concatenated String 
        System.out.println(str); 
    } 
} 

Memory Leaks

One of Java's key advantages is the Java Garbage Collector, which handles memory of objects on the heap. It is immediately freed if an object is unreachable.

A common slip for both new and experienced programmers, however, prevents the release of memory by allowing artifacts that are no longer in use to be reachable. This can be a major drawback to a project because memory leaks block resources and degrade the output of the application. Even java.lang. OutOfMemoryError may result.

Popular cases include:

  • Static Fields: using a static field and forget to set it to null after its data is no longer necessary
  • Unclosed Streams: The JVM allocates memory for any opened connection. Forgetting to close the connection is memory exhausting. Such connections can include: Input Streams, Database Connections, Sessions, etc.
  • Method finalize(): When we override the method finalize), (the object of that class is no longer collected directly from garbage. The Garbage Collector then awaits finalization, which will take place at a later point in time.

Pre/Post Increment Operator

The operators in Java are evaluated from left to right, the side effects can be seen instantly:

public class Main { 
    public static void main(String[] args) { 
        int num = 0; 
        /* First case */
        // The increment operator happens after the value is pushed onto the stack and assigned 
        int num1 = num++; 
        // Prints initial value 
        System.out.println(num1); 
        /* Second case */
        // Increment occurs first, and then it is pushed to the stack and assigned to num 
        int num2 = ++num; 
        System.out.println(num2); 
    } 
} 
Output:  0 
              1 
  • The execution context of the first case is as follows:
    • Store previous value of operand.
    • Increment the value.
    • Return the previous value
  • The execution context of the second case is as follows:
    • Increment the value.
    • Store value of operand (incremented)
    • Return the value

Objects comparison

Many inexperienced programmers try to use the "= =" operator to compare objects, and they get confused when their code behavior is not as planned. One thing to be aware of is that a reference comparison is made by the relation operator == it tests whether both objects point to the same memory location. Using the .equals() method would remove the issue since it compares the values inside the objects.

public class Main { 
    public static void main(String[] args) { 
        String s1 = new String("MyString"); 
        String s2 = new String("MyString"); 
        // Comparing using the relational operator 
        if (s1 == s2) { // false 
            System.out.println("Both objects point to the same memory location!"); 
        } 
        // Comparing using the .equals() 
        if (s1.equals(s2)) { // true 
            System.out.println("The value inside the object instances match!"); 
        } 
        // Declaring a string with a reference to s1 
        String s3 = s1; 
        if (s3 == s1) { // true 
            System.out.println("Both objects point to the same memory location!"); 
        } 
    } 
} 
Output :  The value inside the object instances match! 
               Both objects point to the same memory location! 

Raw Types

Before Java started using generic types, there have not been alternatives to raw types (types that are not parametrized). For backward compatibility reasons, it is still possible to define these:

public class Main { 
    public static void main(String[] args) { 
        // Defining a raw list, without specifying its type parameter 
        List myList = new ArrayList(); 
        // Due to the unspecified type we can add any data type and the code will compile 
        myList.add(100); 
        myList.add(200); 
        myList.add("String1"); 
        // When the second element is reached the compiler will throw a runtime error 
        // because we are trying to cast a string to an integer 
        myList.forEach(k -> System.out.println((int)k * 2)); 
    } 
} 
Output : 200
              400
Exception in thread "main" java.lang.ClassCastException: 
java.base/java.lang.String cannot be cast to java.base/java.lang.Integer

This issue can be prevented by defining the generic type of ArrayList()

Pass by value

Java method calls use pass by value to pass reference and return a result.

When you pass a reference value to a method, you're actually passing a reference to an object by value, which means that it is creating a copy of the object reference.

As long as both object references are still pointing to the same object, you can modify that object from either reference, and this is what causes confusion for some.

However, you are not passing an object by reference. The distinction is that if the object reference copy is modified to point to another object, the original object reference will still point to the original object.

void f(MutableLocation foo) {  
    foo = new MutableLocation(3, 4);   // Point local foo at a different object.
}
void g() {
    MutableLocation foo = MutableLocation(1, 2);
    f(foo);
    System.out.println("foo.x is " + foo.x); // Prints "foo.x is 1".
}

## Accessing non-static members

This error is often made by inexperienced programmers as they do not fully understand the differences in Java between static and non-static material. And interestingly, this error often occurs in the static main() process, where one tries to access a variable or process of an instance. Consider the code below:

public class StaticExample1 {
    public int number;  // instance variable
    public static void main(String[] args) {
        number = 30;    // compile error
    }
}
// Java compiler will issue this error:
error: non-static variable number cannot be referenced from a static context

Here, number is an instance variable which means that we can only access it via an object of its class, as shown in the following correction:

public class StaticExample1 {
    public int number;  // instance variable
    public static void main(String[] args) {
        StaticExample1 obj = new StaticExample1();
        obj.number = 30;
    }
}

The break Keyword

These Java problems can be very humiliating, and often they remain undiscovered until the production starts. In switch statements, fall-through behavior is always useful; however, missing a keyword "break" when such behavior is not desired can lead to catastrophic results. If you forget to place a "break" in "case 0" in the code example below, the program will write "Zero" followed by "One," as the control flow inside here will go through the whole "switch" statement before it reaches a "break." Take , for example:

public static void switchCasePrimer() {
    int caseIndex = 0;
    switch (caseIndex) {
        case 0:
            System.out.println("Zero");
        case 1:
            System.out.println("One");
            break;
        case 2:
            System.out.println("Two");
            break;
        default:
            System.out.println("Default");
    }
}

Ignoring Exceptions

To leave exceptions unchecked is always tempting. But managing them is the best practice for both beginner and experienced Java developers respectively. Exceptions are thrown on purpose, but in most situations we have to tackle the problems that cause these exceptions. Don't miss out on these exceptions. You can either re-throw it, display the user an error dialog or add a message to the log if necessary. At the very least, the explanation why the exception was left unhandled should be clarified to let other developers know the reason.

try {
    var1.method1();
} catch (NullPointerException e) {
    // Who cares!!!
}

Concurrent Modification Exception

This exception arises when a collection is updated using methods other than those provided by the iterator object, when iterating over it. Consider the following example:

List<String> fixedList = Arrays.asList("Apple", "Banana", "Carrot", "Grape");
List<String> listFruit = new ArrayList<>(fixedList);

for (String fruit : listFruit) {
    if (fruit.contains("e")) {
        listFruit.remove(fruit);
    }
}

Exercise 1: What does it print?

public class LastLaugh {
 public static void main(String args[]) {
  System.out.print("H" + "a");
  System.out.print('H' + 'a');
 }
}
Exercise 2: What does it print?
public class InTheLoop {
 public static final int END =
    Integer.MAX_VALUE;
 public static final int START = END - 100;
 public static void main(String[] args) {
  int count = 0;
  for (int i = START; i <= END; i++)
   count++;
   System.out.println(count);
  }
} 

Exercise 3: What does it print?

public class Confusing {
 private Confusing(Object o) {
  System.out.println("Object");
 }
 private Confusing(double[] dArray) {
  System.out.println("double array");
 }
 public static void main(String[] args) {
  new Confusing(null);
 }
}

Exercise 4: What is the problem and how can you fix it?

static void copy(String src, String dest) throws IOException {
 InputStream in = null;
 OutputStream out = null;
 try {
  in = new FileInputStream(src);
  out = new FileOutputStream(dest);
  byte[] buf = new byte[1024];
  int n;
  while ((n = in.read(buf)) >= 0)
   out.write(buf, 0, n);
  } finally {
   if (in != null) in.close();
   if (out != null) out.close();
  }
} 

Exercise 5: What does it print?

import java.math.BigInteger;
public class BigProblem {
 public static void main(String[] args) {
  BigInteger fiveThousand  = 
       new BigInteger("5000");
  BigInteger fiftyThousand = 
       new BigInteger("50000");
  BigInteger fiveHundredThousand = 
       new BigInteger("500000");
  BigInteger total = BigInteger.ZERO;
  total.add(fiveThousand);
  total.add(fiftyThousand);
  total.add(fiveHundredThousand);
  System.out.println(total);
 }
}