Как вы относитесь к вложенным типам с использованием дженериков в Java?

Как создать общий класс, который относится к вложенным родовым типам?

Я пытаюсь создать класс Comparator, который может сравнивать внутренние типы B, не желая раскрывать то, что эти типы. В следующем примере я получаю предупреждение о компиляторе для того, чтобы сырые лить мои внутренние вложенные значения T в 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());
    }
}

Можно ли представить внутренние значения, чтобы безопасно разрешить кастинг?

Вещи, которые я пробовал:

  • Создание сопоставимого шаблона (несовместимого):

    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);
        }
    }
    
  • Объявление вторичного типового типа параметров (U), которое просто откладывает проблему:

    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();
    

Этот компаратор не должен сравнивать два экземпляра классов A, а часть их содержимого.

Ответ 1

Решение подстановочных знаков не работает

    class AComparator2<T extends B<?>> {
        public int compare(T o1, T o2)

потому что T здесь слишком свободен; мы не можем убедиться, что два T могут сравниваться друг с другом - возможно, что o1 является B<X1>, а o2 является B<X2>, а X1, X2 - два разных типа.

Ваше третье решение ограничивает T конкретным B<U>

    class AComparator3<T extends B<U>, U extends Comparable<U>>

это работает отлично; за исключением того, что сайт использования должен указать U, хотя U выводится из T.

    AComparator3<A, Integer>  
                    ^^^^^^^ duh!

Это раздражает. Та же проблема была задана ранее в других случаях использования. Нет хороших ответов.

К счастью, в вашем случае U не требуется нигде на сайте использования, поэтому мы могли бы просто использовать для него подстановочный знак

    AComparator3<A, ?> comparator = sscce.new AComparator3<>();
    comparator.compare(sscce.new A(), sscce.new A());

Фактически, компаратор является Comparator<A>, который, вероятно, все, что вам нужно. Также мы можем создать удобный метод, чтобы скрыть уродство new. Таким образом, вы можете сделать что-то вроде

    Comparator<A> comparator = sscce.comparator(); 

Ответ 2

Считаете ли вы решение Java 8?

 Comparator<A> comparator = ((t1,t2)-> t1.getValue().compareTo(t1.getValue()));
  comparator.compare(sscce.new A(), sscce.new A());

Ответ 3

Вам может быть интересен компаратор, который должен сравнивать типы, расширяющие B, но только если они имеют одинаковый сопоставимый тип. Такой компаратор может выглядеть как

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());
    }
}

и вы можете использовать его как

AComparator<Integer> comparator = sscce.new AComparator<>();
comparator.compare(sscce.new A(), sscce.new A());
comparator.compare(sscce.new A(), sscce.new A2());//compilation error

Ответ 4

Есть несколько вещей, которые вы должны изменить, чтобы достичь того, чего хотите, что я считаю, если просто реализовать Generic Comparator.

Во-первых, AComparator должен выглядеть так:

// 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);
    }
}

Вам не нужен ваш класс B, так как A и A2 будут реализовывать Comparable напрямую. Просто удалите его.

Ваши классы A и A2:

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
    }
}

Важно, чтобы вы прочитали документацию для Comparable, чтобы понять, что ожидается от возвращаемого значения.

Это имеет смысл?

PS: Я не тестировал эти коды, они просто из моей головы.

Ответ 5

Другим вариантом является то, что B реализует Comparable напрямую, поскольку вы используете getValue() для сравнения. Ниже приведено предупреждение:

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());
   }
}

Ответ 6

Я думаю, это то, что вы хотите:

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());
    }
}

Если вы не хотите, чтобы ваш компаратор мог сравнивать экземпляры двух разных подклассов B (например, A2 extends B<String> и A3 extends B<String>), работает следующее:

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());
    }
}