I'm trying to apply a BiFunction to method parameters in a generic way so that I can get the function from a map:
public class ElementFactoryTest {
private final static Map<String,
BiFunction<
? extends Element,
String,
? extends MetaElement<? extends MetaElement<?, ?>, ? extends Element>>>
functionMap = new HashMap<>();
public static <
E extends Element,
M extends MetaElement<? extends MetaElement<?, ?>, ? extends Element>>
void registerFactoryFunction(String type, BiFunction<E, String, M> factoryFunction) {
functionMap.put(type, factoryFunction);
}
public static <E extends Element>
MetaElement<?, ?> getElementList(String type, E element, String namespacePrefix) {
return functionMap.get(type).apply(element, namespacePrefix);
}
}
The registerFactoryFunction
method code compiles fine, but I don't understand why the line
return functionMap.get(type).apply(element, namespacePrefix);
does not compile. Is it not possible to do this?
Update 1
Changing extends
to super
as @josejuan suggests allows the code to compile but the client code that registers the bifunctions fails to compile with the error "reason: Incompatible types: Element is not convertible to ElementSubclass":
ElementFactoryTest.registerElementFactory("elementType", MetaElementSubclass::build);
public static MetaElement<MetaElementSubclass, ElementSubclass> build(ElementSubclass element, String namespace) {
....
}
update 2
Here's a minimal reproducible example:
public class ElementFactoryTest {
static class Parent {
public String getString() {
return "parent";
}
}
static class Child extends Parent {
public String getString() {
return "child";
}
}
private final static Map<String,
BiFunction<? super Parent, String, String>>
functionMap = new HashMap<>();
public static void registerFactoryMethod(String type,
BiFunction<? super Parent, String, String> factoryFunction) {
functionMap.put(type, factoryFunction);
}
public static <E extends Parent>
String getString(String type, E element, String namespacePrefix) {
return functionMap.get(type).apply(element, namespacePrefix);
}
public static String build(Child child, String namespacePrefix) {
return namespacePrefix child.getString();
}
public static void main(String[] args) {
ElementFactoryTest.registerFactoryMethod("child", ElementFactoryTest::build);
String childString = ElementFactoryTest.getString("child", new Child(), "prefix.");
}
}
The line:
ElementFactoryTest.registerFactoryMethod("child", ElementFactoryTest::build);
fails to compile.
CodePudding user response:
Your code fail because your bifunctions must admit any ? super Parent
but you are providing an implementation valid only for Child super Parent
.
The compiler is trying to tell you that you should generalize your Test::build
function.
Without knowing the details of what you need to do, your least bad approach is to downcast at runtime, but surely your original problem admits a compile-time solution (please do not edit your question, create another one if you consider it necessary).
public static String build(Parent child, String namespacePrefix) {
if(child instanceof Child)
return namespacePrefix child.getString();
throw new RuntimeException("Arghh!");
}
testing with
System.out.println(Test.getString("child", new Child(), "prefix."));
System.out.println(Test.getString("child", new Parent(), "prefix."));
with output
prefix.child
Exception in thread "main" java.lang.RuntimeException: Arghh!
Further reading:
Covariance and Contravariance in Java
CodePudding user response:
I had to cast the BiFunction before calling apply like:
public static <E extends Parent>
String getString(String type, E element, String namespacePrefix) {
return ((BiFunction<E, String, String>) functionMap.get(type)).apply(element, namespacePrefix);
}
E
is defined as E extends Parent
and the parameter in the BiFunction
definition is ? extends Parent
so it seems reasonable enough.
I don't know if this is the best solution, but it's the only one that worked for me.
The minimal reproducible example is:
public class ElementFactoryTest {
static class Parent {
public String getString() {
return "parent";
}
}
static class Child extends Parent {
public String getString() {
return "child";
}
public static String build(Child child, String namespacePrefix) {
return namespacePrefix child.getString();
}
}
static class GrandChild extends Child {
public String getString() {
return "grand child";
}
public static String build(Child child, String namespacePrefix) {
return namespacePrefix child.getString();
}
}
private final static Map<String,
BiFunction<? extends Parent, String, String>>
functionMap = new HashMap<>();
public static <E extends Parent>
void registerElementFactory(String type,
BiFunction<E, String, String> factoryFunction) {
functionMap.put(type, factoryFunction);
}
public static <E extends Parent>
String getString(String type, E element, String namespacePrefix) {
return ((BiFunction<E, String, String>) functionMap.get(type)).apply(element, namespacePrefix);
}
public static void main(String[] args) {
ElementFactoryTest.registerElementFactory("child", Child::build);
ElementFactoryTest.registerElementFactory("grand-child", GrandChild::build);
String childString = ElementFactoryTest.getString("child", new Child(), "prefix.");
System.out.println(childString);
String grandChild = ElementFactoryTest.getString("grand-child", new GrandChild(), "prefix.");
System.out.println(grandChild);
}
}