Search code examples
java-8comparable

Comparable class with a List<String> field


I have a simple class which stores an integer and a list of Strings.

As I want to use this class in a TreeSet<>, the one must be Comparable. But when trying to use the Java 8 Comparator class, I cannot compare my inner list. I have the following error:

Bad return type in method reference: cannot convert java.util.List to U

I think there is a very simple way to do that but I could not find it out.

How to do that?

public class MyClass implements Comparable<MyClass> {

    private final int          someInt;
    private final List<String> someStrings;

    public MyClass (List<String> someStrings, int someInt) {
        this.someInt = someInt;
        this.someStrings = new ArrayList<>(someStrings);
    }

    @Override
    public int compareTo(MyClass other) {
        return
                Comparator.comparing(MyClass::getSomeInt)
                        .thenComparing(MyClass::getSomeStrings) // Error here
                        .compare(this, other);
    }

    public int getSomeInt() {
        return someInt;
    }

    public List<String> getSomeStrings() {
        return someStrings;
    }
}

Edit 1

I just want the String list to be compared in the simplest way (using implicitly String.compareTo()).

Note that I do now want to sort my List<String> but I want it to be Comparable so that MyClass is also comparable and finally, I can insert MyClass instances into a TreeSet<MyClass>.

A also saw in the JavaDoc the following:

java.util.Comparator<T> public Comparator<T>
    thenComparing(@NotNull Comparator<? super T> other)

For example, to sort a collection of String based on the length and then case-insensitive natural ordering, the comparator can be composed using following code,

Comparator<String> cmp = Comparator.comparingInt(String::length)
   .thenComparing(String.CASE_INSENSITIVE_ORDER);

It seems to be an clue but I don't know how to apply it to this simple example.

Edit 2

Let's say I want my List<String> to be sorted the following way:

  • First check: List.size() (the shorter is less than the larger one);
  • Second check if sizes match: comparing one by one each element of both Lists until finding one where the String.compareTo method returns 1 or -1.

How to do that with lambdas in a my compareTo method?

Edit 3

This does not duplicates this question because I want to know how to build a comparator of a class which contains a List<String> with Java 8 chaining Comparable calls.


Solution

  • So to compare the list, first you check the length, then you compare each item with same indexes in both list one by one right?

    (That is [a, b, c] < [b, a, c])

    Make a custom comparator for list return join of your list string:

    Comparator<List<String>> listComparator = (l1, l2) -> {
         if (l1.size() != l2.size()) {
            return l1.size() - l2.size();
         }
         for (int i = 0; i < l1.size(); i++) {
            int strCmp = l1.get(i).compareTo(l2.get(i));
            if (strCmp != 0) {
                return strCmp;
            }
         }
         return 0; // Two list equals
    };
    

    Then you can compare using that custom comparator:

    @Override
    public int compareTo(MyClass other) {
        return  Comparator.comparing(MyClass::getSomeInt)
                        .thenComparing(Comparator.comparing(MyClass:: getSomeStrings , listComparator))
                        .compare(this, other);
    }
    

    If you want [a, b, c] = [b, a, c], then you have to sort those list first before comparing:

    public String getSomeStringsJoined() {
        return getSomeStrings().stream().sort(Comparator.naturalOrder()).collect(Collectors.joining());
    }