Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Generics: Returning object of generic class with type parameter that is a super of argument T

Tags:

java

generics

I am trying to accomplish something with Java generics and am having a hell of a time since the approaches I would instinctively take do not work in this language, due to type erasure. I can cobble something reasonable together, though, if I get get the following to work. I have two classes:

class A {}

class B extends A {}

And a third class:

class C<T extends A> {}

I want a generic factory method that will return an instance of C, if I give it an instance of either A or B. A first attempt might look like this:

<T extends A> C<T> getObject(T t) { ... }

The issue I am having, which I suppose is not very unique, is most easily explained with the following line:

B b = new B();
C<A> = getObject(b);

The way I have defined getObject, the above call will return an object of type C<B>, which is not C<A> and does not inherit from C<A>, so this will not compile.

What I want to know is: is it possible to invoke some generics magic so that the compiler chooses the generic type T in getObject to be the superclass involved in the above function call, namely A?

Thanks so much!

like image 803
user3545992 Avatar asked Oct 20 '22 10:10

user3545992


2 Answers

One thing you can do is change the declaration to be like this:

// (I've deviated from Java generic naming convention for clarity)
<TRet extends A, TArg extends TRet> C<TRet> getObject(TArg arg) { ... }

So now the return type and argument type are inferred independently:

  • TRet is inferred based on the assignment type of the return value.
  • TArg is inferred based on the type of the argument.

But TArg still must be TRet or a subclass of TRet. (Or looking at it in the other direction, TRet must be TArg or a superclass of TArg.)

Though I think that is a bit of an ordeal. Too, it's already been mentioned in comments that the singular-typed version compiles on Java 8.

But this:

I can cobble something reasonable together, though, if I get get the following to work.

kind of makes me wonder. It seems like you've described an attempted solution but not the actual problem and it makes me wonder if our suggestions are actually helpful here. If they are, excellent, but if they aren't, don't hesitate to edit the OP or comment with clarification.

like image 133
Radiodef Avatar answered Oct 23 '22 10:10

Radiodef


I am going ahead and "steal the answer" (see question comments :)).

There is nothing wrong with your class definition. Sometimes you just need to give a hint to the compiler to help it with type inference (guessing what it type should be substituted instead of the generic parameter) when calling a generic method:

public class Test {

    static class A { }
    static class B extends A { }
    static class C<T extends A> { }

    public static <T extends A> C<T> getObject(T obj) {
        return null; // We don't need to construct anything here
    }

    public static void main(String[] args) {
        // This compiles just fine
        C<A> result1 = Test.<A>getObject(new B());
        // Of course you can just type-cast the argument
        C<A> result2 = Test.getObject((A) new B());
    }

}

UPDATE (again, credits to Sotirios) Java 8 clearly has an improved generic type inference and your code would work without the explicit parameter specification as shown above (i.e. C<A> result = getObject(new B()); would simply work out of the box).

like image 29
Pavel Horal Avatar answered Oct 23 '22 11:10

Pavel Horal