Обобщения в Java с использованием подстановочных знаков - PullRequest
4 голосов
/ 02 февраля 2009

У меня есть вопрос об Generics в Java, а именно об использовании подстановочных знаков. У меня есть пример класса GenClass, как это:

public class GenClass<E> {

    private E var;

    public void setVar(E x) {
        var = x;
    }

    public E getVar() {
        return var;
    }
}

У меня есть еще один простой класс:

public class ExampleClass {

}

Я написал следующий тестовый класс:

public class TestGenClass {

    public static void main(String[] str) {

        ExampleClass ec = new ExampleClass();

        GenClass<ExampleClass> c = new GenClass<ExampleClass>();

        c.setVar(ec);
        System.out.println(c.getVar());  // OUTPUT: ExampleClass@addbf1
    }

}

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

GenClass<?> c = new GenClass<ExampleClass>();

на месте:

GenClass<ExampleClass> c = new GenClass<ExampleClass>();

у компилятора нет проблем с этим новым оператором, однако он жалуется на

c.setVar(ec);

В нем говорится, что «метод (setVar ()) не применим для аргументов (ExampleClass)». Почему я получаю это сообщение?

Я подумал, что способ, которым я использовал подстановочный знак, заставляет ссылочную переменную c иметь тип GenClass, который будет принимать в качестве параметра любой класс - на месте E у меня будет любой класс. Это просто объявление переменной. Затем я инициализирую его с

new GenClass<ExampleClass>()

, что означает, что я создаю объект типа GenClass, который имеет в качестве параметра класс типа ExampleClass. Итак, я думаю, что теперь E в GenClass будет ExampleClass, и я смогу использовать метод setVar (), предоставив ему в качестве аргумента что-то типа ExampleClass. Это было мое предположение и понимание, но, похоже, Java не нравится, и я не прав. Любые комментарии приветствуются, спасибо.

Ответы [ 2 ]

14 голосов
/ 02 февраля 2009

Эта точная ситуация описана в Обучающем руководстве по Java .

Обратите внимание, что [с подстановочным знаком] мы все еще можем читать элементы из [универсального Collection] и давать им тип Object. Это всегда безопасно, поскольку независимо от типа коллекции она содержит объекты. Однако добавлять к нему произвольные объекты небезопасно:

Collection<?> c = new ArrayList<String>();
c.add(new Object()); // Compile time error

Поскольку мы не знаем, что означает тип элемента c, мы не можем добавлять к нему объекты. Метод add() принимает аргументы типа E, типа элемента коллекции. Когда фактический параметр типа равен ?, он обозначает некоторый неизвестный тип. Любой параметр, который мы передаем add, должен быть подтипом этого неизвестного типа. Поскольку мы не знаем, что это за тип, мы не можем ничего передать. Единственное исключение - null, которое является членом каждого типа.

(выделено мое)

2 голосов
/ 02 февраля 2009

mmyers имеет правильный ответ, но я просто хотел прокомментировать эту часть вашего вопроса (которая звучит как ваше обоснование для желания использовать подстановочный знак):

Я подумал, что способ, которым я использовал подстановочный знак, заставляет ссылочную переменную c иметь тип GenClass, который будет принимать в качестве параметра любой класс - на месте E у меня будет любой класс. Это просто объявление переменной. Затем я инициализирую это с

Если вы действительно хотите это сделать, вы можете сделать что-то вроде без ошибок компиляции:

GenClass<Object> gc = new GenClass<Object>();
gc.setVar(new ExampleClass());

Но опять же, если вы хотите объявить экземпляр GenClass, который может содержать любой тип, я не уверен, зачем вам вообще нужно использовать дженерики - вы можете просто использовать необработанный класс:

GenClass raw = new GenClass();
raw.setVar(new ExampleClass());
raw.setVar("this runs ok");
...