I'm migrating a project from Eclipse to Ant. While the following code compiles using Eclipse, it fails using JDK 1.5.0_06 with the following error:
type parameters of <B>B cannot be determined; no unique maximal instance exists for type variable B with upper bounds A,java.lang.Object
private <B> B getOtherValue()
{
returnnull;
}
private <A> A getValue()
{
return getOtherValue();
}
The compiler seems to have troubles infering the type of the generic <B> parameter in getOtherValue, based on the return type of getValue.
Could somebody explain me what's wrong with that code?
Now do you see what's wrong? OK, so it's a silly example, but it illustrates how you have not told the compiler anything about the relationship between A and B. If A is Object then B can be anything, but if at runtime A turned out to be Integer, and B turned out to be String, clearly we'd have a problem. The compiler has no way of knowing what A and B will be, so intuitively it must disallow this.
It's not that there's no possible correct answer, obviously, there's always Object, but it can't know whether the code is correct or not because it can't know if A and B are compatible.
Re: no unique maximal instance exists for type variable A
Apr 19, 2006 10:29 AM
(reply 2
of 19) (In reply to
#1 )
I don't get it: as soon as I fix the value of A as being Integer, it becomes very clear that B is Integer as well. There is strictly no ambiguity here: whatever A, B is equal to A. The compiler has no problem with
There is no reason for the compiler to decide that B is Object when it is called from an Integer context. So why can't it perform exactly the same inference when 'Integer' is replaced by 'A'?
And once again, Eclipse has no trouble compiling the initial code.
Re: no unique maximal instance exists for type variable A
Apr 19, 2006 5:27 PM
(reply 3
of 19) (In reply to
#2 )
I don't get it: as soon as I fix the value of A as
being Integer, it becomes very clear that B is
Integer as well. There is strictly no ambiguity here:
whatever A, B is equal to A. The compiler has no
problem with
Yes... though that code is useless, because I don't think you can ever return anything other than null from getOtherValue(). Nothing else will compile...?
There is no reason for the compiler to decide that B
is Object when it is called from an Integer context.
So why can't it perform exactly the same inference
when 'Integer' is replaced by 'A'?
And once again, Eclipse has no trouble compiling the
initial code.
Well, both Javac and Eclipse have bugs. The question is, which has a bug in this case (always assuming the Java Lang Spec isn't ambiguous).
Unfortunately I'm not an expert on this stuff - my gut feeling is this should not compile as you originally posted it. But I may well be wrong.
It might help to reason about it if you can give us a bigger piece of your original program so we can think about what you are trying to achieve. Right now it is difficult to see why this code is useful. Not than I'm doubting that it is useful.
Another thought experiment... what if you added another method to the class:
private <C> C getValue()
{
return getOtherValue();
}
Now, you've told the compiler you have two methods, one returning an A and one returning a C. They both call getOtherValue() which returns a B.
Clearly there are cases where this could work. If A and C and compatible types. But the compiler can't prove this.
It must consider the case where A and C are not compatible, e.g. if A were Integer and C was String, then no B exists that can satisfy that contract (the bounds). B would have to be String sometimes and Integer other times - there's no unique instance that can do it.
You can say maybe it should compile when there is only the one method returning A - but then that would mean that if you later changed the source code of the class to add the method that returns C, the method that returns B would suddenly no longer compile even though you didn't change it.
Re: no unique maximal instance exists for type variable A
Apr 20, 2006 11:16 AM
(reply 4
of 19) (In reply to
#3 )
Once again, I don't see your point about the compatibility between A and C. The compiler never has to decide, once for all, what is the value for A or C. It depends on the context.
Take the following code (that builds using both Eclipse and Sun):
private <A> A getValue()
{
returnnull;
}
public String getStringValue()
{
return getValue();
}
public Integer getIntegerValue()
{
return getValue();
}
What's the value for A? Well, it depends on the context. In the first context, A is a String, in the second A is an Integer. There is no context-less value for A. That's the purpose of this "method-level" generic parameter, unless I'm completely lost.
My actual code is rather complex and involves several classes, but I found a less trivial example:
public <A> A getValue(Class<A> clazz,String name)
{
A result=null;
if(name.equals("User"))
{
result=(A)"Plouf";
}
elseif(name.equals("Age"))
{
result=(A)new Integer(26);
}
if(result.getClass()!=clazz) thrownew RuntimeException();
return result;
}
public String getUserName()
{
return getValue(String.class,"User");
}
publicint getUserAge()
{
return getValue(Integer.class,"Age");
}
This code is safe and compiles using both Eclipse and Sun. Now let say that you want to refactor this getValue method, and split the actual value extraction from the type verification:
public <B> B getValueUnsafe(String name)
{
B result=null;
if(name.equals("User"))
{
return (B)"Plouf";
}
elseif(name.equals("Age"))
{
return (B)new Integer(26);
}
returnnull;
}
public <A> A getValue(Class<A> clazz,String name)
{
A result=getValueUnsafe(name);
if(result.getClass()!=clazz) thrownew RuntimeException();
return result;
}
public String getUserName()
{
return getValue(String.class,"User");
}
publicint getUserAge()
{
return getValue(Integer.class,"Age");
}
Now this code does not compile anymore using Sun JDK! I agree this code is a little bit far-fetched, but this is all I found both usefull and still small enough for this forum.
Re: no unique maximal instance exists for type variable A
Apr 20, 2006 12:30 PM
(reply 6
of 19) (In reply to
#4 )
Once again, I don't see your point about the
compatibility between A and C. The compiler never has
to decide, once for all, what is the value for A or
C. It depends on the context.
Take the following code (that builds using both
Eclipse and Sun):
private <A> A getValue()
{
returnnull;
}
public String getStringValue()
{
return getValue();
}
public Integer getIntegerValue()
{
return getValue();
}
What's the value for A? Well, it depends on the
context. In the first context, A is a String, in the
second A is an Integer. There is no context-less
value for A. That's the purpose of this
"method-level" generic parameter, unless I'm
completely lost.
Granted, you can use it that way. You gain in that you don't have to insert unsafe casts to String and Integer - the compiler infers them for you from the calling context. However, you only have no casts in the code above because you are returning null. If you wanted to actually return a value you have to cast to (A) as you do in the examples below, so you still have an unsafe cast. You've saved a bit of typing but not much else.
Now let say that you want to refactor this
getValue method, and split the actual value
extraction from the type verification:
public <B> B getValueUnsafe(String name)
{
B result=null;
if(name.equals("User"))
{
return (B)"Plouf";
}
elseif(name.equals("Age"))
{
return (B)new Integer(26);
}
returnnull;
}
public <A> A getValue(Class<A> clazz,String name)
{
A result=getValueUnsafe(name);
if(result.getClass()!=clazz) thrownewnew RuntimeException();
return result;
}
public String getUserName()
{
return getValue(String.class,"User");
}
publicint getUserAge()
{
return getValue(Integer.class,"Age");
}
Now this code does not compile anymore using Sun JDK!
I agree this code is a little bit far-fetched, but
this is all I found both usefull and still small
enough for this forum.
OK... now I see what you're doing This is how I'd write that example:
public Object getValueUnsafe(String name) {
if ( "User".equals(name) ) {
return "Plouf";
} elseif ( "Age".equals(name) ) {
returnnew Integer(26);
}
returnnull;
}
public <A> A getValue(Class<A> clazz, String name) {
return clazz.cast(getValueUnsafe(name));
}
public String getUserName() {
return getValue(String.class, "User");
}
publicint getUserAge() {
return getValue(Integer.class, "Age");
}
The method Class.cast() hides the unsafe cast inside of it, so you do not get a warning. The getValueUnsafe() just returns an Object - which is all it ever did when it was declared to return a 'B' as B was unconstrained. Hopefully this is applicable to your real code and it may make it simpler. Class.cast() throws ClassCastException - if you want a different exception you'll have to catch that and wrap it.
As to whether it's a bug that Javac doesn't accept the original code, or a bug that Eclipse does, I still do not know, but I'd suggest something like the above as an alternative.
Re: no unique maximal instance exists for type variable A
Apr 20, 2006 12:57 PM
(reply 7
of 19) (In reply to
#6 )
Well that does not really apply because my method is actually something like this:
public <A extends Serializable,B> A convertFromNetwork(B param)
{
return _converterImpl.convertFromNetwork(param);
}
With _converterImpl having its method like this
public <A extends Serializable,B> A convertFromNetwork(B param);
But I managed to fix my problem by doing an explicit cast:
public <A extends Serializable,B> A convertFromNetwork(B param)
{
return (A)_converterImpl.convertFromNetwork(param);
}
That's not the most clean thing ever, but that'll do the job... The only drawback is that previously the only location where I had to do the cast was in the _convertImpl instance, and not a little bit everywhere as it is now... Funnily enough, I now have MORE casts than if I didn't use generics...
Re: no unique maximal instance exists for type variable A
Apr 20, 2006 4:51 PM
(reply 8
of 19) (In reply to
#7 )
It's really hard to say without seeing more complete code, but if that's the case maybe you shouldn't be using generics.
If there really no relationship between the return type A and the param type B?
It looks like you're using unsafe casts in the convertFromNetwork code, to hide the need to cast from your client code that calls convertFromNetwork(). This can be good or bad, but without knowing the whole shape of the problem it's hard to say.
This looks like it could possibly be similar to the FAQ in this forum where someone asks: "given a Map, how can I make it so if the key is a String the value is known to be a StringFoo and if the key is an Integer, the value is known to be an IntegerFoo?" (you can't with generics)
I'm curious, if you're willing to talk more, but as you've found a solution...
Re: no unique maximal instance exists for type variable A
Apr 21, 2006 11:54 AM
(reply 9
of 19) (In reply to
#8 )
It is true that there is no relationship between A and B and that it is impossible for the compiler to be 100% certain that everything will work at compile-time.
The generics here allow me not to do castings all over the place. Some might argue that I'm trying to emulate dynamic-typing in Java using generics.
With generics, you can do things like
public <A> A getValue(Object key)
{
return (A)_map.get(key);
}
That's not safe at all, but heh, not more that doing the explicit cast when doing the getValue call... and now you can write something like
String name=getValue("name");
int age=getValue("age");
Re: no unique maximal instance exists for type variable A
May 20, 2008 7:56 AM
(reply 13
of 19) (In reply to
#12 )
Hi everybody
I'm Maurizio Cimadamore, the type-system engineer of javac. I just tried to compiled your example and I found no problems.
I tried this using the latest version of the openjdk-6 compiler. Which release of javac are you using? Btw, the suggested workaround
works whenever the compiler is wrong in inferring method type arguments, since it basically disables type-inference.