Универсальное программирование на Java с неизвестным универсальным типом интерфейса

Я использую несколько интерфейсов с универсальными типами. При объединении их вместе у меня возникают некоторые проблемы, когда мне приходится использовать их из части кода, которая не знает о конкретном типе универсального параметра.

Предположим, у меня есть следующий интерфейс:

public interface MyObjectInterface<T extends Number> {}

Объект, реализующий этот интерфейс, хранится в универсальной коллекции того же универсального типа:

public interface MyCollectioninterface<T extends Number> {
    public void updateObject(MyObjectInterface<T> o);
}

Конкретные экземпляры MyCollectionInterface содержат несколько MyObjectInterface одного и того же универсального параметра:

public class ConcreteCollection<T extends Number> implements
 MyCollectionInterface<T> {
    List<MyObjectInterface<T>> list;
    public void updateObject(MyObjectInterface<T> o){}

}

Теперь у меня есть несколько вопросов о том, как использовать эти универсальные интерфейсы из клиентского класса, который (и должен быть) не знает о конкретном типе дженериков.

Предположим, у меня есть следующий класс:

public class ClientClass{

    private MyCollectionInterface<?> collection;  //1st possibility
    private MyCollectionInterface collection;  //2nd possibility

    public ClientClass(MyCollectionInterface<?> collection){
        this.collection = collection;
    }

    public void foo(MyObjectInterface<?> o){
         this.collection.updateObject(o);  //this doesn't compile
    }
    public void foo(MyObjectInterface<? extends Number> o){
         this.collection.updateObject(o);  //this doesn't compile either
    }
    public void bar(MyObjectInterface o){
         MyObject b = o; //warning
         this.collection.updateObject(o);  //this compile but with warnings
    }
}

Первый вопрос:

  1. Учитывая тот факт, что ClientClass не заботится о том, какой конкретный тип, расширяющий Number, является коллекцией, должен ли я объявить коллекцию с символом "?" или без него? ? Если я использую вторую версию, я получаю следующее предупреждение:

MyCollectionInterface — это необработанный тип. Ссылки на универсальный тип LatticeInterface должны быть параметризованы.

Второй вопрос:

  1. Почему метод foo не компилируется?

Третий вопрос:

  1. Кажется, мне нужно использовать сигнатуру бара для вызова метода updateObject. В любом случае это решение выдает предупреждение при попытке назначить параметр MyObjectInterface, как в первом вопросе. Могу ли я удалить это предупреждение?

Последние вопросы:

  1. Я делаю что-то странное с этими универсальными интерфейсами, и я должен реорганизовать свой код?
  2. Мне действительно нужно заботиться обо всех этих предупреждениях?
  3. Как я могу использовать безопасный общий интерфейс из класса, где я не знаю его конкретного типа?

person Heisenbug    schedule 04.09.2011    source источник
comment
Разве не должно быть MyObjectInterface вместо MyObject в методах foo и bar?   -  person guardianpt    schedule 04.09.2011
comment
да, моя вина. Я починил это. Это MyObjectInterface.   -  person Heisenbug    schedule 04.09.2011


Ответы (1)


Хорошо, я немного поиграл с вашим кодом и пришел к выводу.

Проблема в том, что ваш ConcreteCollection (и его интерфейс MyCollectionInterface) объявляет метод updateObject как получающий аргумент типа MyObjectInterface<T> (где T extends Number) - обратите внимание, что тип является конкретным (а не подстановочным знаком).

Теперь в вашем клиентском классе вы получаете коллекцию и сохраняете ее как MyCollectionInterface<?> но экземпляр, который передается конструктору ClientClass', будет иметь конкретный тип, например:

new ClientClass(new ConcreteCollection<Integer>());

Это означает, что метод updateObject этого экземпляра будет принимать только аргумент типа MyCollectionInterface<Integer>.

Затем в методе foo вы пытаетесь передать MyObjectInterface<?> в updateObject, но, поскольку компилятор не знает, какой универсальный тип принимает ваша коллекция (это может быть Integer, как в моем примере, но также может быть Double или любой другой тип, расширяющий Number), он не позволит передать какой-либо объект.

Короче говоря, если вы объявите свою ссылку как MyCollectionInterface<?>, вы не сможете вызвать для нее updateObject. Итак, у вас есть два варианта:

1) Выберите тип бетона и придерживайтесь его:

private MyCollectionInterface<Number> collection;

public ClientClass(MyCollectionInterface<Number> collection){
    this.collection = collection;
}

public void foo(MyObjectInterface<Number> o){
     this.collection.updateObject(o);  //compiles
}

Но тогда вы ограничиваете коллекции, которые вы можете получить в своем конструкторе (что может быть неплохой идеей), или:

2) Измените интерфейс, чтобы он принимал подстановочный знак:

public interface MyCollectionInterface<T extends Number> {
    public void updateObject(MyObjectInterface<? extends Number> o);
}

public class ConcreteCollection<T extends Number> implements MyCollectionInterface<T> {
    List<MyObjectInterface<T>> list;
    public void updateObject(MyObjectInterface<? extends Number> o) {}
}

private MyCollectionInterface<?> collection;

public ClientClass(MyCollectionInterface<?> collection){
    this.collection = collection;
}

public void foo(MyObjectInterface<?> o){
     this.collection.updateObject(o);  //compiles
}

Кроме того, обратите внимание, что даже в 2) вы все равно можете столкнуться с той же проблемой при реализации метода updateObject, если только вы не объявите свой list примерно так (например, с ArrayList):

List<MyObjectInterface<? extends Number>> list = new ArrayList<MyObjectInterface<? extends Number>>();

В этом случае вы также можете удалить <T extends Number> из MyCollectionInterface и ConcreteCollection, так как T больше не используется.

@Ваши последние вопросы:

1) Вероятно, да
2) Вы должны
3) Вы не можете, если вам все равно, какие объекты вы храните в коллекции, вам следует вообще отказаться от дженериков.

Извините за длинный ответ, надеюсь, это поможет.

person guardianpt    schedule 04.09.2011
comment
Проблема не в методе foo. Даже если я изменю подпись, проблема будет здесь: public void updateObject(MyObjectInterface‹T›o){} . Даже если T объявлен как T extends Number, строка : this.collection.updateObject(o); не работает. Я могу объявить о даже ‹? расширяет Number›, и это тоже не сработает. - person Heisenbug; 06.09.2011