Reputation: 133
What does the following compilation error mean?
The method apply(Sample.Foo, capture#5-of ?) in the type FooSetter is not applicable for the arguments (Sample.Foo, capture#6-of ?)
It occurs in the code below (at the bottom where I've indicated with a comment) which tries to map setters of one bean to the getters of another bean without casting (yes I know there are utilities to do this but this is an example). I use the following two functional interfaces
@FunctionalInterface
public interface FooSetter<V> {
void apply(Foo foo, V value);
}
@FunctionalInterface
public interface BarGetter<T> {
T apply(Bar from);
}
public class Sample {
public static class Bar{
public String description;
public Integer num;
public String getDecription(){
return description;
}
public void setDescription(String desc){
this.description = desc;
}
public Integer getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
public static class Foo{
private String name;
private Integer age;
public Foo setName(String name){
this.name = name;
return this;
}
public Foo setAge(int age){
this.age = age;
return this;
}
@Override
public String toString(){
return "Name" + name + " Age " + age;
}
}
public static void main(String[] args) throws Exception {
HashMap<String, BarGetter<?>> barGetters= Maps.newHashMap();
barGetters.put("description", (BarGetter<String>)(Bar b) -> b.getDecription());
barGetters.put("num", (BarGetter<Integer>)(Bar b) -> b.getNum());
HashMap<String, FooSetter<?>> fooSetters= Maps.newHashMap();
fooSetters.put("name", (Foo f, String name) -> f.setName(name));
fooSetters.put("age", (Foo f, Integer age) -> f.setAge(age));
HashMap<String,String> fooFromBar = Maps.newHashMap();
// FOO BAR
fooFromBar.put("name", "description");
fooFromBar.put("age", "num");
Bar bar = new Bar();
bar.setDescription("bar desc");
bar.setNum(5);
Foo newFoo = new Foo();
for (String key : fooFromBar.keySet()) {
//COMPILATION ERROR
fooSetters.get(key).apply(newFoo, barGetters.get( fooFromBar.get(key) ).apply(bar) );
}
}
}
Is there something I'm not seeing here with respect to type erasure?
Upvotes: 0
Views: 293
Reputation: 298153
If you have a collection of differently-typed generic elements, you need a helper class
which wraps the actual element and guard it’s access with a runtime-check which replaces the compile-time check that doesn’t work in this case.
E.g.
final class FooSetterHolder<T> {
final Class<T> guard;
final FooSetter<T> setter;
FooSetterHolder(Class<T> guard, FooSetter<T> setter) {
this.guard = guard;
this.setter = setter;
}
<U> FooSetterHolder<U> cast(Class<U> expectedType) {
if(guard != expectedType) throw new ClassCastException(
"cannot cast FooSetterHolder<"+guard.getName()
+"> to FooSetterHolder<"+expectedType.getName()+'>');
// now that we have checked the type
@SuppressWarnings("unchecked") FooSetterHolder<U> h=(FooSetterHolder)this;
return h;
}
}
You can use this helper class in the following way:
HashMap<String, FooSetterHolder<?>> fooSetters= new HashMap<>();
fooSetters.put("name", new FooSetterHolder<>(String.class, Foo::setName));
fooSetters.put("age", new FooSetterHolder<>(Integer.class, Foo::setAge));
FooSetter<String> nameSetter=fooSetters.get("name").cast(String.class).setter;
FooSetter<Integer> ageSetter=fooSetters.get("age").cast(Integer.class).setter;
The cast
method provides an operation which uses the unavoidable unchecked operation internally but performs a runtime comparison of the expected and actual type so that the code using it is type safe (assuming that there are no other unchecked operations).
So the following code won’t be rejected by the compiler
FooSetter<Integer> wrong=fooSetters.get("name").cast(Integer.class).setter;
but doesn’t create heap pollution but rather throws a ClassCastException
with the message “cannot cast FooSetterHolder<java.lang.String> to FooSetterHolder<java.lang.Integer>
” very similar to ordinary, non-generic type casts.
Upvotes: 1
Reputation: 279940
This
fooSetters.get(key)
returns a value of type FooSetter<?>
, ie. a FooSetter
that doesn't know about the type it sets. Its apply
method therefore has the following (inferred) definition
void apply(Foo foo, ? value);
You can't use this method with anything (for its second parameter) but null
(which can be used for any reference type) since you don't know what type it is.
This
barGetters.get( fooFromBar.get(key) )
returns a value of type BarGetter<?>
, ie a BarGetter
that doesn't know what it's getting. Its apply
method therefore appears as
? apply(Bar from);
This ?
can be something completely different than what fooSetter.apply
is expecting.
The type system can therefore not allow it and gives you a compilation error.
Upvotes: 2