Monday, April 4, 2011

Simplify Sorting of objects with Java Collections


Generic ways to sort the collection of user define objects.


Basically we used the sort method of the collections class to sort the objects.  Specific sorter class or anonymous inner class every time we have to write to sort the collection objects. By default if class implements comparable interface then Collections.sort method used the methods overridden by the class.  If we have to override the default behavior of  the comparable interface then we have to supply methods of comparator.

Code snippet 1:


User define View Object (VO) has following properties
package com.myorg.vo;
public class User {
      private Integer userid;
      private String firstName;
      private String lastName;
      private String userType;
      public Integer getUserid() {
            return userid;
      }
      public void setUserid(Integer userid) {
            this.userid = userid;
      }
      public String getFirstName() {
            return firstName;
      }
      public void setFirstName(String firstName) {
            this.firstName = firstName;
      }
      public String getLastName() {
            return lastName;
      }
      public void setLastName(String lastName) {
            this.lastName = lastName;
      }
      public String getUserType() {
            return userType;
      }
      public void setUserType(String userType) {
            this.userType = userType;
      }
           
      public User(Integer userid, String firstName, String lastName,
                   String userType) {
            this.userid = userid;
            this.firstName = firstName;
            this.lastName = lastName;
            this.userType = userType;
      }
     
      public User(){}
      @Override
      public String toString() {
            return "UserID ("+ userid + ") First Name ("+firstName+ ") Last                      Name ("+lastName+") UserType ("+userType+ ")";
      }
     
     
}


Code Snippet 2:

Sort the list of users according to user id. Generally we write the comparator to sort the result as shown in code snippet 2

/**
 * @author Ashish.Chudasama
 */
public class Main {

      public static void main(String[] args) {
            List<User> lstUsers = new ArrayList<User>();
            lstUsers.add(new User(2, "Amit", "Sharma", "Admin"));
            lstUsers.add(new User(4, "Tusha", "Koli", "P1"));
            lstUsers.add(new User(1, "Ashish", "Chudasama", "P2"));
            lstUsers.add(new User(3, "Amit", "Jhala", "P2"));
            // sort the user by user id
            Collections.sort(lstUsers, new Comparator<User>() {
                  @Override
                  public int compare(User u1, User u2) {
                        //for ascending Order
                        return u1.getUserid().compareTo(u2.getUserid());
                        // for Descending order
                        //return u2.getUserid().compareTo(u1.getUserid());
                  }
            });
            for (User user : lstUsers) {
                  System.out.println(user);
            }
      }

}

Output:
UserID (1) First Name (Tusha) Last Name (Koli) UserType (P1)
UserID (2) First Name (Riten) Last Name (Jhala) UserType (P1)
UserID (3) First Name (Ashish) Last Name (Chudasama) UserType (P2)
UserID (4) First Name (Amit) Last Name (Sharma) UserType (Admin)


Generic way to sort the any user define class object can be accomplished using combination of generic type and reflection as shown in code snippet 3.

 

Code snippet 3:


This code snippet describe how to write the generic sorter that sort any types of data types in ascending and descending order. By default sorter sort the result into ascending order. We can change the order by supplying order by operation through overloaded constructors.

package com.sorter.multilevel;

import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.Date;

/**
 * This class used to sort the User define objects.
 * This class simulate the SQL like feature provided by the Orderby clause.
 * @author Ashish.Chudasama
 */
class GenericSorter<T> implements Comparator<T> {
      public static int ASC = 1;
      public static int DESC = 2;
      Comparator<T> chainComparator = null;
      String fieldName;
      int orderBy = ASC;

