Nested Type Parameters in Java

2019-03-19 02:13发布

问题:

This is an example which I made up to be a simplification of my real code, so I apologize if it is a little contrived. What I would like to do is to effectively get two type parameters out of a single nested type argument. I'm pretty sure this is impossible, but I thought I'd give it a shot.

//Not legal java code
public class Foo<C extends Collection<T>> { //where T is another type parameter
    private C coll;

    public Foo(C coll) {
        this.coll = coll;
    }

    public void add(T elem){
        this.coll.add(elem);
    }
    //UPDATED TO ADD GETTER
    /**
     * I may need to retrieve the collection again, or pass it
     * on to another function that needs the specific C type
     */
    public C getColl(){
        return coll;
    }
}
...
List<String> strings = new ArrayList<String>();
Foo<List<String>> foo = new Foo<List<String>>(strings);
foo.add("hello");

I know that I could do it by adding another type parameter:

public class Foo<C extends Collection<T>,T>

but then I have to add the redundant:

Foo<List<String>,String> foo = new Foo<List<String>,String>(strings);

And in my real world case, my generics can sometimes be specified in the implements clause like

public class Bar implements Baz<String>

Having to specify that second type parameter is even more painful then, because it feels like it throws the implementation details in my face. Having to say

Foo<Bar,String>

when there is a relationship between String and Bar already, just seems inelegant. I get that its Java, so that goes with the territory, but just curious if there was a solution for this.

回答1:

It's not possible and I don't think it's ideal anyway because there is nothing in your existing class that requires invariance.

Foo<T,C extends Collection<T>>

could more generally be

Foo<T,C extends Collection<? super T>>

if the only reason to have T is to allow mutation of the collection.

Note, if you're concerned about having to specify two type parameters frequently, you can create a shallow subclass:

class DerivedFoo<T> extends Foo<Collection<T>,T>

and you can use factory methods to avoid having to double-specify at creation time

public static <T> Foo<Collection<T>,T> fromCollection(Collection<T> c)

You can also abstract the interface into an interface to get the benefits of concise types that you get with DerivedFoo above.



回答2:

Why wouldn't you just use T as your only type parameter, as in:

public class Foo<T> { //where T is another type parameter
private Collection<T> coll;

public Foo(Collection<T> coll) {
    this.coll = coll;
}

public void add(T elem){
    this.coll.add(elem);
}


回答3:

Prior to Java7, constructors don't do type inference, the workaround is to have a static factory method. That's no longer necessary. In Java 7 you can

Foo<List<String>,String> foo = new Foo<>(strings);

Regarding T and C, if we have 2 type parameters with constraints between them, there got to be some degree of redundancy. In your example, since one parameter C totally dictates the another parameter T, the redundancy seems unbearable. I don't see a solution.

But you probably can feel better if the type parameters are reordered

Foo<String,Bar> foo = new Foo<>(bar);

so we declare String first; then further provide a Baz<String> which is Bar