You need to sort a collection of objects that have a preestablished order, such as the days of the week or the order of planets in the solar system.
Use FixedOrderComparator
in
Commons Collections. When using FixedOrderComparator
, you supply an array of
objects in a sorted order and the Comparator
returns comparison results based on
the order of the objects in this array. The following example uses a
fixed string array to compare different Olympic medals:
import java.util.*; import org.apache.commons.beanutils.BeanComparator; import org.apache.commons.collections.comparators.FixedOrderComparator; String[] medalOrder = {"tin", "bronze", "silver", "gold", "platinum"}; Comparator medalComparator = new FixedOrderComparator( medalOrder ); Comparator athleteComparator = new BeanComparator( "medal", medalComparator ); Athlete athlete1 = new Athlete( ); Athlete athlete2 = new Athlete( ); int compare = medalComparator.compare( athlete1.getMedal( ), athlete2.getMedal( ) );
In this code, a FixedOrderComparator
compares two Athletes
by the value of the medal
property. The medal
property can be "tin," "bronze,"
"silver," "gold," or "platinum," and a FixedOrderComparator
uses the order of the
medalOrder
array to compare these
values. The medalOrder
array
establishes a fixed relationship between the three medal types; "bronze"
is less than "silver," which is less than "gold."
Use FixedOrderComparator
when
sorting an array or a collection that contains values that are ordered
in a pre-determined fashion: days of the week, planets in the solar
system, colors in the spectrum, or hands dealt in a poker game. One way
to sort an array containing the days of the week would be to assign a
numerical value to each day—"Monday" is one, "Tuesday" is two,
"Wednesday" is three, etc. Then you could sort the array with a Comparator
that takes each day's name, sorting
elements based on the numerical value corresponding to a day's name. An
alternative is the use of FixedOrderComparator
, letting the comparator
order objects based on the order of an array of day names.
If a bean contains a property to be sorted according to a
fixed-order array, you can use the BeanComparator
in conjunction with FixedOrderComparator
. The following example
sorts cards by value and suit using a FixedOrderComparator
and a BeanComparator
; A PlayingCard
object, defined in Example 4-3, is sorted according to the
order of two arrays—one for the face value of the PlayingCard
and one for the suit of the
PlayingCard
.
Example 4-3. A bean representing a playing card
package org.discursive.jccook.collections.compare; public class PlayingCard( ) { public static String JOKER_VALUE = null; public static String JOKER_SUIT = null; private String value; private String suit; public PlayingCard( ) {} public PlayingCard(String value, String suit) { this.value = value; this.suit = suit; } public String getValue( ) { return value; } public void setValue(String value) { this.value = value; } public String getSuit( ) { return suit; } public void setSuit(String suit) { this.suit = suit; } public String toString( ) { String cardString = "JOKER"; if( value != null && suit != null ) { cardString = value + suit; } return cardString; } }
Example 4-4 creates a
ComparatorChain
of BeanComparators
, which compares the value
and suit
properties using a FixedOrderComparator
. Each card's suit is
compared first, and, if two cards have the same suit, they are compared
by face value. Jokers do not have suits or a face value, and this
example handles jokers with a null
-valued suit
and value
property by wrapping each FixedOrderComparator
with a NullComparator
.
Example 4-4. Combining FixedOrderComparator with BeanComparator, NullComparator, and ComparatorChain
package com.discursive.jccook.collections.compare; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import org.apache.commons.beanutils.BeanComparator; import org.apache.commons.collections.comparators.NullComparator; import org.apache.commons.collections.comparators.FixedOrderComparator; import org.apache.commons.collections.comparators.ComparatorChain; public class FixedOrderExample { // Suit order "Spades", "Clubs", "Diamonds", "Hearts" private String[] suitOrder = { "S", "C", "D", "H" }; private String[] valueOrder = { "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A" }; public static void main(String[] args) { FixedOrderExample example = new FixedOrderExample( ); example.start( ); } public void start( ) { List cards = new ArrayList( ); cards.add( PlayingCard( "J", "C" ) ); cards.add( PlayingCard( "2", "H" ) ); cards.add( PlayingCard( PlayingCard.JOKER_VALUE, PlayingCard. JOKER_SUIT)); cards.add( PlayingCard( "2", "S" ) ); cards.add( PlayingCard( "Q", "S" ) ); cards.add( PlayingCard( "4", "C" ) ); cards.add( PlayingCard( "J", "D" ) ); System.out.println( "Before sorting: " + printCards( cards ) ); // Create a null-safe suit order comparator that will compare the // suit property of two Java beans Comparator suitComparator = new FixedOrderComparator( suitOrder ); suitComparator = new NullComparator( suitComparator ); suitComparator = new BeanComparator( "suit", suitComparator ); // Create a null-safe value order comparator that will compare the // value property of two Java beans Comparator valueComparator = new FixedOrderComparator( valueOrder ); valueComparator = new NullComparator( valueComparator ); valueComparator = new BeanComparator( "value", valueComparator ); // Create a chain of comparators to sort a deck of cards Comparator cardComparator = new ComparatorChain( ); cardComparator.addComparator( suitComparator ); cardComparator.addComparator( valueComparator ); Collections.sort( cards, cardComparator ); System.out.println( "After sorting: " + printCards( cards ) ); } private String printCards( List cards ) { StringBuffer resultBuffer = new StringBuffer( ); Iterator cardIter = cards.iterator( ); while( cardIter.hasNext( ) ) { PlayingCard card = (PlayingCard) cards.next( ); resultBuffer.append( " " + card.toString( ) ); } return resultBuffer.toString( ); } }
This example sorts the PlayingCard
objects and produces the following
output:
Before sorting: JC 2H JOKER 2S QS 4C JD After sorting: 2S QS 4C JC JD 2H JOKER
The list is sorted such that all the cards of a similar suit are
grouped together—spades are less than clubs, clubs are less than
diamonds, and diamonds are less than hearts. A sorted collection of
cards is grouped by suits, and, within each suit, cards are organized
according to face value—2 is low and aces is high. The order used in the
sorting is captured in two fixed-order arrays, suitOrder
and faceOrder
. If a shuffled deck were used in the
example, it would end up as a perfectly sorted deck of cards.
Example 4-4 ties a number of
simple Comparators
together to
perform a fairly complex sort. A FixedOrderComparator
is wrapped in a NullComparator
, which is then wrapped with a
BeanComparator
. These BeanComparator
instances are then combined in
a ComparatorChain
. The use of
NullComparator
with a BeanComparator
is recommended to avoid a
NullPointerException
from BeanComparator
; BeanComparator
is not designed to handle
null
-valued bean properties, and it
throws an exception if you ask it to play nice with null
s.
BeanComparator
is discussed in
Recipe 3.10. This helpful
utility is indispensable if you are working with systems that need to
sort JavaBeans.
For more information about the ComparatorChain
object, see Recipe 4.4. For more information
on the NullComparator
, see Recipe 4.5.