      /**
 * @param fieldName
 */
public GenericSorter(String fieldName) {
      this.fieldName = fieldName;
}

public GenericSorter(String fieldName, int orderBy) {
      this.fieldName = fieldName;
      this.orderBy = orderBy;
}

/**
 * @param chainComparator
 * @param fieldName
 */
public GenericSorter(String fieldName, Comparator<T> chainComparator) {
      this.chainComparator = chainComparator;
      this.fieldName = fieldName;
}

public GenericSorter(String fieldName, int orderBy,
            Comparator<T> chainComparator) {
      this.chainComparator = chainComparator;
      this.fieldName = fieldName;
      this.orderBy = orderBy;
}

@Override
public int compare(T o1, T o2) {
      int result = this.orderBy == ASC ? compareValue(o1, o2) : compareValue(
                  o2, o1);
      if (result == 0 && chainComparator != null) {
            result = chainComparator.compare(o1, o2);
      }
      return result;
}

private int compareValue(T o1, T o2) {
      int returnValue = 0;
      try {
            //obtain the filed.
      Field field = o1.getClass().getDeclaredField(this.fieldName);
      boolean isAccessible=true;
      //check field is accessible or not
      // if not then enable the access
      if(!field.isAccessible())
      {
            field.setAccessible(true);
            isAccessible=false;
      }
      //Retrieve fields values
      Object objectO1 = field.get(o1);
      Object objectO2 = field.get(o2);
      // check for the number
      if (objectO1 instanceof Number) {
            if ((objectO1 != null && objectO2 != null)
                        && (objectO1 instanceof Integer
                                    || objectO1 instanceof Long || objectO1 instanceof Byte)) {
                  returnValue = Long.valueOf(objectO1 + "").compareTo(
                              Long.valueOf(objectO2 + ""));
            } else if ((objectO1 != null && objectO2 != null)
                        && (objectO1 instanceof Double || objectO1 instanceof Float)) {

                  returnValue = Double.valueOf(objectO1 + "").compareTo(
                              Double.valueOf(objectO2 + ""));

            }
      } else if (objectO1 instanceof String
                  || objectO1 instanceof Character) {
            if ((objectO1 != null) && objectO2 != null) {
                  returnValue = String.valueOf(objectO1).compareToIgnoreCase(
                              String.valueOf(objectO2));
            }
      } else if (objectO1 instanceof Date) {
            returnValue = ((Date) objectO1).compareTo((Date) objectO2);
      }
      //reset the field access
                  field.setAccessible(isAccessible);
            } catch (Exception e) {
                  e.printStackTrace();
            }
            return returnValue;
      }

}

Code snippet 4:


The code snippet 4 demonstrates the used of GenericSorter class to sort the list of users in ascending order according to userId field.

/**
 * @author Ashish.Chudasama
 */
public class Main {

      public static void main(String[] args) {
            List<User> lstUsers = new ArrayList<User>();
            lstUsers.add(new User(4, "Amit", "Sharma", "Admin"));
            lstUsers.add(new User(1, "Tusha", "Koli", "P1"));
            lstUsers.add(new User(3, "Ashish", "Chudasama", "P2"));
            lstUsers.add(new User(2, "Riten", "Jhala", "P1"));
            // sort the user by user id
Collections.sort(lstUsers, new GenericSorter<User>("userid"));
/**
* Note: userid is a field name/variable name of class user
 */
            for (User user : lstUsers) {
                  System.out.println(user);
            }
      }

}
Output:
UserID (1) First Name (Tusha) Last Name (Koli) UserType (P1)
UserID (2) First Name (Riten) Last Name (Jhala) UserType (P1)
UserID (3) First Name (Ashish) Last Name (Chudasama) UserType (P2)
UserID (4) First Name (Amit) Last Name (Sharma) UserType (Admin)

Code snippet 5:


The code snippet 5 demonstrates the used of GenericSorter class to sort the list of users in descending order according to userId field.

/**
 * @author Ashish.Chudasama
 */
public class Main {

