4

I have a list of objects which I want to sort. But I have three different conditions. That is why I have this code:

Collections.sort(myList, new Comparator<MyObject>() {
    @Override
    public int compare(MyObject o1, MyObject o2) {
        // my code
    }
});

Three times. First sorting all elements with condition x to the bottom of the list. Then a second time sorting all elements with condition y to the bottom and again for condition z.

Now I wonder how I could combine multiple conditions in one compare-method. So I don't have to do this three times.

Edit: To be more clear about the conditions. I want to sort all objects that have the criteria x to the bottom of the list. If an element fulfills criteria y it should be even below x and the same applies analog for z.

progNewbie
  • 4,362
  • 9
  • 48
  • 107
  • it's not clear what you mean by "condition" do you mean "sort criteria"? so sort by `x` and then by `y` and then by `z`? – Sharon Ben Asher Mar 14 '19 at 13:18
  • You could just have a `compareHelper` that takes a third parameter that indicates what attribute to sort by, then defer to that. We'd likely need to see the relevant code to make any suggestions though. At the very least, you could use lambda to make it more succinct. – Carcigenicate Mar 14 '19 at 13:19
  • Check guava Ordering https://www.baeldung.com/guava-ordering – Wojciech Wirzbicki Mar 14 '19 at 13:19
  • @SharonBenAsher I edited my code. Maybe this clears it. – progNewbie Mar 14 '19 at 13:21

3 Answers3

11

You can use Java Streams. This is also used when using Collection.sort():

myList.sort(Comparator.comparing(MyObject::getAttributeX)
    .thenComparing(i -> i.getSomething().getSubValue())
    .thenComparing((a, b) -> a.getInt() - b.getInt()));

If you are using a lower version than Java 8 you have to implement the sort logic yourself in a Comparator or use an external library:

Collections.sort(myList, new Comparator<MyObject>() {
    @Override
    public int compare(MyObject a, MyObject b) {
        int cmp0 = a.getAttributeX().compareTo(b.getAttributeX());
        if (cmp0 != 0) {
            return cmp0;
        }
        int cmp1 = a.getSomething().getSubValue().compareTo(b.getSomething().getSubValue());
        if (cmp1 != 0) {
            return cmp1;
        }
        return a.getInt() - b.getInt();
    }
});
Samuel Philipp
  • 10,631
  • 12
  • 36
  • 56
  • Unfortunately my API level in my android app where I want to apply this code is too low to use this feature. – progNewbie Mar 14 '19 at 13:26
  • Usage: There are no streams in the code in this answer. There are lambdas. It’s not the same. The connection is that lambdas are often (but not always) used with streams too. – Ole V.V. Mar 14 '19 at 13:58
  • 2
    @OleV.V. Another connection is that both lambdas and streams were introduced in Java 8, which is the issue OP is having with the stream solution. – daniu Mar 14 '19 at 14:16
  • Correction (again): Both were introduced in Java 8, which is the issue OP is having with the **lambda** solution. We tend to understand each other better when we use the correct letters, no, sorry, sentences, no, I mean the correct **words**. :-) – Ole V.V. Mar 14 '19 at 14:19
  • 2
    @OleV.V. Another correction, he does not have an issue with the lambda solution, but with the `Comparator.comparing` solution (which was also introduced in Java 8). Also, "Lambda" falls short, since the answer we're commenting parameterizes `Comparator.comparing` with a method reference, not a lambda. – daniu Mar 14 '19 at 14:30
  • 2
    Don’t use minus to implement a comparator. `a.getInt() - b.getInt()` can overflow. Use `Integer.compare(a.getInt(), b.getInt())`. In case of the comparator chaining you can use the even simpler `thenComparingInt(MyObject::getInt)` instead of `thenComparing((a, b) -> a.getInt() - b.getInt())`. – Holger Mar 19 '21 at 09:13
3

I am assuming that MyObject has methods like

public boolean isX() {
    return // either false or true;
}

Then sorting is easiest in this way:

    Collections.sort(myList,
            Comparator.comparing(MyObject::isX)
                    .thenComparing(MyObject::isY)
                    .thenComparing(MyObject::isZ));

Since isX etc. return boolean values, these values are sorted, false comes before true. So the sorting will make sure that all the objects that fulfil the x condition (isX returns true) will come at the end of the list. Among the remaining objects, those that fulfil y will be moved last, just before the x-s. Similarly for z.

What if instead x, y and z are determined by a method in the class doing the sorting? Let’s call it the Sorter class in this example. Such methods may look like:

public static boolean isY(MyObject obj) {
    return // either false or true;
}

All you need to do is replace MyObject::isX with Sorter::isX:

    Collections.sort(myList,
            Comparator.comparing(Sorter::isX)
                    .thenComparing(Sorter::isY)
                    .thenComparing(Sorter::isZ));

You may also mix, define some methods in Sorter and some in MyMethod.

What really happens is that the boolean values returned are boxed into Boolean objects that are then compared, but you need not be concerned with this detail.

EDIT: Version for lower Android API levels:

    Comparator<MyObject> xyzComparator = new Comparator<MyObject>() {

        @Override
        public int compare(MyObject o1, MyObject o2) {
            int diff = Boolean.compare(o1.isX(), o2.isX());
            if (diff != 0) {
                return diff;
            }
            diff = Boolean.compare(o1.isY(), o2.isY());
            if (diff != 0) {
                return diff;
            }
            diff = Boolean.compare(o1.isZ(), o2.isZ());
            // return whether 0 or not
            return diff;
        }
    };
    Collections.sort(myList, xyzComparator);

It even saves the auto-boxing mentioned above.

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
0

You could use a comparison chain for example:

   public int compareTo(Foo that) {
     return ComparisonChain.start()
         .compare(this.aString, that.aString)
         .compare(this.anInt, that.anInt)
         .compare(this.anEnum, that.anEnum, Ordering.natural().nullsLast())
         .result();
   }

See here: link to docs

thomasters
  • 183
  • 1
  • 15