How do you create a generic class that refers to nested generic types?
I'm trying to create a Comparator class which can compare the inner types of B without wanting to expose what those types are. In the following example I get a compiler warning for raw casting my T inner nested values to Comparable:
public class SSCCE {
// Compare my A instances.
class AComparator<T extends B> implements Comparator<T> {
@Override
public int compare(final T o1, final T o2) {
return o1.getValue().compareTo(o2.getValue());
}
}
class A extends B<Integer> {
@Override Integer getValue() { return 1; }
}
class A2 extends B<String> {
@Override String getValue() { return "Test String!"; }
}
abstract class B<T extends Comparable<T>> {
abstract T getValue();
}
public static void main(String[] args) {
SSCCE sscce = new SSCCE();
AComparator<A> comparator = sscce.new AComparator<>();
comparator.compare(sscce.new A(), sscce.new A());
}
}
Is it possible to represent the inner values using to safely allow casting?
Things I've tried:
Creating a wildcard comparable (uncompilable) :
class AComparator2<T extends B<? extends Comparable<?>>> implements Comparator<T> { @Override public int compare(final T o1, final T o2) { Comparable<?> o1value = (Comparable) o1.getValue(); Comparable<?> o2value = (Comparable) o2.getValue(); return o1value.compareTo(o2value); } }
Declaring a secondary generic parameter type (U), which simply postpones the problem:
class AComparator3<T extends B<U>, U extends Comparable<U>> implements Comparator<T> { @Override public int compare(final T o1, final T o2) { U o1value = o1.getValue(); U o2value = o2.getValue(); return o1value.compareTo(o2value); } } ... AComparator3<A, Comparable<U>> comparator = sscce.new AComparator3();
This comparator isn't to compare two instances of the classes A, rather part of their contents.
The wildcard solution does not work
class AComparator2<T extends B<?>> {
public int compare(T o1, T o2)
because T
is too loose here; we can't make sure two T
's can compare to each other -- it's possible that o1
is a B<X1>
and o2
is a B<X2>
, and X1, X2
are two different types.
Your 3rd solution restricts T
to a specific B<U>
class AComparator3<T extends B<U>, U extends Comparable<U>>
this works perfectly; except that the use site has to specify U
, even though U
is deducible from T
.
AComparator3<A, Integer>
^^^^^^^ duh!
This is annoying. The same problem has been asked before from other use cases. No good answers.
Fortunately, in your case, U
isn't needed anywhere on use site, therefore we could simply use a wildcard for it
AComparator3<A, ?> comparator = sscce.new AComparator3<>();
comparator.compare(sscce.new A(), sscce.new A());
In fact, the comparator is a Comparator<A>
, which is probably all you need. Also we can create a convenience method to hide the ugliness of new
. So you may do something like
Comparator<A> comparator = sscce.comparator();
Have you consider Java 8 solution?
Comparator<A> comparator = ((t1,t2)-> t1.getValue().compareTo(t1.getValue()));
comparator.compare(sscce.new A(), sscce.new A());
You may be interested in comparator which should compare types extending B but only if they hold same comparable type. Such comparator may look like
class AComparator<T extends Comparable<T>> implements Comparator<B<T>> {
@Override
public int compare(final B<T> o1, final B<T> o2) {
return o1.getValue().compareTo(o2.getValue());
}
}
and you can use it like
AComparator<Integer> comparator = sscce.new AComparator<>();
comparator.compare(sscce.new A(), sscce.new A());
comparator.compare(sscce.new A(), sscce.new A2());//compilation error
There are a few things you have to change to achieve what you want, which I believe if just implement a Generic Comparator .
First, AComparator should look like:
// Compare my A instances.
class AComparator<T extends Comparable<T>> implements Comparator<T> {
@Override
public int compare(final T o1, final T o2) {
return o1.compareTo(o2);
}
}
You don't need your class B, since A and A2 will implement Comparable directly. Just delete it.
Your A and A2 classes:
class A implements Comparable<A> {
@Override public int compareTo(A other) {
// your compare logic here
// return negative if less than, 0 if equal, positive if greater than
}
}
class A2 implements Comparable<A2> {
@Override public int compareTo(A2 other) {
// your compare logic here
// return negative if less than, 0 if equal, positive if greater than
}
}
It is important that you read the documentation for Comparable , to understand what is expected from the returned value.
Does that makes sense?
PS: I didn't test those codes, they are just out of my head.
Another option is to have B implement Comparable directly, since you are using getValue() to do the compare. The below gets rid of the warning:
import java.util.Comparator;
public class SSCCE {
class A extends B<Integer> {
@Override Integer getValue() { return 1; }
}
class A2 extends B<String> {
@Override String getValue() { return "Test String!"; }
}
abstract class B<T extends Comparable<T>> implements Comparable<B<T>>{
abstract T getValue();
@Override
public int compareTo(B<T> other)
{
return getValue().compareTo(other.getValue());
}
}
public static void main(String[] args) {
SSCCE sscce = new SSCCE();
Comparator.naturalOrder().compare(sscce.new A(), sscce.new A());
}
}
I guess this is what you want:
public class SSCCE {
static class BComparator<E extends Comparable<E>> implements Comparator<B<E>> {
@Override
public int compare(final B<E> o1, final B<E> o2) {
return o1.getValue().compareTo(o2.getValue());
}
}
static class A extends B<Integer> {
@Override Integer getValue() { return 1; }
}
static class A2 extends B<String> {
@Override String getValue() { return "Test String!"; }
}
static abstract class B<T extends Comparable<T>> {
abstract T getValue();
}
public static void main(String[] args) {
SSCCE sscce = new SSCCE();
BComparator<Integer> comparator = new BComparator<>();
comparator.compare(new A(), new A());
BComparator<String> comparator2 = new BComparator<>();
comparator2.compare(new A2(), new A2());
}
}
If you don't want your comparator to be able to compare instances of two different subclasses of B (like A2 extends B<String>
and A3 extends B<String>
), the following works:
public class SSCCE {
static class BComparator<E extends Comparable<E>, T extends B<E>> implements Comparator<T> {
@Override
public int compare(final T o1, final T o2) {
return o1.getValue().compareTo(o2.getValue());
}
}
static class A extends B<Integer> {
@Override Integer getValue() { return 1; }
}
static class A2 extends B<String> {
@Override String getValue() { return "Test String!"; }
}
static abstract class B<T extends Comparable<T>> {
abstract T getValue();
}
public static void main(String[] args) {
SSCCE sscce = new SSCCE();
BComparator<Integer, A> comparator = new BComparator<>();
comparator.compare(new A(), new A());
BComparator<String, A2> comparator2 = new BComparator<>();
comparator2.compare(new A2(), new A2());
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.