      public static void main(String[] args) {
            List<User> lstUsers = new ArrayList<User>();
            lstUsers.add(new User(4, "Amit", "Sharma", "Admin"));
            lstUsers.add(new User(1, "Tusha", "Koli", "P1"));
            lstUsers.add(new User(3, "Ashish", "Chudasama", "P2"));
            lstUsers.add(new User(2, "Riten", "Jhala", "P1"));
            // sort the user by user id
Collections.sort(lstUsers, new GenericSorter<User>("userid",GenericSorter.DESC));
/**
* Note: userid is a field name/variable name of class user
 */
for (User user : lstUsers) {
                  System.out.println(user);
            }
      }

}
Output:
UserID (4) First Name (Amit) Last Name (Sharma) UserType (Admin)
UserID (3) First Name (Ashish) Last Name (Chudasama) UserType (P2)
UserID (2) First Name (Riten) Last Name (Jhala) UserType (P1)
UserID (1) First Name (Tusha) Last Name (Koli) UserType (P1)

Code snippet 6:


SQL support the orderby clause by which we can sort the result into multiple levels. The code snippet 6 shows the lists of users are sorted according to FirstName and then UserType.  
/**
 * @author Ashish.Chudasama
 */
public class Main {

      public static void main(String[] args) {
            List<User> lstUsers = new ArrayList<User>();
            lstUsers.add(new User(2, "Amit", "Sharma", "Admin"));
            lstUsers.add(new User(4, "Tusha", "Koli", "P1"));
            lstUsers.add(new User(1, "Ashish", "Chudasama", "P2"));
            lstUsers.add(new User(3, "Amit", "Jhala", "P2"));
            // sort the user by user id
Collections.sort(lstUsers, new GenericSorter<User>("firstName" ,new GenericSorter<User>("userType")));
/**
* Note: userid is a field name/variable name of class user
 */

for(User user : lstUsers) {
                  System.out.println(user);
            }
      }

}
Output:
UserID (2) First Name (Amit) Last Name (Sharma) UserType (Admin)
UserID (3) First Name (Amit) Last Name (Jhala) UserType (P2)
UserID (1) First Name (Ashish) Last Name (Chudasama) UserType (P2)
UserID (4) First Name (Tusha) Last Name (Koli) UserType (P1)

Code snippet 7:


SQL support the orderby clause by which we can sort the result into multiple levels. The code snippet 6 shows the lists of users are sorted according to FirstName in ascending order and then UserType in descending order.  

/**
 * @author Ashish.Chudasama
 */
public class Main {

      public static void main(String[] args) {
            List<User> lstUsers = new ArrayList<User>();
            lstUsers.add(new User(2, "Amit", "Sharma", "Admin"));
            lstUsers.add(new User(4, "Tusha", "Koli", "P1"));
            lstUsers.add(new User(1, "Ashish", "Chudasama", "P2"));
            lstUsers.add(new User(3, "Amit", "Jhala", "P2"));
            // sort the user by user id
Collections.sort(lstUsers, new GenericSorter<User>("firstName" ,new GenericSorter<User>("userType",GenericSorter.DESC)));
/**
* Note: userid is a field name/variable name of class user
 */

            for (User user : lstUsers) {
                  System.out.println(user);
            }
      }

}
Output:
UserID (3) First Name (Amit) Last Name (Jhala) UserType (P2)
UserID (2) First Name (Amit) Last Name (Sharma) UserType (Admin)
UserID (1) First Name (Ashish) Last Name (Chudasama) UserType (P2)
UserID (4) First Name (Tusha) Last Name (Koli) UserType (P1)

