Совместное использование подстановочного знака в дженериках Java - PullRequest
3 голосов
/ 20 января 2012

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

interface Foo<T> { 
    void foo(T x); 
    T bar() 
}

и объект этого типа с неизвестным параметром: Foo<?> baz. Тогда я могу позвонить baz.foo(baz.bar()).

Однако теперь мне нужно поместить значение baz.bar() в коллекцию и позже вызвать baz.foo(). Что-то вроде

List<???> list; // can I tell the compiler this is the same type as baz's wildcard?
list.add(baz.bar());
...
baz.foo(list.get(1));

Это тоже не работает:

List<Object> list;
list.add(baz.bar());
...
baz.foo((???) list.get(1)); // I can't write down the type I need to cast to

Есть ли способ сделать это?

РЕДАКТИРОВАТЬ: Выше было упрощено из моей реальной ситуации. Скажем, у нас есть

class Bar {
    private final Foo<?> foo;
    private List<???> list; // the type argument can be selected freely

    Bar(Baz baz) {
        foo = baz.getFoo(); // returns Foo<?>, can't be changed 
    }

    void putBar() {
        list.add(foo.bar());
    }

    void callFoo() {
        foo.foo(list.get(0));
    }
}

Ответы [ 3 ]

7 голосов
/ 20 января 2012

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

public <T> void myMethod(Foo<T> baz) {
  List<T> list; // initialise it...
  list.add(baz.bar());
  // ...
  baz.foo(list.get(1));
}

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

После редактирования ОП :

Как уже упоминалось, внешний класс Bar должен знать общий универсальный тип T. Кроме того, с вашим ограничением невозможности изменить подпись Baz.getFoo(), я думаю, вам придется небезопасно приводить Foo<?> к Foo<T> в конструкторе Bar<T>:

class Bar<T> {

    private final Foo<T> foo;
    private List<T> list;

    Bar(Baz baz) {
        // Since baz cannot be changed, you will have to
        // unsafe cast Foo<?> to Foo<T> here.
        foo = (Foo<T>) baz.getFoo();
    }

    void putBar() {
        list.add(foo.bar());
    }

    void callFoo() {
        foo.foo(list.get(0));
    }
}

В качестве альтернативы (поскольку Foo, Bar, Baz не являются хорошим средством для понимания того, что делает ваш реальный сценарий использования), вы все равно можете избежать генерации Bar следующим образом:

class Bar {

    private final Foo<Object> foo;
    private List<Object> list;

    Bar(Baz baz) {
        // Since baz cannot be changed, you will have to
        // unsafe cast Foo<?> to Foo<Object> here.
        foo = (Foo<Object>) baz.getFoo();
    }

    void putBar() {
        list.add(foo.bar());
    }

    void callFoo() {
        foo.foo(list.get(0));
    }
}
4 голосов
/ 20 января 2012

Это неуклюжая проблема.

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

В вашем случае кажется, что класс является необходимой областью действия.Итак:

public class Whatever<T> {
    private List<T> list;
    private Foo<T> baz;

    public void bazToList() {
        list.add(baz.bar());
    }

    public void listToBaz() {
        baz.foo(list.get(1));
    }
}

Проблема в том, что вы добавили переменную типа в класс.Теперь везде, где используется этот класс, любое упоминание его типа должно иметь привязку к типу (например, Whatever<String> или Whatever<?>).Это может быть невероятно раздражающим, если код, использующий класс, на самом деле не заботится о типе вещей, которые он хранит в своем списке.

Чтобы обойти это, вы можете создать универсальный класс-держатель.Что-то вроде:

public class Whatever {
    private WhateverHolder<?> holder;

    public void bazToList() {
        holder.bazToList();
    }

    public void listToBaz() {
        holder.listToBaz();
    }

    private static class WhateverHolder<T> {
        private List<T> list;
        private Foo<T> baz;

        public void bazToList() {
            list.add(baz.bar());
        }

        public void listToBaz() {
            baz.foo(list.get(1));
        }
    }
}

Но это довольно уродливо.Вы вводите совершенно новый класс и накладные расходы на новый объект во время выполнения, просто чтобы обойти некоторые надоедливые обобщения.

1 голос
/ 20 января 2012

Как это возможно, что за пределами Foo<T> вы не знаете, что такое T?

            Foo<String> obj = ...; //
            List<String> list = new ArrayList<>();
            list.add(obj.bar());

или

            Foo<MyClass> obj = ...; //
            List<MyClass> list = new ArrayList<>();
            list.add(obj.bar());

В соответствии с правкой редактирования

Вы также должны параметризовать свой внешний класс

    class OuterClass<S> {

        private List<S> list;

        private Foo<S> baz;

        public void doSomething() {
            Foo<S> obj = ... Consctruct it somehow;
            List<S> list = new ArrayList<>();
            list.add(obj.bar());                
        }


        public static void main(String[] args) throws InterruptedException {


        }
    }

    interface Foo<T> { 
        void foo(T x); 
        T bar();
    }
...