Google

Jul 27, 2014

Top 10 most common Core Java beginner mistakes

Mistake #1: Using floating point data types like float or double for monetary calculations. This can lead to rounding issues.

package com.monetary;
import java.math.BigDecimal;

public class MonetaryCalc {
 
    public static void main(String[] args) {
      System.out.println(1.05 - 0.42); //1:  $0.6300000000000001
       //2: $0.63
      System.out.println(new BigDecimal("1.05") .subtract(new BigDecimal("0.42")));
      //3: $0.630000000000000059952043329758453182876110076904296875
      System.out.println(new BigDecimal(1.05) .subtract(new BigDecimal(0.42)));
      //4: $0.63
      System.out.println(BigDecimal.valueOf(1.05) .subtract(BigDecimal.valueOf(0.42)));
      System.out.println(105 - 42); //5: 63 cents
   }
}

In the above code, 2, 4, and 5 are correct and 1 and 3 are incorrect usage leading to rounding issues. So, either use BigDecimal properly as shown in 2 and 4, or use the smallest monetary value like cents with data type long. The cents approach performs better, and handy in applications that have heavy monetary calculations.

Mistake #2: Using floating point variables like float or double in loops to compare for equality. This can lead to infinite loops.

 public static void main(String[] args) {
         float sum = 0;
         while (sum != 1.0) { //causes infinite loop.
             sum += 0.1;
         }
         System.out.print("The sum is: "+sum);
 }


Fix is to change while (sum != 1.0) to while (sum < 1.0). Even then, due to mistake #1, you will get rounding issues (e.g. The sum is: 1.0000001). So, the better fix is

   public static void main(String[] args) {
         BigDecimal sum = BigDecimal.ZERO;
         while (sum.compareTo(BigDecimal.ONE)  != 0) { 
             sum =  sum.add(BigDecimal.valueOf(0.1)); 
         }
         System.out.print("The sum is: "+sum); //The sum is: 1.0
 }


Mistake #3: Not properly implementing the equals(..) and hashCode( ) methods. Can you pick if anything wrong with the following Person class.

public class Person  {

 private String name;
 private Integer age;

 public Person(String name, Integer age) {
  this.name = name;
  this.age = age;
 }

 //getters and setters

 @Override
 public String toString() {
  return "Person [name=" + name + ", age=" + age + "]";
 } 
}

Now, the main class that creates a collection of Person, and then searches for a person.

import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CopyOnWriteArraySet;

public class PersonTest {
 
 public static void main(String[] args) {
  
  Set<Person> people = new CopyOnWriteArraySet<>();
  people.add(new Person("John", 35));
  people.add(new Person("John", 32));
  people.add(new Person("Simon", 30));
  people.add(new Person("Shawn", 30));
  
  System.out.println( people);
  
  Person search = new Person("John", 35);
  
  if(people.contains(search)){
   System.out.println("found: " + search);
  }
  else {
   System.out.println("not found: " + search);
  }
 
 }

}

The output

[Person [name=John, age=35], Person [name=John, age=32], Person [name=Simon, age=30], Person [name=Shawn, age=30]]
not found: Person [name=John, age=35]

Q. Why is the person not found even though there in the collection?
A. Every Java object implicitly extends the Object class, which has the default implementation of equals(...) and hashCode( ) methods. The equals(...) method is  implicitly invoked by the Set class's contains(...) method. In this case the default implementation in the Object class performs a shallow comparison of the references, and not the actual values like name and age. The equals and hashCode methods in Java are meant for overridden for POJOs. So, this can be fixed as shown below by overriding the equals and hashCode methods in Person.

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;

public class Person  {

 private String name;
 private Integer age;

 public Person(String name, Integer age) {
  this.name = name;
  this.age = age;
 }

 //getters and setters

 @Override
 public int hashCode() {
  return new HashCodeBuilder().append(name).append(age).hashCode();
 }

