Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you refer to nested types using generics in Java?

Tags:

java

generics

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.

like image 300
SlopeOak Avatar asked Sep 25 '15 13:09

SlopeOak


4 Answers

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(); 
like image 95
ZhongYu Avatar answered Nov 18 '22 18:11

ZhongYu


Have you consider Java 8 solution?

 Comparator<A> comparator = ((t1,t2)-> t1.getValue().compareTo(t1.getValue()));
  comparator.compare(sscce.new A(), sscce.new A());
like image 26
user902383 Avatar answered Nov 18 '22 20:11

user902383


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
like image 3
Pshemo Avatar answered Nov 18 '22 18:11

Pshemo


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());
   }
}
like image 1
Nate Avatar answered Nov 18 '22 20:11

Nate