Guava Ordering - Part 2

In Ordering part 1 we showed the basics. In this screencast we will show how to sort an arraylist of objects by a field, order an enum in explicit order, chain multiple operations and combine comparators.

Detailed Video Notes

Before we dive into more examples utilizing Guava ordering, it is important to understand how to read chaining. Chaining is where you wrap or you call multiple ordering variations together. From the guava ordering explained page we can look at this example:

Ordering<Foo> ordering = Ordering.natural().nullsFirst().onResultOf(new Function<Foo, String>() {

  public String apply(Foo foo) {

    return foo.sortedBy;

  }
});

When you read Ordering chains, you read right to left instead of left to right. Above we can read it as: for each element to order we will apply a function then place nulls first in the list and we want it by the natural order. There is one exception to the rule and that is when you compound multiple comparator together. A suggestion the guava folks have is to avoid intermixing compound calls with chain calls.

Set up

[1:15]

For our second series of examples we created two objects: an address object and enum that represents the US states. We created an arraylist of states and initialized it with random addresses.

public class Address {

    String address;
    String city;
    State state;
    String zip;

    //...

    static Function<Address, State> onState = new Function<Address, State>() {
        @Override
        public State apply(Address input) {
            return input.state;
        }
    };
}

public enum State {

    Alabama("AL"), Montana("MT"), Alaska("AK"), Nebraska("NE"), Arizona("AZ"), Nevada(

    //...

    static Ordering<State> stateOrder = Ordering.explicit(State.Alabama,
            State.California, State.Florida, State.Washington, State.Wisconsin);
}

Since most of our real life situations we are looking to order a collection of objects, lets look at a few ways Ordering helps us.

Ordering by field

[1:48]

If we want to order a collection by an object field, we can create an Ordering instance that looks at the object by creating a guava function and passing it to onResultOf. By creating a function that accepts an address and returns a string that represents a city, we can create a comparator that will sort a list of addresses by city.

@Test
public void by_field() {

    // sort a list of object by a field/attribute
    Ordering<Address> byCity = Ordering.natural().onResultOf(
            new Function<Address, String>() {
                public String apply(Address address) {
                    return address.city;
                }
            });
    List<Address> orderedAddress = byCity.sortedCopy(addresses);

    for (Address address : orderedAddress) {
        System.out.println(address);
    }
}
Address{address=369 Pleasant Bear Heights, city=Goobertown, state=Alabama, zip=05427-6375}
Address{address=16 Emerald Impasse, city=Mozart, state=Washington, zip=05873-2086}
Address{address=7037 Green Butterfly Swale, city=Ritzville, state=California, zip=82918-7511}
Address{address=7966 Iron Panda Freeway, city=Sour Dough Flat, state=Wisconsin, zip=82254-8067}
Address{address=8180 Middle Campus, city=Winklepleck Grove, state=Florida, zip=82648-0809}

From existing comparator

[2:49]

We can construct a Ordering class by calling Ordering.from and passing String.CASE_INSENSITIVE_ORDER comparator that orders String objects by the compareToIgnoreCase. Then creating a function that accepts an address object and returns a string for address line we will pass it to onResultOf. Next calling sortedCopy and passing the list of address we sort address by address line.

@Test
public void comparing_string() {

    // sort address by address line
    // case insensitive order
    List<Address> sortedList = Ordering.from(String.CASE_INSENSITIVE_ORDER)
            .onResultOf(new Function<Address, String>() {
                public String apply(Address address) {
                    return address.address;
                }
            }).sortedCopy(addresses);

    for (Address address : sortedList) {
        System.out.println(address.getAddress());
    }
}
16 Emerald Impasse
369 Pleasant Bear Heights
7037 Green Butterfly Swale
7966 Iron Panda Freeway
8180 Middle Campus

Explicit order

[3:53]

Enums by default are ordered in which they are declared but if we want to declare them in a specific order, in this instance lets order them by midwest states. We can specify an Ordering class by calling Ordering.explicit and list the order which we want the collection sorted. We can create an list of enums of type state and then call Collections.sort. This is kind of neat way to order enums as many folks use enums values in drop down lists.

@Test
public void explicit_order_or_order_enum() {

    // Order states, an enum, in an explicit order
    Ordering<State> byMidwestState = Ordering.explicit(State.Illinois,
            State.Indiana, State.Iowa, State.Michigan, State.Minnesota,
            State.Ohio, State.Wisconsin);

    List<State> states = Lists.newArrayList(State.Iowa, State.Indiana,
            State.Wisconsin, State.Minnesota, State.Ohio, State.Illinois);

    Collections.sort(states, byMidwestState);

    System.out.println(states);
}
[Illinois, Indiana, Iowa, Minnesota, Ohio, Wisconsin]

Sort address by enum in specific order

[5:12]

In this snippet we will sort addresses by State enum in a specific order. We will read right to left, we will return a sorted copy of addresses then we will say Address.onState which is a function that accepts an address and returns a State enum. Then calling State.stateOrder we can specify an specific order, so what we are saying is we want to sort the addresses by the State enum in a specific order.

@Test
public void test() {

    // sort address by state enum in a specific order
    List<Address> addressesSorted = State.stateOrder.onResultOf(
            Address.onState).sortedCopy(addresses);

    System.out.println(addressesSorted);
}
[
Address{address=369 Pleasant Bear Heights, city=Goobertown, state=Alabama, zip=05427-6375},
Address{address=7037 Green Butterfly Swale, city=Ritzville, state=California, zip=82918-7511},
Address{address=8180 Middle Campus, city=Winklepleck Grove, state=Florida, zip=82648-0809},
Address{address=16 Emerald Impasse, city=Mozart, state=Washington, zip=05873-2086},
Address{address=7966 Iron Panda Freeway, city=Sour Dough Flat, state=Wisconsin, zip=82254-8067}
]

Compound

In this snippet we will show how to compound or combine multiple comparators first by creating an Ordering object from String.CASE_INSENSITIVE_ORDER and then call the compound or add the byLength comparator.

Comparator<String> byLength = new Comparator<String>() {
    public int compare(String left, String right) {
        return Integer.compare(left.length(), right.length());
    }
};

@Test
public void chain_comparators() {

    // When reading a chain of Ordering calls, work "backward" from right to left. 
    
    List<String> random = Lists.newArrayList("WELCOME1", "welcome", "to",
            "leveluplunch");

    List<String> sorted = Ordering.from(String.CASE_INSENSITIVE_ORDER)
            .compound(byLength).sortedCopy(random);
    
    System.out.println(sorted);
}
[leveluplunch, to, welcome, WELCOME1]

Thanks for joining in today's level up lunch, I hope you got a bit more out of the Ordering class that guava provides.