Nested Type Parameters in Java
Asked Answered
D

3

11

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.

Dryden answered 8/11, 2011 at 23:27 Comment(0)
Y
6

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.

Yentai answered 8/11, 2011 at 23:31 Comment(2)
The factory method idea is interesting, but it still bothers me that the type is specified twice even though my code dictates that they always be the same.Dryden
@RusselLeggett, ok, so you do need invariance then, no Collection<? super T>? Yep. That's a pain. My advice would be to deal with the complexity in your library and try to expose terse APIs by having factories whose return type is interface Foo<T> extends ComplicatedFoo<CollectionSubType<T>,T> so that clients can just use the single parameter version.Yentai
T
3

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);
}
Thorma answered 8/11, 2011 at 23:32 Comment(2)
I'm curious to see why this wouldn't be the solution as well.Unfounded
As I said, it's a contrived example, but what if the type of collection that went in mattered as well. It could be a list or a set or a TreeList. Let's say I added a getter to retrieve the collection again - the type would matter.Dryden
I
2

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

Invariable answered 9/11, 2011 at 2:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.