Принуждение общих параметров Java к одному типу

Как можно добиться аналогичной функциональности без ошибок?

class A<K> {
   void f(K x) {}
}

void foo(A<? extends X> a, X x) {
    a.f(x); // AN error: The method f(capture#1-of ? extends X) in the 
            // type A<capture#1-of ? extends X> is not applicable for the 
            // arguments (X)
}

Я знаю, что это происходит потому, что "a" может быть экземпляром A < "non-X" > , поэтому его "f" не должен принимать экземпляр X в качестве параметра, но как я могу заставить параметры быть одного типа?

Вот еще код:

Класс тестирования:

class Test {
   <T> void foo(A<T> a, T x) {
   a.f(x); // now it works!
 }
}

В некотором классе:

Container<X> container;
public void test() {
    X x = new X();
    new Test().foo(container.get(), x);
}

Здесь контейнерный класс:

public class Container<K> {
    A<? extends K> get() {
    return new A<K>();
    }
}

Ответ 1

Вы можете заставить параметры быть одного типа, выполнив следующие действия:

// the first class, A<K>:
class A<K> {
  void f(K x) {}
}

// the second class, defining the method with generic type parameters
class Test {
  <T> void foo(A<T> a, T x) {
    a.f(x); // now it works!
  }
}

// a third class, that uses the above two:
class Main {
  public static void main(final String... args) {
    final Test test = new Test();
    final A<String> a = new A<>();
    test.foo(a, "bar");
  }
}

Что это такое: метод foo определяет параметр типового типа T и использует его для обеспечения того, чтобы параметр типа K класса A должен соответствовать типу x, второй параметр foo.

Вы можете даже наложить ограничения на <T>, если хотите, и если это имеет смысл для вашей проблемы, например <T extends Bar> void foo(A<T> a, T x) {...} или super. Вы хотели бы этого, если, как сказал Джони в комментарии в вопросе, x на самом деле является типом, а не параметром типа: вы должны использовать <T extends X> void foo(...).


После того, как вы указали больше кода, проблема становится ясной.

Метод .get() контейнера возвращает экземпляр A<? extends K>. Поэтому параметр типа экземпляра, который вы получаете из .get(), не указан полностью. Как правило, это не очень хороший дизайн для возврата такого неуказанного типа. Для видео-презентации с Джошуа Блохом, автором Effective Java и многих API-интерфейсов и функций на Java, показывающих, как улучшить такой API, проверьте это: http://www.youtube.com/watch?v=V1vQf4qyMXg&feature=youtu.be&t=22m. В точности 25'36 ", Джошуа Блох говорит:" Не пытайтесь использовать их [подстановочные типы] для возвращаемых значений ", и он объясняет это позже. В принципе, вы не получаете больше гибкости, используя их, и просто делаете это очень тяжело для пользователей API, чтобы справиться с ним (вы просто почувствовали эффект от этого...).

Чтобы исправить ошибку, вы можете просто попытаться изменить подпись .get() на A<K> get(), поэтому класс контейнера будет выглядеть следующим образом:

public class Container<K> {
  A<K> get() {
    return new A<K>();
  }
}

Поскольку вы знаете, что get() возвращает экземпляр A<K>, нет причин использовать старшую подпись: он просто заставляет вас потерять информацию, которую вы уже знаете!

И если это все еще не работает, ваша проблема может быть где-то в другом месте, и вам нужно будет показать еще больше кода... или еще лучше, задайте другие вопросы!:)

Ответ 2

Принимая во внимание правило PECS и учитывая способ использования X, вы должны указывать как нижнюю, а не верхнюю границу:

void foo(A<? super X> a, X x)

Таким образом не создаются ошибки компилятора, и вы используете наиболее общую подпись.