0

I have an abstract class A<T> with generic wildcard T. There are two extensions of A,

public class A1 extends A<Integer> {...}

and

public class A2 extends A<String> {...}

Now I have another class, let's call it B, that is basically a collection of either A1 or A2. So I defined it as a class of generic type T extends A:

public class B<T extends A> {...}

What I would like to be able to do is, within B, create methods that return the type of T's generic. For example,

B<A1> foo = new B<A1>;
foo.get(); // returns Integer, corresponding to A1's generic type

and

B<A2> bar = new B<A2>;
bar.get(); // returns String, corresponding to A2's generic type

Is it possible to do this in Java? I'm having trouble figuring out what return type to put when declaring B's methods (if I wanted B to return T, I'd put public T get() {...}, but I actually want to return the parametrized type of T). Or is there a pattern that solves this problem better than the way I'm approaching it?

Thanks.

tytk
  • 2,082
  • 3
  • 27
  • 39
  • `T` is the generic type of `T`, and `B` (as posted) for both `foo` and `bar` is a [`Raw Type`](http://docs.oracle.com/javase/tutorial/java/generics/rawTypes.html) (which is another way of saying it isn't generic). – Elliott Frisch Feb 02 '15 at 05:42

2 Answers2

3

You can declare a second type parameter for B and have that be the parameterization of A

class B<K, T extends A<K>> {
    public K get() {
        // actual implementation
        return null;
    }
}

Then declare your variables

B<String, A2> var = new B<>();
String ret = var.get();

What is a raw type and why shouldn't we use it?

Community
  • 1
  • 1
Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
  • 1
    You might also be able to remove the parameter `T`, if you don't do anything else with it. – user253751 Feb 02 '15 at 05:47
  • Thanks for the quick answer. @Sotirios, is this a bad design to use? I'm curious about your link to raw types. I guess I meant `B foo = new B`, etc (fixed). Does that change your answer at all? @immibis, I would like `B` to be associated with `T` rather than `K` and I thought that the encapsulated information of `K` might somehow be retrieved from `T`... – tytk Feb 02 '15 at 06:02
  • @tytk `A` is a generic type. If you don't parameterized it, you're getting its raw version. So in your snippet, `B` was using `A` as a raw type. – Sotirios Delimanolis Feb 02 '15 at 06:04
  • Ahh I see, thank you. So is the extra parameter necessary then? Couldn't I just declare `class B>`? – tytk Feb 02 '15 at 06:07
  • 1
    @tytk `K` is used as a type. It must exist, ie. be declared somewhere. If it's a type variable, it needs to be declared as a type variable, in the generic type parameter declaration list. – Sotirios Delimanolis Feb 02 '15 at 06:08
  • Hmm. Interesting. Makes sense, it just seems a tad verbose to have to include a type that's already part of the definition of A1 or A2. Thank you. – tytk Feb 02 '15 at 06:10
  • @tytk If you didn't care about the parameter of `A`, you would use `class B>` – user253751 Feb 02 '15 at 06:20
1

I would like to provide an example that may help you better understand why we would need two generic type parameters. First, the sample declarations of the A and its direct subclasses .

class A<T> {

    private T value;

    public A(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }   
}

class A1 extends A<Integer> {
    A1(Integer i) {
        super(i);
    }
}

class A2 extends A<String> {
    A2(String str) {
        super(str);
    }
}

Now, the B class declaration. Notice, how it references both the generic types in its definition to provide access to the members of the wrapped A subclass.

public class B<T, X extends A<T>> {

    private X data;

    public B(X data) {
        this.data = data;
    }

    public T get() {
        return data.getValue();
    }

    public static void main(String[] args) {

        B<Integer, A1> foo = new B<Integer, A1>(new A1(10));
        System.out.println(foo.get()); // returns Integer

        B<String, A2> bar = new B<String, A2>(new A2("Ten"));
        System.out.println(bar.get()); // returns String
    }
}

If you switch to just one type parameter, you have two options.

You either keep the type of the A subclass like

public class B<T extends A<?>> {

    private T data;

    public B(T data) {
        this.data = data;
    }

    public Object get() {
        return data.getValue();
    }

    ...
}

Or, the actual return type of the getter.

public class B<T> {

    private T value;

    @SuppressWarnings("unchecked")
    public B(A<?> data) {
        this.value = (T) data.getValue();
    }

    public T get() {
        return value;
    }

    ... 
}

Notice, how both the solutions are fragile.

One returning an Object and requiring the use of instanceof to cast it properly and the other even more brittle with an unchecked cast. Since, you're not providing both the generic types you need, you've constrained your class design and run the risk of getting a ClassCastException now.

Ravi K Thapliyal
  • 51,095
  • 9
  • 76
  • 89
  • Thank you! This was a very clear example. It still seems a little unnecessarily verbose to me but I guess that's how Java is. Already accepted the other answer but upvoted this one for helpfulness. – tytk Feb 02 '15 at 06:43
  • Just updated my answer to explain: why, what seems like verbosity in Java, is actually required to make the class completely type safe. – Ravi K Thapliyal Feb 02 '15 at 06:57