Sharing a wildcard in Java generics

329 views Asked by At

Suppose I have an interface

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

and an object of this type with unknown parameter: Foo<?> baz. Then I can call baz.foo(baz.bar()).

However, now I need to put the value baz.bar() into a collection and call baz.foo() on it later on. Something like

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));

This doesn't work either:

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

Is there a way to do this?

EDIT: The above was oversimplified from my actual situation. Say we have

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

There are 3 answers

1
Tom Anderson On BEST ANSWER

This is an awkward problem.

The solution is to find a scope which encloses both the uses of the wildcard, put a type variable on it, replace the wildcards with references to the type variable, and then bind the type variable.

In your case, it sounds like the class is the necessary scope. So:

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));
    }
}

The problem is that you have added a type variable to the class. Now, everywhere that this class is used, any mention of its type needs to have a type bound (so Whatever<String>, or Whatever<?>). That can be incredibly annoying if the code using the class doesn't really care about the type of the things it's keeping in its list.

To get around this, you can create a generic holder class. Something like:

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));
        }
    }
}

But that's pretty ugly. You're introducing a whole new class, and the runtime overhead of a new object, just to work around some annoying generics.

0
Lukas Eder On

You can wrap your logic in a generic method like this:

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

That's the only way I know of, to achieve what you want, without resorting to unsafe casting.

After the OP's edit:

As others have mentioned, the outer class Bar needs to know the common generic type T. Besides, with your constraint of not being able to change the signature of Baz.getFoo(), I guess you'll have to unsafe cast Foo<?> to Foo<T> in the constructor of 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));
    }
}

Alternatively (as Foo, Bar, Baz are not good means of understanding what your real-life use-case does), you can still avoid generifying Bar like this:

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));
    }
}
2
Piotr Gwiazda On

How is it possible, that outside the scope of Foo<T> you don't know what T is?

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

or

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

Accoring to edit

You must parametrize your outer class too then

    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();
    }