Why does the compiler state no unique maximal inst

2019-02-05 15:18发布

问题:

I have the following classes:

public class Obj<T> extends BaseModel {

    public static final String OBJECT = "object";

    public Obj(T object) {
        setObject(object);
    }

    public T getObject() {
        return get(OBJECT);
    }

    public void setObject(T object) {
        set(OBJECT, object);
    }
}

And...

/** This is a 3rd party library class **/
public class BaseModel implements ModelData, Serializable {
  //...members and stuff...

  @SuppressWarnings({"unchecked", "rawtypes"})
  public <X> X get(String property) {
    X obj = null;
    if (start > -1 && end > -1) {
      Object o = map.get(property.substring(0, start));
      String p = property.substring(start + 1, end);
      if (o instanceof Object[]) {
        obj = (X) ((Object[]) o)[Integer.valueOf(p)];
      } else if (o instanceof List) {
        obj = (X) ((List) o).get(Integer.valueOf(p));
      } else if (o instanceof Map) {
        obj = (X) ((Map) o).get(p);
      }
    } else {
      obj = (X) map.get(property);
    }
    return obj;
  }
}

When I compile, I get the following error.

type parameters of <X>X cannot be determined; no unique maximal instance exists for type variable X with upper bounds T,java.lang.Object -> getObject()

It doesn't happen in Eclipse, which, as far as I can tell, is using the same JDK as my Ant build. I've seen the SO thread about the Sun compiler issue, but that seemed to be for static methods declaring types on the fly.

Why am I getting this error, and more importantly, how do I get around it?

So far the only why I've found is to cast in my method like this:

@SuppressWarnings({"unchecked"})
public T getObject() {
    return (T) get(OBJECT); //yuck
}

Telling my I'm on crack and this is the proper way is acceptable.

回答1:

It does not compile because your code expects too much from generics -> i.e., the < X > X part in:

public <X> X get(String property) { ... }

In the following code:

public T getObject() {
  return get(OBJECT);
}

you have to keep in mind that generics are always "unfolded" before the compiler actually starts to compile the Java code. It is a pre-processing step.

In your case, the compiler does not know what to use to replace X at compile time. The compiler needs to be sure about the type of X, because it needs to check it against T to validate the code. Hence the error...

A solution to your issue is to replace < X > X with Object:

public Object get(String property) { ... }

and add a cast in:

public T getObject() {
  return (T) get(OBJECT);
}

Your will get an unchecked-cast warning at compile time, but your code will compile (so yes your workaround is valid).



回答2:

This is dummy bug that has been fixed in Java SE 7.



回答3:

Method type parameters are most often implicitly inferred from the arguments to that method. Note, however, get has no explicit relationship between the argument and the type parameter:

public <X> X get(String property)

Type inference is the usual path, but methods can also be invoked with explicit type arguments, just like classes. The format roughly follows that of the declaration, so inside of Obj you could have

public T getObject() {
    return super.<T>get(OBJECT);
}

You could also just be direct and use <Object>, but you'd still have to use that unchecked cast to get it back to T. Note the explicit argument needs a qualifier, usually the instance name of the class. Since your example used a method of the superclass, its reference is implicit through super.

This doesn't solve the underlying problem of applying a generic method (<X> X get) inside of a non-generic class (BaseModel). Note the code in the library makes forcible type casts to the type argument. This style is indeed one of the solutions to back-porting generic features into non-generic Java code. It looks like they're trying to hide this from the library users, but since they didn't genericize the class the type can't be inferred from the instance (i.e. you really want to have Obj<T> extends BaseModel<T>).

[EDIT: corrected and explained explicit method type argument]



回答4:

I just encountered a similar issue with a project using Apache Pivot. The client code was riddled with lines like:

boolean foo = org.apache.pivot.json.JSON.get(item, "foo");

The code would compile in Eclipse, but not using Maven or javac from the command line. It appears to be Bug 6302954, but I still see it after updating to the latest JDK.

As the JSON class is provided by Pivot, it's not something I could modify within my own source tree (forking the library is not an option on this project)

The solution that worked for me came from the first reply in the bug report, changing the code to read:

boolean foo = org.apache.pivot.json.JSON.<Boolean>get(item, "foo");