Как вы относитесь к вложенным типам с использованием дженериков в 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());
}
}