2

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.

jbaptperez
  • 656
  • 6
  • 20
  • 3
    How do you expect two `List` returned by getSomeStrings() to be compared? `List` doesn't implement `Comparable` – Eran May 16 '18 at 09:47
  • Try `thenComparing(a->a.getSomeStrings().containsAll(other.getSomeStrings()))` – Hadi J May 16 '18 at 09:49
  • 1
    @HadiJ it depends on what the OP thinks about how those lists should be comparable to begin with... I guess it could also be done via `.thenComparing(x -> x.getSomeStrings().stream().sorted().collect(Collectors.joining()))`, but unless the OP tells us how the comparison should be made, this is really unclear – Eugene May 16 '18 at 09:53
  • @Eugene, why you have used `stream` ? i think it is verbose. – Hadi J May 16 '18 at 09:56
  • @HadiJ I dont have to use, its just an example... – Eugene May 16 '18 at 09:57
  • @Eran: see my Edit 1. – jbaptperez May 16 '18 at 10:25
  • @jbaptperez How would you compare two Lists of Strings with String's compareTo? String's compareTo allows you to compare two Strings, not Lists. – Eran May 16 '18 at 10:27
  • @HadiJ: This works but I think the comparison is incomplete (neither List size test nor element ordering test). – jbaptperez May 16 '18 at 10:28
  • @Eran: Sure! So can the List loop be done implicitly (delegated to any List helper method)? Otherwise how to do that properly with lambdas (but it seems that I would reinvent the wheel)? – jbaptperez May 16 '18 at 10:31
  • Creating new `Comparator` instances on every evaluation of `compareTo` can become horribly inefficient, e.g. when putting it in a `TreeSet` or when sorting large lists of `MyClass`. You can create a `TreeSet` with that `Comparator` instead, using a single `Comparator` for all operations. So there is no need for `MyClass` to be comparable. – Holger May 16 '18 at 10:48
  • 2
    we understand that you want to compare your list of `String`s using `String.compareTo`; problem is suppose you want to compare `[a, b, c]` and `[d, b, a, c]`, I *can only assume* that the first one will be "less" than the second one (since it has less elements). but what if you would have `[a, b, c]` and `[c, b, a]` are these equal because they have the same content? Are these different because `a` comes before `c`? etc - this is what you need to define – Eugene May 16 '18 at 11:38
  • @Eugene: See my Edit 2. – jbaptperez May 16 '18 at 15:46
  • @Holger: Sure, when I understand how to do what I want, I will extract a `static` `Comparator` for performance. – jbaptperez May 16 '18 at 15:48
  • Possible duplicate of [Java Sort List of Lists](https://stackoverflow.com/questions/35761864/java-sort-list-of-lists) – Sean Van Gorder May 16 '18 at 17:34

1 Answers1

3

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());
}
Mạnh Quyết Nguyễn
  • 17,677
  • 1
  • 23
  • 51
  • Joining the strings only works if the strings are all the same length. This fails on [ab, c] > [a, c]. – Sean Van Gorder May 16 '18 at 17:25
  • 1
    I think, the order should be `a, aa, aaa, b, bb, bbb, c, cc, ccc`, in other words, the length should only be compared at the end, if one string is a prefix of the other. – Holger May 17 '18 at 06:02
  • @MạnhQuyếtNguyễn Thanks, this is what I wanted: using chaining `Comparator` methods in order to make my class `Comparable`. As I can inline the `Comparator>` declaration, this should solve my issue. – jbaptperez May 17 '18 at 12:06