 @Override
 public boolean equals(Object obj) {
  Person rhs = (Person) obj;
  return new EqualsBuilder().append(this.name, rhs.name).append(this.age, rhs.age).isEquals();
 }

 @Override
 public String toString() {
  return "Person [name=" + name + ", age=" + age + "]";
 }
 
}

Now, output will be

[Person [name=John, age=35], Person [name=John, age=32], Person [name=Simon, age=30], Person [name=Shawn, age=30]]
found: Person [name=John, age=35]

There are other subtle issues you can face if the hashCode and equals contracts are not properly adhered to. Refer to the Object class API for the contract details.

When sorting objects in a collection with compareTo, it is strongly recommended, but not strictly required that (x.compareTo(y)==0) == (x.equals(y)). Generally speaking, any class that implements the Comparable interface and violates this condition should clearly indicate this fact.

Mistake #4: Getting a ConcurrentModificationException  when trying to modify (i.e. adding or removing an item) a collection while iterating.

The following code throws a ConcurrentModificationException.

List<T> list = getListOfItems();
for (Iterator<T> iter = list.iterator(); iter.hasNext(); ) {
  T obj = iter.next();
  if (obj.someCondition()) {
    list.remove(0); // ConcurrentModificationException
  }
}

To avoid ConcurrentModificationException in a single-threaded environment, you can remove the object that you are working on with the iterator.

List<T> list = getListOfItems();
for (Iterator<T> iter = list.iterator(); iter.hasNext(); ) {
  T obj = iter.next();
  if (obj.someCondition()) {
    iter.remove(); //OK to use the iterator
  }
}

To Avoid ConcurrentModificationException in a multi-threaded environment:

If you are using JDK1.5 or higher then you can use ConcurrentHashMap and CopyOnWriteArrayList classes. This is the recommended approach compared to other approaches like locking the list with synchronized(list) while iterating, but this approach defeats the purpose of using multi-threading.

Q. Difference between fail-fast and fail-safe iterators
A. Iterators returned by most of pre JDK1.5 collection classes like Vector, ArrayList, HashSet, etc are fail-fast iterators. Iterators returned by JDK 1.5+ ConcurrentHashMap and CopyOnWriteArrayList classes are fail-safe iterators.


Mistake #5: Common exception handling mistakes.

a) Sweeping exceptions under the carpet by doing nothing.

try{
 
}catch(SQLException sqe){
    // do nothing
}


In few rare scenarios, it is desired to do nothing with an exception, e.g. in finally block, we try to close database connection, and some exception occurs. In this case, exception can be ignored.

try{
 
}catch(SQLException sqe){
    ...
    ...
}finally{
    try{
        conn.close();
    }catch(Exception e){
        //leave it.
    }
}


But in general, this is a very bad practice and can hide issues.

b) Inconsistent use of checked and unchecked (i.e. Runtime) exceptions.

Document a consistent exception handling strategy. In general favor unchecked (i.e. Runtime) exceptions, which you don't have to handle with catch and throw clauses. Use checked exceptions in a rare scenarios where you can recover from the exception like deadlock or service retries. In this scenario, you will catch a checked exception like java.io.IOException and wait a few seconds as configured with the retry interval, and retry the service configured by the retry counts.



c) Exceptions are polymorphic in nature and more specific exceptions need to be caught before the generic exceptions. 

So, it is wrong to catch Exception before IOException.

try{
   //....
} catch(Exception ex){
    log.error("Error:" + ex)
} catch(IOException ex){
    log.error("Connectivity issue:" + ex); //never reached as Exception catch block catches everything
}


Fix this by catching the more specific IOException first

try{
   //....
} catch(IOException ex){
    log.error("Connectivity issue:" + ex);
} catch(Exception ex){
    log.error("Error:" + ex)
} 

In Java 7 on wards, you can catch multiple exceptions like

try{
   //....
} 
catch (ParseException | IOException exception) {
    // handle I/O problems.
} catch (Exception ex) {
    //handle all other exceptions
}

d) Wiping out the stack trace

