Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java generics puzzler with generic static factory

Tags:

java

generics

I recently started writing a generic object mapper for a project and ran into something I don't quite understand. Given the following:

public class G<X> {
  public G(Class<X> c) { }

  public void m(X x) { }

  public static <T> G<T> create(Class<T> c) {
    return new G<T>(c);
  }

  public static void main(String[] args) {
    Object o = ""; // irrelevant!
    G<?> t = create(o.getClass());
    t.m(o);
  }
}

I get the following compilation error:

m(capture#402 of ?) in G<capture#402 of ?> cannot be applied to (java.lang.Object)

I can't seem to figure out a way to properly cast t to make this compile. What am I missing? Using JDK 1.6.

EDIT:

This is not an academic question. I'm trying to write a mapper from hibernate objects to their corresponding DTO to be passed around in the REST layer. The assumption is that for each ORM object Foo, there might exist a class FooDTO that has a constructor that takes an instance of Foo as a parameter. The generic class that maps Foo to FooDTO will encapsulate this assumption and throw appropriate exceptions if FooDTO doesn't exist or doesn't have the proper constructor:

class Mapper<Foo,FooDTO> {
  private final Constructor<FooDTO> dtoConstructor;
  Mapper(Class<Foo> fooClass, Class<FooDTO> fooDTOClass){
    // find the constructor of FooDTO or throw ...
  }
  public FooDTO map(Foo f){
    return dtoConstructor.newInstance(f);
  }
  // this factory is for convenience when we don't know the type of FooDTO:
  public static Mapper<X,Object> create(Class<X> fromClass){
    Class<Object> dtoClass = // ... find it
    return new Mapper<X,Object>(fromClass,dtoClass);
  }
}

This seems to break if I pass a generic object class to create.

Note that my actual implementation has all FooDTO classes extends from a generic super class, i.e., the signature of Mapper is actually something like Mapper<Foo,DTO<Foo>>. I don't think that's relevant here.

EDIT 2:

Actually the suggestion of changing the line G<?> t = create(o.getClass()); to G<Object> t = (G<Object>) create(o.getClass()); worked in this context.

Unfortunately I didn't realize that the fact that my class is more complex actually has an impact. Here's a more complete example (I apologize for the piecemeal question):

public class Y<T> {
}

public class G<X, Z extends Y<X>> {
  public G(Class<X> c, Class<Z> s) {
  }

  public void m(X x) {
  }

  public static <T, S extends Y<T>> G<T, S> create(Class<T> c) {
    Class<S> s = null; // find this via some reflection magic
    return new G<T, S>(c, s);
  }

  public static void main(String[] args) {
    Object o = ""; // irrelevant!
    G<? extends Object, Y<? extends Object>> t = create(o.getClass());
    t.m(o);
  }
}

In this case the object Class<S> is created using reflection and some conventional location for objects of that type. That part works fine and should be irrelevant to this discussion. The error I am getting now is the following:

inconvertible types
found   : G<capture#155 of ? extends java.lang.Object,Y<capture#155 of ? extends java.lang.Object>>
required: G<java.lang.Object,Y<java.lang.Object>>

And if I change the incriminated line to:

G<Object, Y<Object>> t = (G<Object, Y<Object>>) create(o.getClass());

I get a similar error:

java: inconvertible types
required: G<java.lang.Object,Y<java.lang.Object>>
found:    G<capture#1 of ? extends java.lang.Object,Y<capture#1 of ? extends java.lang.Object>>

Once again, I apologize for the piecemeal information. I am sorting through this while I am writing.

like image 808
Giovanni Botta Avatar asked Mar 12 '14 20:03

Giovanni Botta


2 Answers

You have passed the Class object from the getClass() method, which returns a Class<?>, meaning that you had to declare t to be a G<?>.

You cannot call a method with a generic type parameter when the generic type parameter of the variable is a wildcard. The compiler doesn't know which specific class the wildcard really is, so it cannot guarantee type safety when such a method is called. It's the same reason that add can't be called on a List<?>.

To get this to compile, you must use a class literal, to avoid having a Class<?>, and declare t not to have a wildcard.

G<Object> t = create(Object.class);

Then

t.mo(o);

will compile.

like image 195
rgettman Avatar answered Oct 22 '22 23:10

rgettman


What you have here is a consumer. However, the following seems to compile (in Eclipse).

public static class G<X, Z extends Y<X>> {
    public G(Class<? extends X> c, Class<Z> s) {}
    public void m(X x) {}
    public static <T, S extends Y<T>> G<T, S> create(Class<? extends T> c) {
        Class<S> s = null; // find this via some reflection magic
        return new G<T, S>(c, s);
    }
    public static void main(String[] args) {
        Object o = ""; // irrelevant!
        create(o.getClass()).m(o);
    }
}
like image 36
Judge Mental Avatar answered Oct 23 '22 01:10

Judge Mental