Function-Oriented Java

Arrays and Collections

Generic Comparator for sorting Collections
Sort object lists with Specific comparators
Sorting arrays with comparators

Generic Comparator

The scenario where the GenericComparator class rises to the occasion is the collection of data objects that have to be sorted and re-sorted by a client application. An example is a web-based e-mail page, where a collection of messages is displayed in an HTML table. The headers at the top of the page allow a user to click on the header and sort the page by a different criterion.

A common implementation for the above scenario is to write a special Comparator. This class is able to accept an attribute String or Integer to help the Comparator understand what attribute has been selected for sorting. To picture this, imagine a "Invoice" object that contain attributes like Amount, PurchaseDate, Item. If you passed a String like "PurchasedDate", the Comparator would understand that it should sort on one of these three.

The comparator implements the required compare() method, normally casting the Object parameters to the agreed-upon custom type. Frequently, the Comparator will have additional attributes that help it understand whether an ascending or descending search order is required.

When there are large numbers of classes that require this type of flexibility in sorting, you have to implement a Comparator for each type. As well, each Comparator implementation has to be coded and tested. A much better approach might be to consider a more generic Comparator that lets you specify which attribute you would like to sort on, regardless of class or attribute type.

To accomplish this, the GenericComparator relies heavily on Java reflection to evaluate the attribute String passed in and the value of each attribute for the objects passed in. Where there is a considerable number of objects to be sorted, this approach may be slower than an implementation that avoids Java reflection. Still, with modern Java Virtual Machines, this is becoming less of an issue and, if you aren't sorting many records, the GenericComparator may save some work.

How to call the Function

You can use any type of Java object, but for the illustration, we've provided a type called ValueObject which allows for quick data-entry in its constructor. In the code below, the Comparator is created with these parameters:

  • the attribute to sort on ( a field in the class)
  • whether the sort should be ascending or descending
  • a field of the attribute that should be evaluated, ie, java.util.Date should be sorted by "time". If you don't specify a field, the comparator default to the toString() value.
  • in the example of java.util.Date, you may prefer to sort using a method instead of a field, i.e., "getTime"
    ArrayList list = new ArrayList();
    list.add(new ValueObject("Toyota", 2033, new GregorianCalendar(2002,12,2).getTime()));
    list.add(new ValueObject("Dodge", 303, new GregorianCalendar(2003,11,2).getTime()));
    list.add( new ValueObject("Ford", 1033, new GregorianCalendar(2004,1,2).getTime()));
    System.out.println("*********SORT***********");
    System.out.println(list);
    Collections.sort(list, new GenericComparator("make",GenericComparator.ASC, null,null));
    System.out.println(list);
    Collections.sort(list, new GenericComparator("purchasedDate",GenericComparator.DESC, null,"getTime"));
    System.out.println(list);
    

    Function code

    //GenericComparator.java
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.GregorianCalendar;
    
    /**
     * @author Gervase Gallant gervasegallant@yahoo.com
     * 
     */
    public class GenericComparator implements Comparator {
    	public static final String ASC="asc";
    	public static final String DESC="desc";
    	
    	
    	private String fieldName = null;
    	private String compareField = null;
    	private String compareMethod = null;
    	private String sortOrder = null;
    	
    	
    	
    	/**
    	 * Constructor for GenericComparator.
    	 */
    	public GenericComparator(String field, String order, String compareField, String compareMethod) {
    		super();
    		this.fieldName=field;
    		this.compareField = compareField;
    		this.compareMethod = compareMethod;
    		if (order == null || order.equals(ASC)){
    			this.sortOrder=ASC;	
    		} else if (order.equals(DESC)){
    			this.sortOrder=DESC;	
    		}	
    		
    	}
    	
    	public GenericComparator(String field){
    		this(field,"asc",null, null);	
    	}	
    	/**
    	 * @see java.util.Comparator#compare(Object, Object)
    	 */
    	public int compare(Object o1, Object o2) {
    		Object value1;
    		Object value2;
    		try{
    			Field f = this.getAccessibleField(o1);
    					
    			if (f == null) return 0;
    			
    			if (this.sortOrder.equals(ASC)){
    				value1 = f.get(o1);
    				value2 = f.get(o2);
    			} else {
    				value2 = f.get(o1);
    				value1 = f.get(o2);
    				
    			}	
    			
    			if (this.compareField != null){ //someone told us which field to sort on...
    				f = this.getAccessibleField(this.compareField);
    				
    				value1 = f.get(value1);
    				value2=  f.get(value2);
    				return this.compareField( value1, value2);
    				
    			} else if (this.compareMethod != null){
    				Method m = value1.getClass().getMethod(this.compareMethod, null); //assume public....
    				value1 = m.invoke(value1, null);
    				value2 = m.invoke(value2, null);	
    				return this.compareField( value1, value2);
    			}	
    			
    			return this.compareField( value1, value2);		
    		}catch (Exception nsfe){
    			System.out.println(nsfe);
    			return 0;	
    		}	
    		
    	}
    	
    	private int compareField(Object value1, Object value2) throws IllegalAccessException{
    			
    			if (value1 instanceof Comparable){
    				return ((Comparable) value1).compareTo(value2);
    					
    			} else {		
    				return value1.toString().compareTo(value2.toString()); //try String sort.
    			}	
    	
    	
    	}	
    	
    	private Field getAccessibleField(Object object){
    	
    			Field[] fields = object.getClass().getDeclaredFields();
    			for (int i = 0; i < fields.length; i++){
    				if (fields[i].getName().equals(this.fieldName)){
    						fields[i].setAccessible(true); //hope Security Manager will allow;
    						return fields[i];
    						
    				}
    					
    			}
    			System.out.println("Sorry...couldn't sort on " + this.fieldName);
    			return null;
    	}
    
    
    }
    
    //Value object************************************************
    import java.util.Date;
    
    /**
     * @author Gervase Gallant gervasegallant@yahoo.com
     *
     */
    public class ValueObject {
    	private String make;
    	private int weight =  0;
    	private Date purchasedDate = null;
    	
    	/**
    	 * Constructor for TestDomainObj.
    	 */
    	public ValueObject(String make, int weight, Date d) {
    		super();
    		this.make = make;
    		this.weight = weight;
    		this.purchasedDate=d;
    	}
    	public String toString(){
    		return String.valueOf(make);	
    	}
    
    	public static void main(String[] args) {
    	}
    }
    
    
    

  • Copyright (c)2004 Gervase Gallant gervasegallant@yahoo.com