try{
    //....
}catch(IOException ioe){
    throw new MyException("Problem in data reading."); //ioe stack is lost
}

e) Unnecessary Exception Transformation. 

In a layered application, many times each layer catches exception and throws new type of exception. Sometimes it is absolutely unnecessary to transform an exception. An unchecked exception can be automatically bubbled all the way upto the GUI Layer, and then handled at the GUI layer with a log.error(ex) for the log file and a generic info like "An expected error has occurred, and please contact support on xxxxx" to the user. Internal details like stack trace with host names, database table names, etc should not be shown to the user as it can be exploited to cause security threats.

Data Access Layer --> Business Service Layer --> GUI Layer

Mistake #6: Using System.out.println(... ) statements for debugging without making use of the debugging capabilities provided in IDE tools. If you need to log, use log4j library instead.

if(log.isDebugEnabled()) {
    log.debug("..................");
}
//....
log.info("................");
//...
log.error(ex);


The log4j library will not only perform better than System.out.println statements, but also provides lots of additional features like writing to a file, console, queue, etc, archiving and rolling the log files, controlling the log levels with debug, warn, info, error, etc and many more.

Mistake #7: Reinventing the wheel by writing your own logic when there are already well written and proven APIs and libraries are available. When coding, always have Core Java APIs, Apache APIs, Spring framework APIs, Google Gauva library APIs, and relevant reference documentations handy to reuse them instead of writing your own half baked solutions.

Instead of:

if(str != null && str.trim().length() > 0) {
   //..............
}

You can use the StringUtils library from the Apache commons API to simplify your code

if(!StringUtils.isEmpty(str){
    //...........
}

EqualsBuilder, HashCodeBuilder, StringBuilder, etc are Apache commons utility methods.


Mistake #8: Resource leak issues are reported when resources are allocated but not properly disposed (i.e. closed) after use.

The system resources like file handles, sockets, database connections, etc are limited resources that need to be closed once done with them otherwise your applications run the risk of leaking resources and then running out of sockets, file handles, or database connections.

Bad code: if an exception is thrown before in.close() is reached, the Scanner that is holding on to the System.in resource will never get closed.

public void readShapeData() throws IOException {
    Scanner in = new Scanner(System.in);
    log.info("Enter salary: ");
    salary = in.nextDouble();
    in.close();
}

Good code: The finally block is reached even if an exception is thrown. So, the scanner will be closed.

public void readShapeData() throws IOException {
    Scanner in = new Scanner(System.in);
    try {
        log.info("Enter salary: ");
        salary = in.nextDouble();
    } finally {
        in.close();  //reached even when an exception is thrown
    }
}


Mistake #9: Comparing two objects ( == instead of .equals)

When you use the == operator, you are actually comparing two object references, to see if they point to the same object. You cannot compare, for example, two strings or objects for equality, using the == operator. You must instead use the .equals method, which is a method inherited by all classes from java.lang.Object.


Mistake #10: Confusion over passing by value, and passing by reference as Java has both primitives like int, float, etc and objects.

When you pass a primitive data type, such as a char, int, float, or double, to a function then you are passing by value, which means a copy of the data type is duplicated, and passed to the function.If the function chooses to modify that value, it will be modifying the copy only.

When you pass a Java object, such as an array or an Employee object, to a function then you are passing by reference, which means the reference is copied, but both the original and copied references point to the same object. Any changes you make to the object's member variables will be permanent - which can be either good or bad, depending on whether this was what you intended.


5 bonus mistakes:

  1. Forgetting that Java is zero-indexed.
  2. Not writing thread-safe code with proper synchronization or thread-local objects. 
  3. Not favoring immutable objects. Immutable objects are inherently thread-safe.
  4. Not properly handling null references, and causing the ubiquitous NullPointerException. This is a run time exception and the compiler can't warn you. 
  5. Capitalization errors where the class names and Java file names should start with capital letters and method and variable names should start with lowercase letters.


Labels:

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]

<< Home