In java, I try to pass a class param to a mapper using reflexion on the generic dto I receive as a param.
Despite all my tries, I can't seem to get it to work, even though I think my reasoning is correct and shouldn't raise incompatible types error in java.
Here is a snippet with a minimal reproducible example : https://code.sololearn.com/cTysx65IHL2Q/#java
I have an interface :
interface IDto18Now<TypeDtoParam> {
TypeDtoParam getAttribute();
void setAttribute(TypeDtoParam param);
}
and 2 methods where my param implements this interface :
private <T> T someMapperMethod(String paramToParse, Class<T> valueType){
// some code which definitively works, as it's a mapper
}
private <SpecificDto extends IDto18Now<?>> void callerMethod(SpecificDto paramDto) {
// some logic to take the string, it's actually a param but it's not important here
String unparsedString = "some json or whatever";
paramDto.setAttribute(someMapperMethod(unparsedString, paramDto.getClass().getMethod("getAttribute").getReturnType()));
}
and as you can see on snippet, I have an error where my types aren't compatible on the line where I call the setAttribute, where the truth is, for me, that they are, as the type returned with reflexion is, per the interface contract, the same that the one expected as param in the setAttribute method.
Currently I got this to work by passing an additional class param, but given my models are quite big and tend to chain lots of child dtos, I'd rather avoid passing the class param, especially as it should be guessable from the type of the param passed.
Can anyone 1, explain why this reflexion doesn't work, 2, what would be the best alternative solution for "deducing" like here the class to avoid passing it as an adiitional param.
Thank you for your time.
The issue is that the getMethod method has return type Class<?> which is not compatible with the Class<T> parameter of someMapperMethod.
I think the simplest way to fix this is to introduce a <T> parameter to callerMethod and then cast the Class<?> to Class<T>.
private <T> void callerMethod(IDto18Now<? super T> paramDto) throws NoSuchMethodException {
// some logic to take the string, it's actually a param but it's not important here
String unparsedString = "some json or whatever";
paramDto.setAttribute(someMapperMethod(unparsedString, (Class<T>)paramDto.getClass().getMethod("getAttribute").getReturnType()));
}
Note that I removed the SpecificDto type parameter and replaced it with a generic <T> parameter. I then cast the return of getReturnType to Class<T>. This should be type compatible with any existing callerMethod call sites but there could be corner cases I'm not thinking of.
EDIT: Understanding the error message
The error you were getting is
./Playground/Playground.java:13: error: incompatible types: inference variable T has incompatible bounds
paramDto.setAttribute(someMapperMethod(unparsedString, paramDto.getClass().getMethod("getAttribute").getReturnType()));
^
equality constraints: CAP#1
lower bounds: CAP#2,Object
where T is a type-variable:
T extends Object declared in method <T>someMapperMethod(String,Class<T>)
where CAP#1,CAP#2 are fresh type-variables:
CAP#1 extends Object from capture of ?
CAP#2 extends Object from capture of ?
This is a pretty unhelpful error but it gives you some useful information. First, it tells you that the T parameter on someMapperMethod is getting incompatible type requirements from different places. inference variable T has incompatible bounds
Next, it tells you what the incompatible types (really bounds) are:
equality constraints: CAP#1
lower bounds: CAP#2,Object
So T must be equal to CAP#1 and a super type of CAP#2. But where are these constraints coming from? The T variable is in the return type and the Class<T> parameter, so these are the two directions that may be incompatible.
The return type is the input type to setAttribute so that will impose bounds, and the input to the Class<T> parameter is from getReturnType so that is where the other bound comes from.
Finally, the error says
CAP#1,CAP#2 are fresh type-variables:
CAP#1 extends Object from capture of ?
CAP#2 extends Object from capture of ?
Because we know, from earlier in the error, that T has an equality constraint on CAP#1 this tells us that the problem is that the compiler cannot prove that CAP#1 is a super type of CAP#2. Because both types involved are wildcards this makes sense. Those types can be anything, so it's impossible to prove any relationship between them.
Related
I have a class with strict, simple generic type:
public class GenericTools<T> {
private final Supplier<T> supplier;
private final Consumer<T> consumer;
public GenericTools(Supplier<T> supplier, Consumer<T> consumer) {
this.supplier = supplier;
this.consumer = consumer;
}
public Supplier<T> getSupplier() {
return supplier;
}
public Consumer<T> getConsumer() {
return consumer;
}
}
What is the exact reason for the fact that "capture of ?" cannot be used here and file does not compile?
GenericTools<?> tools = new GenericTools<>(Math::random, System.out::println);
tools.getConsumer().accept(tools.getSupplier().get());
Error:(27, 59) java: incompatible types: java.lang.Object cannot be
converted to capture#1 of ?
With explicit <Double> it compiles with no problems:
GenericTools<Double> tools = new GenericTools<>(Math::random, System.out::println);
tools.getConsumer().accept(tools.getSupplier().get());
I have used Java 1.8 to compile.
Please note that this is completely not a duplicate of Java generics “capture of ?”, where poster have no idea what we need to pass as "?"-typed argument when code requires it. In my case I am quite aware of capture mechanism, but stil type looking like supposed to work cannot be used. Most importantly, here I am asking about the exact reason (specification reference or something), not about what I should pass.
It's because the type ? is invariant, and Object is not a subtype of all types within the bounds of ?.
I believe the Java 8 type inference is capable of inferring that T is Double on the RHS, but since you explicitly assign to a GenericTools<?> on the LHS, the capture is of an unbounded type variable, which unifies with the unbounded variable T which also has no bounds.
Without any bounds, the T in the signatures of Supplier::get and Consumer::accept are not guaranteed to be the same type -- remember, the type variable is invariant, as no co- or contra-variant bound is expressed. The erasure of the T on the Supplier side is just Object, and the compiler cannot insert a runtime check that the runtime type is actually ? (because ? is not reifiable!). Therefore: the type Object cannot be implicitly converted to ? and compilation fails.
As written in the Generics tutorial:
Collection<?> c = new ArrayList<String>();
c.add(new Object()); // Compile time error
Since we don't know what the element type of c stands for, we cannot
add objects to it. The add() method takes arguments of type E, the
element type of the collection. When the actual type parameter is ?,
it stands for some unknown type. Any parameter we pass to add would
have to be a subtype of this unknown type. Since we don't know what
type that is, we cannot pass anything in. The sole exception is null,
which is a member of every type.
On the other hand, given a List<?>, we can call get() and make use of
the result. The result type is an unknown type, but we always know
that it is an object. It is therefore safe to assign the result of
get() to a variable of type Object or pass it as a parameter where the
type Object is expected.
This is equivalent to
GenericTools<?> tools = ...
tools.getConsumer().accept(new Object()); //or tools.getSupplier().get() which returns Object
Once you declare your generic type this is what the compiler will know for the rest of the calls. getConsumer().accept() will have to accept ? (but it can't) and getSupplier().get() will have to return Object.
When declaring GenericTools<Double> you retain type information and both getConsumer().accept() and getSupplier().get() know that they are working with Double.
I realise Java 8 is still in Beta but this one struck me as odd:
public class Fields<C extends Enum<C>> {
public Fields(Set<C> columns) {
// A sample column used to find the universe of the enum of Columns.
C sampleCol = columns.iterator().next();
// Java 8 needs a cast here.
Set<C> allColumns = EnumSet.allOf((/*Class<C>)*/ sampleCol.getClass());
// ... there's more to this that I've deleted.
}
}
The error reads:
error: incompatible types: inferred type does not conform to equality constraint(s)
Set<C> allColumns = EnumSet.allOf(sampleCol.getClass());
inferred: C
equality constraints(s): C,CAP#1
where C is a type-variable:
C extends Enum<C> declared in class Test.Fields
where CAP#1 is a fresh type-variable:
CAP#1 extends Enum from capture of ? extends Enum
Is this a bug or a new feature of Java 8?
Interesting, this is a subtle change in the treatment of raw types.
First, let's clarify your example. The return type of Object.getClass is special:
The actual result type is Class<? extends |X|> where |X| is the erasure of the static type of the expression on which getClass is called.
In this case, X would be the type parameter C, which erases to Enum. So sampleCol.getClass() returns Class<? extends Enum>. EnumSet.allOf declares the type parameter E extends Enum<E>, and in your case ? extends Enum is being inferred as its type argument.
The important part is that Enum is a raw type. The use of raw types has been seen to erase seemingly unrelated generics, for example on this post: Why won't this generic java code compile? In his answer there, Jon Skeet cites JLS §4.8 ("Raw Types") to cover this unintuitive behavior.
Similar behavior seems to be happening in your example with Java 7: EnumSet.allOf(sampleCol.getClass()) is allowed to compile with an "unchecked invocation" warning (this gets hidden by the subsequent "unchecked conversion" warning from assigning the resulting raw EnumSet to Set<C>).
The question becomes: should the occurrence of a raw type in a generic wildcard's bounds allow unchecked conversions? JLS §4.8 makes no mention of this, so it's ambiguous. Possibly it's a bug, but it seems like a reasonable tightening of this behavior. While a standard raw type like Enum might itself be expected from legacy APIs, a "half-baked" type like Class<? extends Enum> could only occur post-generics and so it doesn't really make sense to let it disrupt generic type checking.
Anyway I'm interested to see if anyone can point to documentation about this change - my search didn't turn anything up.
About your specific code: you should use getDeclaringClass() instead. The compiler can't know that calling getClass on a C will return exactly Class<C>; in fact, it won't if used on an enum with a constant-specific class. This is exactly the use case for which Enum declares that method.
I am having an issue with generics and what I presume is type erasure whilst trying to reflect some generic information.
I am using a class based on http://www.artima.com/weblogs/viewpost.jsp?thread=208860 to do the reflection, but that isn't the issue.
The code below demonstrates the issue:
private <U extends MyClass1> U doSomething(MyClass2<U> p) {
Class<U> classIWant = null;
for (Class<?> c : GenericTypeUtils.getTypeArguments(MyClass2.class, p.getClass())) {
if (MyClass1.class.isAssignableFrom(c)) {
classIWant = c.asSubclass(MyClass1.class);
break;
}
}
}
Unfortunately the line
classIWant = c.asSubclass(MyClass1.class);
Shows the following error
java: incompatible types
required: java.lang.Class<U>
found: java.lang.Class<capture#4 of ? extends MyClass1>
And that's really confusing me. U extends MyClass1 and therefore U is a MyClass1.
Obviously there is some piece of type erasure I am not understanding here - can anybody help, please. BTW this is Java 1.7.0_40
Thanks
Conrad Winchester
The method asSubclass() is implemented like so
public <U> Class<? extends U> asSubclass(Class<U> clazz) {
if (clazz.isAssignableFrom(this))
return (Class<? extends U>) this;
else
throw new ClassCastException(this.toString());
}
with a return type of Class<? extends U>. Note, that your U and the U in the asSubclass method are completely unrelated type variables.
So the method asSubclass() returns a value declared with the type Class of some unknown subtype of U, where U in this case is MyClass1.
You are trying to assign this value to a variable of type Class with some known subtype of MyClass1. The compiler can't guarantee that they match and therefore doesn't allow compilation.
You can cast the returned value if you wish, but may end up getting ClassCastException at runtime, if the types don't match.
The Java compiler is unable to guarantee that the Class object c matches the generic type U. Because of type erasure, it is up to the compiler to guarantee type safety, and ensure that the Class coming back from c.asSubclass matches Class<U>, but it can't.
The asSubclass method returns a Class<? extends U>. In the Class class, U represents the type of Class passed into asSubclass. When you call it, that is c. But the compiler cannot guarantee that this return type matches your own declared <U extends MyClass1>. They could be different subclasses of MyClass1.
If you can guarantee that c.asSubclass will return a Class<U>, then cast it as a Class<U>:
classIWant = (Class<U>) c.asSubclass(MyClass1.class);
You will receive an "unchecked cast" warning. This is there because the compiler cannot guarantee that is really is a Class<U>, but you are saying that it is. Be careful as this may result in a hard-to-track-down ClassCastException elsewhere, even after you return the U. Do this only if you can guarantee the type safety yourself.
Given the following not-very-useful code:
package com.something;
import java.util.ArrayList;
import java.util.Collection;
//Not a generic class!
public class Test {
public <T> void plain(T param1, T param2) {}
public <T> void fancy(T param1, Collection<T> param2) {}
public void testMethod() {
//No error
fancy("", new ArrayList<String>());
//Compiler error here!
fancy("", new ArrayList<Integer>());
//No error
plain("", new ArrayList<Integer>());
}
}
(Please correct my understanding if it's wrong!)
The 2nd call to fancy() is a compiler error because Java can't infer any common type between the two arguments (can't infer Object since the second parameter must be a Collection.)
The call to plain() is not a compiler error because Java infers the common type of Object between the two arguments.
I recently came across code that had a method signature similar to plain().
My question is this:
Is plain()'s signature useful for anything?
Perhaps the person who wrote that code thought that plain()'s signature would enforce that both parameters have the same type at compile time, which is obviously not the case.
Is there any difference from or benefit to writing a method with a signature like plain() rather than just defining both parameters to be Objects?
While the compiler does not infer the generic type one might intend, it will enforce type constraints that are explicitly specified. The following invocation results in a type error.
this.<String>plain("", new ArrayList<Integer>()); /* Compiler error. */
The parameterized method <String>plain(String, String) of type Test is not applicable for the arguments (String, ArrayList<Integer>)
I guess you could say it serves as some sort of documentation, so that the user knows that you expect both arguments to be of the same type. Of course, any two objects are of the same type to some degree (they're all Object), so it's a meaningless statement.
Long story short, it's a useless signature.
Of course, if plain returned a type T, that'd be a different story.
The 2nd call to fancy() is a compiler error because Java can't infer
any common type between the two arguments (can't infer Object since
the second parameter must be a Collection.)
Well, I am not sure this is the reason, I would say the reason is that the generic type T in Collection<T> is an invariant whose value determines the type of the first parameter T.
For instance, this is valid:
fancy("", new ArrayList<CharSequence>()); //compiles Ok
Because all String are CharSequences. It is expected that the first parameter is a CharSequence once the type is inferred from ArraysList<CharSequence>.
However, this is not valid:
fancy((CharSequence)"", new ArrayList<String>()); //compiler error
Because it is expected the type of the first parameter be String, and we cannot ensure that all CharSequences are, in fact, of type String, right?
So, AFAIK, the reason the types are not compatible is due to the nature of generics in this case, and not to the fact that the second type is a Collection.
i've got this code:
public <T extends Scrapper> Class<T> getScrapper() {
return MyScrapper.class;
}
MyScrapper is a class implementing Scrapper interface. Why is this not working? U'm getting the following error in JDK7:
error: incompatible types
required: Class<T>
found: Class<MyScrapper>
where T is a type-variable:
T extends Scrapper declared in method <T>getScrapper()
P.S.
I've honestly tried searching for whole 30-40 minutes.
Update:
if i declare the method as public Class<? extends Scrapper> getScrapper() { it works. but I still don't understand why the original declaration was not compiling. what's wrong with it?
With a generic method like getScrapper(), the caller of the method determines what the actual type argument to the method is (T in this case). The caller could pick any subtype of Scrapper as T, and your method (which always returns MyScrapper.class) would not be returning the correct class.
Given the signature of the method, the caller of this method would expect to be able to do this:
Class<MyOtherScrapper> c = foo.<MyOtherScrapper>getScrapper();
Changing the method to return Class<? extends Scrapper> makes it no longer a generic method... there are no type parameters for the caller to set. Instead, the signature says that the method returns the class object for some unknown subtype of Scrapper, and MyScrapper.class does fit the bill for that.