简体   繁体   中英

Java generics puzzler with generic static factory

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, ie, 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.

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.

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);
    }
}

You're creating a G<Object> and then assigning it to a variable of type G<?> . The method invoked takes a variable of the generic type, which won't take anything for <?> . If you change the variable to G<Object> it will work.

Since you are specifying G<?> , javac is expecting to figure out what the generics are (what classes do they represent). Changing the statement to G t = create(o.getClass()); fixes the errors.

capture errors generally mean that the compiler is unable to figure out the classes...

Its not really clear what you are trying to do... Perhaps that information would be useful in helping you more...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM