java.util.Comparator.naturalOrder takes a <T extends Comparable<? super T>> and returns a Comparator<T> - why?
Asked Answered
A

2

9

(If this is a duplicate please point me to the right answer! I searched and read several (>5) related questions but none seemed on the mark. Also looked at the Generics FAQ and other sources...)

It is apparently proper practice that when a collection class takes a comparator it should have the type Comparator<? super T> for your parameterized type T. And you can see that lots of places, e.g., TreeMap. Okay.

My problem is working with Comparator.naturalOrder() which is parameterized on T extends Comparable<? super T> but returns a Comparator<T>. I'm trying to have a field in my collection class that holds either the user-specified comparator or the Comparator.naturalOrder comparator.

I can't get it to work. My questions, all related, are:

  1. How is Comparator.naturalOrder properly used?
    • And can I do what I want which is have a field where I store either a user-supplied comparator or the naturalOrder comparator?
  2. Given that most collection classes (in the framework) are parameterized on T not T implements Comparable<? super T>, so that's the design pattern chosen, how is naturalOrder useful since it requires the latter bounded wildcard, not the unconstrained type parameter?

Thanks!

Here follows the actual examples with compiler errors:

So: If I have code like this in some class where T has no bounds (as in all existing collection classes):

class Foo<T> {

    private Comparator<? super T> comparator;

    public void someMethod(Comparator<? super T> comparator)
    {
        this.comparator = comparator;                  // no compile error
        this.comparator = Comparator.naturalOrder();   // incompatible types
    }
}

with this error:

Error:(331, 50) java: incompatible types: inferred type does not conform to upper bound(s)
    inferred: T
    upper bound(s): java.lang.Comparable<? super T>

So if I decide to forgo the advantages of ? super T then I have:

class Foo<T> {

    private Comparator<T> comparator;

    public void someMethod(ComparatorT> comparator)
    {
        this.comparator = comparator;                  // no compile error
        this.comparator = Comparator.naturalOrder();   // incompatible types
    }
}

where I have

Error:(nnn, 50) java: incompatible types: inference variable T has incompatible bounds
    equality constraints: T
    upper bounds: java.lang.Comparable<? super T>
Assonance answered 4/7, 2014 at 0:55 Comment(0)
P
11

This compiles:

import java.util.*;

class Foo<T extends Comparable<? super T>> {

    private Comparator<T> comparator;

    public void someMethod(Comparator<T> comparator)
    {
       this.comparator = comparator;                  // no compile error
       this.comparator = Comparator.<T>naturalOrder(); // <T> is optional, compiler can infer
    }
}

The simplest way to think about it is this: you are trying to use type T with the Comparator interface, which imposes certain requirements on it (in particular it has that fancy recursive requirement that T must implement Comparable interface). You do not impose such requirement when genericising (?) your class, so compiler is not happy. Your requirements on T must be as strong as the class that you are using it with.

You are confused about what natural ordering method does. It just takes a class which implements Comparable and creates the default Comparator for it. No way around it -- you can't create a Comparator for something that is not Comparable.

You want TreeMap to require Comparable, but you can't, because it is a valid case to use something that is not comparable, as long as you have provided a Comparator. So TreeMap ends up not enforcing Comparable and just casts explicitly at runtime (and throws an exception).

Picofarad answered 4/7, 2014 at 1:17 Comment(7)
OK. I didn't really understand that syntax for specializing the generic method (to use C++ terminology that may not be appropriate).Assonance
But I see you put a bound on the type parameter to the class. That's what I'm interested in avoiding, if possible, because a) the rest of the (collections) framework doesn't work that way, and b) because you may have a type that doesn't extend Comparable in any way, e.g., javafx.util.Pair, and you just want to pass in an appropriate Comparator.Assonance
you are confused about what natural ordering method does. It just takes a class which implements Comparable and creates the default Comparator for it. No way around it -- you can't create a Comparator for something that is not Comparable. (also note that <T> is optional, compiler can infere it from the lvalue's type, but you do need the bound I added!)Picofarad
That was my confusion all right. Looking in the sources for the collections (like TreeMap) which are only declared with T I see they're casting to Comparable to get a comparator at run time. Thanks for explaining that!Assonance
Ha, I didn't realize that. This is pretty hilarious, right? You want TreeMap to require Comparable, but you can't, because it is a valid case to use something that is not comparable, as long as you have provided a Comparator. Sometimes Duck Typing does seem like a good idea.Picofarad
Yeah. Every time I get frustrated with Java or C# generics I have to whisper to myself "It isn't C++. Just accept it and move on." I think I'm going to look into it further: See if I can arrange something with generic methods on a wrapper "Tree" class that use internal more "raw" classes to do the data structure. That kind of thing isn't safe in general, but in this case where the internal data structure (and their types) would never escape the wrapper's API, it would just be up to me to make it correct, and it wouldn't impact a user.Assonance
That's the ugly truth about generics -- they are a thin wrapper that allows API designers pretend that things are type safe, while in fact the insides are terrible.Picofarad
C
2

I guess you have to implement Comparable to use Comparator.naturalOrder() in your class Foo in your example. You must have a method compareTo(Object o), that method is the one that implements the natural order so you don't need to store it in a variable.

I think you can use comparators in a class that doesn't implements the comparable interface at least in Java 8, so they don't implements compareTo(Object o) but you have to implement this

@FunctionalInterface
public interface Comparator<T>

This is from the Java 8 API

A comparison function, which imposes a total ordering on some collection of objects. Comparators can be passed to a sort method (such as Collections.sort or Arrays.sort) to allow precise control over the sort order. Comparators can also be used to control the order of certain data structures (such as sorted sets or sorted maps), or to provide an ordering for collections of objects that don't have a natural ordering.

A way of implementing and initialising it:

private  Comparator<Operario> ComparatorOperario =
    (o, p)-> o.getNombre().compareTo(p.getNombre());

then you can have getters and setters for this variable so you can change the way of ordering

note that the class Operario doesn't implements Comparable, it uses a Comparator<Operario> that compares 2 attributes from the class Operario, in this case two Strings.

Cleek answered 4/7, 2014 at 1:19 Comment(3)
I don't understand the part about @FunctionalInteface, how does it make a differencePicofarad
I use this kind of interfaces as a way of storing lamdas For example private Predicate<LocalDateTime> esActivo = esActivo = fecha -> ListaJornadas.stream().anyMatch(j->j.caeEnJornada((fecha))); and to use it esActivo.test(fecha)Cleek
for the @FunctionalInterface anotation it is a informative anotation uses when defining functional interface. You don`t have to use it to implement the interface Comparator since it is defined in the hava 8 api like Predicate, Function, Bifunction, Consumer... You can use it for defining new Functional interfaces if you need some one that not is in the api.Cleek

© 2022 - 2024 — McMap. All rights reserved.