开发者

Chaining order in Guava

开发者 https://www.devze.com 2023-02-10 10:35 出处:网络
I\'m a bit new to Guava and it\'s style. I\'m definitely digging it, but one thing I keep tripping over is the order of chained methods. Where I seem to have this problem the most is when using compou

I'm a bit new to Guava and it's style. I'm definitely digging it, but one thing I keep tripping over is the order of chained methods. Where I seem to have this problem the most is when using compound Orderings. I have to keep asking myself questions like:

  • Where does the natural go?
  • Where does the nullFirst (or last) go?
  • Which nullsFirst does what? (In the example below, one for host, one for last name, one for first name?)

Here's an example of one that I was just working on. It looks cumbersome, and I'm just not sure if I put it all together right. I ha开发者_开发百科ve some JUnits to test it, and it seems okay, but there are always those quirky boundary cases.

Ordering<Host> lastNameThenFirstNameOrdering = Ordering.natural().nullsFirst().onResultOf(new Function<Host, String>() {
    public String apply(Host host) {
        return host.getLastName();
    }}).compound(Ordering.natural().nullsFirst().onResultOf(new Function<Host, String>() {
    public String apply(Host host) {
        return host.getFirstName();
    }})).nullsFirst();

As for an actual question: Is there a well-defined rule for how these things get executed? It seems to be last-to-first, but I'm having trouble telling that.

edit: Just wanted to point out the large, ugly code I was trying to replace:

    Ordering<Host> ordering2 = new Ordering<Host>() {
        public int compare(Host host1, Host host2) {
            if (host1 == null || host2 == null) {
                return host1 == host2 ? 0 : ((host1 == null) ? -1 : 1); 
            }
            if(host1.getLastName() != null || host2.getLastName() != null){
                if (host1.getLastName() == null) {
                    return -1;
                } else if (host2.getLastName() == null) {
                    return 1;
                }

                if (host1.getLastName().compareTo(host2.getLastName()) != 0) {
                    return host1.getLastName().compareTo(host2.getLastName());
                }
            }

            if (host1.getFirstName() == null) {
                return -1;
            } else if (host2.getFirstName() == null) {
                return 1;
            }

            return host1.getFirstName().compareTo(host2.getFirstName());
        }};


I think what you do is correct, but awfully ugly. Try this for readability:

Use an Enum

Move the functions to an enum that implements Function<Host, String>. Each of the enum items can provide it's own implementation.

enum HostFunctions implements Function<Host, String>{
    GETFIRSTNAME{

        @Override
        public String apply(final Host host){
            return host.getFirstName();
        }
    },
    GETLASTNAME{

        @Override
        public String apply(final Host host){
            return host.getLastName();
        }
    }

}

Indent your Code

Now reference those enum functions and indent your code properly. This is what it will look like:

final Ordering<Host> orderingByLastAndFirstName =
    Ordering
        .natural()
        .nullsFirst()
        .onResultOf(HostFunctions.GETLASTNAME)
        .compound(
            Ordering
                .natural()
                .nullsFirst()
                .onResultOf(HostFunctions.GETFIRSTNAME))
        .nullsFirst();

I'd say that makes everything much more understandable.

IDE Configuration

Regarding proper indentation (at least if you use Eclipse), see this question:

How to indent the fluent interface pattern “correctly” with eclipse?

Enums as Functions

Regarding the enum: this is called the enum singleton pattern. The Guava guys use it all over their code base. Read about it on wikipedia or in Effective Java, Item 3. Although those sources both talk about single-item enums, the approach is almost the same here.


Each chaining call is "wrapping" the previous ordering into a new one, so you're right, the execution order can be thought of as "backwards".

I wrote and reviewed the Ordering class and I still regularly have to stop and scratch my head over the correct interleaving of nullsFirst(), and onResultOf() and reverse()!


The following would be my preference for doing this, assuming you must be able to handle null hosts, first names and last names. To me, it seems like a non-null first name and last name ought to be a requirement of the Host class. And you should generally try to avoid allowing collections to contain null objects.

Ordering<Host> lastNameFirstNameOrdering = new Ordering<Host>() {
  @Override public int compare(Host left, Host right) {
    return ComparisonChain.start()
      .compare(left.getLastName(), right.getLastName(), Ordering.natural().nullsFirst())
      .compare(left.getFirstName(), right.getFirstName(), Ordering.natural().nullsFirst())
      .result();
  }
}.nullsFirst();

Alternatively, I'd take an approach similar to Sean's but break things down for readability.

Ordering<Host> lastNameOrder = Ordering.natural().nullsFirst()
    .onResultOf(Host.LAST_NAME);
Ordering<Host> firstNameOrder = Ordering.natural().nullsFirst()
    .onResultOf(Host.FIRST_NAME);
Ordering<Host> orderingByLastAndFirstName =
     lastNameOrder.compound(firstNameOrder).nullsFirst();

Keep in mind that you could also make these individual orderings static final fields of the class, allowing you to easily use them anywhere when sorting like Host.LAST_NAME_ORDER.

0

精彩评论

暂无评论...
验证码 换一张
取 消