Home > front end >  Applying a BiFunction from a map
Applying a BiFunction from a map

Time:09-17

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.

enter image description here

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

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);
  }
}
  • Related