Let's say there's a framework type Row
with multiple type parameters, and a method that works with instances of the Row
type and uses all these type parameters.
I have a method that works with any type of Row
, even different types at the same time, so obviously I'm using the wildcard type Row<?,?>
. The question is, how do I invoke a method that takes a Row<R,K>
with a Row<?,?>
?
My line of thought: I don't exactly know what type Row<?,?>
is, but it's surely some kind of Row
alright. And when a generic method takes Row<R,K>
it means that it wants to do something with R
and K
but otherwise it can deal with any type of Row
. So my "any" type should work with a method that takes "any" type, right?
I'm attaching sample code below with things that I tried. The weirdest thing is that the very last line actually works, but it's not any more type safe than anything else I think. So basically I'd like a cleaner solution than this if possible or an explanation why this is the way to go.
package foo;
public class Experiment {
// Defined by a framework.
interface Key<K extends Key<K>> {}
interface Row<R extends Row<R, K>, K extends Key<K>> {}
static <R extends Row<R, K>, K extends Key<K>> R copyRow(R row) {
return row;
}
// My experiments below.
static class Wrapper<R extends Row<R, K>, K extends Key<K>> {
public final R row = null; // fixme
public final Class<R> clazz = null; // fixme
}
static <R extends Row<R, K>, K extends Key<K>> R upcast(Row<?, ?> row) {
return (R) row;
}
static <R extends Row<R, K>, K extends Key<K>> R upcast(Row<?, ?> row, Class<R> clazz) {
assert row.getClass().equals(clazz);
return (R) row;
}
public static void main(String[] args) {
Wrapper<?, ?> wr = null; // fixme
copyRow(wr.row); // Compilation error
copyRow(upcast(wr.row)); // Compilation error
copyRow(upcast(wr.row, wr.clazz)); // This works, why?
}
}
(You can send this sample straight to javac to see what happens. With Java 1.8: https://pastebin.com/LB10ySsD)
Here is one possibility:
public class WildcardsExperiment {
// Defined by a framework. <begin unmodifiable>
interface Key<K extends Key<K>> {}
interface Row<R extends Row<R, K>, K extends Key<K>> {}
static <R extends Row<R, K>, K extends Key<K>> R copyRow(R row) {
return row;
}
// <end unmodifiable>
interface NaturalRowTransformer {
<R extends Row<R, K>, K extends Key<K>> R apply(R r);
}
class Wrapper<R extends Row<R, K>, K extends Key<K>> {
private final R row = null; // fixme (constructor etc.)
public final Class<R> clazz = null; // fixme
R invokeNat(NaturalRowTransformer nat) {
return nat.apply(row);
}
}
static final NaturalRowTransformer CopyRow = new NaturalRowTransformer() {
public <R extends Row<R, K>, K extends Key<K>> R apply(R row) {
return copyRow(row);
}
};
public static void main(String[] args) {
Wrapper<?, ?> wr = null; // fixme
wr.invokeNat(CopyRow); // compiles
}
}
Essentially, the copyRow
method is wrapped into a visitor NaturalRowTransformer
, which guarantees that it can handle all possible valid combinations of F-bounded type pairs R
and K
. The Wrapper
then provides an accepting method invokeNat
, which accepts the visitor and performs the copyRow
operation in a scope where R
and K
are concrete (not wildcards).
The trick is inspired by category theory (hence the "natural" in the name), and imported from Scala, even though current implementations of Scala's pattern matching on types allow for more concise solutions. This solution is known to work under slightly more complex constraints.