11 comments:

  1. Amazing...

    Just To list, Advantages are

    1) We can achive multi column sorting of list page.

    AND

    2) We can have only one class to sort any kind of object in our application.

    ReplyDelete
  2. Serious disadvantage is performance decreasing.

    ReplyDelete
  3. Yes,
    The performance is directly proportional to the number of condition checks and method calls for this code.
    1) Reflection is used to access the filed values and based on type comparisons is done ( this is first factor)
    2) Widening of data type before executing comparisons is second factor affecting performance.
    Situation specific code are “mostly but not always” shows the good performance, whereas generic code sometime sacrifice performance for the commonness (generic code).

    ReplyDelete
  4. What's bad with just sort it as it is normally done through the collections? In fact, you get it all sorted in database anyways.

    ReplyDelete
  5. Suppose we have to sort the temporary result stored in collection object but still that collection needs to follow some sorting order. The sorting order is not predefined, the sorting order is configure using admin console (for web app) for each screen.



    If we know that we have to sort the collections of user (any object) objects by column 1 ascending order, column 2 descending order. We have two ways to address this sorting

    1) Use chaining of comparator
    2) Use single comparator with if else block. If first compare result is zero then try to sort the second column.


    To address such scenarios we need simple and generic comparator to sort the collection objects. Eventually we are using the same Collections.sort(list,comparator) method to sort the collection but we generating comparator chain as per the requirement.



    Consider the scenario; you are developing multilingual web application for English, Chinese, Japanese, French language and English language will be your default language. Client requirement is, if data translation is missing (not available) for any language then default language data needs to be display. In such a scenario whenever result contains missing data that missing data needs to be fetch from the default table. Hence the order by clause needs to apply on merged result and can’t rely on database order by clause.

    ReplyDelete
  6. 2Anonymous: I support Ashish, it worth to delegate to Data Access level just some default sorting.
    For example, User can freely change sorting and Data Access level should not take part in resorting.
    Resorting is not responsibility of Data Access level.
    It is responsibility of Presentation level.
    So sorting without Data Access level participation is needed.
    I think for a non-performance sensitive application the way of sorting described in the article is suitable.
    But Architect of such non-performance sensitive application should keep in mind constantly performance specifics of the comparator.

    Such comparator simplifies life of developer.
    And it is one of advantages of the comparator.
    It increases performance of development.

    ReplyDelete
  7. I do like the basic idea. Make life easier when data has to be sorted depending on more than one field.

    Concept of chain comparator is good indeed.
    Yes there are performance issues. But when we want to make every thing generic. It costs some type checks too.

    You may avoid usage of Strings. I think most of the wrapper classes have compareTo method which takes arguments of same types. Like Integer.compareTo(Integer). You can use that instead of casting it to Strings.
    Long.valueOf(objectO1 + "").compareTo(
    Long.valueOf(objectO2 + "")).

    ReplyDelete
  8. Hi Mohammed,

    Thanks for your feedback, you are right but we have certain limitation while we prefer reflection.

    Reflection returns field value as object (Object reference). Object class does not have compareTo method hence we can’t directly compare two objects. Wrapper class does not provide any overloaded method to convert object type to appropriate wrapper type. Every wrapper class support s string overloaded method to convert string argument to appropriate wrapper instance, hence string casting is done in example code.

    ReplyDelete
  9. Hi, probably our entry may be off topic but anyways, I have been surfing around your blog and it looks

    very professional. It’s obvious you know your topic and you appear fervent about it. I’m developing a

    fresh blog plus I’m struggling to make it look good, as well as offer the best quality content. I have

    learned much at your web site and also I anticipate alot more articles and will be coming back soon.Thanks you.




    ASC Coding

    ReplyDelete
    Replies
    1. Nice class, I'm glad you wrote about.

      A minor improvement:
      Add an inner enum instead of using ints for the sort order, for better type safety:
      public enum OrderBy { ASC, DESC }

      A major improvement:
      Why have the logic to see if the objects are Number, Double, Date, etc., when they all implement Comparable? Also, if your User object has a member which is an object itself, your GenericSorter won't sort on it, even if that child class implements Comparable. The meat of your compareValue() can be replaced with just this, and it works for all classes that implement Comparable:

      if ((value1 instanceof String) || (value1 instanceof Character)) {
      returnValue = String.valueOf(value1).compareToIgnoreCase(String.valueOf(value2));
      } else if ((value1 instanceof Comparable) && (value2 instanceof Comparable)) {
      returnValue = ((Comparable)value1).compareTo((Comparable)value2);
      }

      -Conrad

      Delete