Is this possible to do using plain BiFunction in method definition

3k Views Asked by At

I had the following problem: a method should take in a bi-function that takes 2 arguments - one is of type Collection<T> and the other is T; the actual function could be actually Collection::remove or Collection::add, or a more complex operation; the actual function is used for more than a dozen of collection and there are several types of values and collections in the function.

Initially there was no genericity - there were just Collection<String>s and elements were Strings so declaring the argument as BiFunction<Collection<String>, String, Boolean> worked just well:

List<String> idCodes;
void visitElement(Element e, 
                  BiFunction<Collection<String>, String, Boolean> elementOp) {
    elementOp.apply(idCodes, e.getIdCode());
}

However then I added other types of collections too, and found that I could no longer find out how to use BiFunction generically:

List<String> idCodes;
List<Integer> weights;

void visitElement(Element e, 
                  BiFunction<...> elementOp) {
    elementOp.apply(idCodes, e.getIdCode());
    elementOp.apply(weights, e.getWeight());
}

but failed - I could just get compile errors in one place or another no matter what I used for type parameters it would fail.

One of my attempts was

<T> void visitElement(Element e, 
                      BiFunction<Collection<T>, T, Boolean> elementOp) 

would fail not when passing in Collection::add but when actually applying the function to Collection<String> and String; or Collection<Integer> and int.

Then I made another interface:

interface ElementOp {
    <T> boolean apply(Collection<T> collection, T item);
}

And not so surprisingly this now works exactly as I wanted. Therefore my question is:

Do I really have to use

interface ElementOp {
    <T> boolean apply(Collection<T> collection, T item);
}

void visitElement(Element e, 
                  ElementOp elementOp) {
    elementOp.apply(idCodes, e.getIdCode());
    elementOp.apply(weights, e.getWeight());
}

or would it be somehow possible to use BiFunction for this case?

P.S. I am using Eclipse 4.3 Mars Java compiler, so it might just be that this might not work because of a some bug there.

2

There are 2 best solutions below

0
On BEST ANSWER

You could not use a BiFunction with T generic that handles both cases (String and Integer).

This code could not compile :

<T> void visitElement(Element e,  
        BiFunction<Collection<T>, T, Boolean> elementOp) {  
   ...
   elementOp.apply(idCodes, e.getIdCode());
   elementOp.apply(weights, e.getWeight());
}

as BiFunction is a generic class and you parameterized it with Collection<T> and T as function arguments.
So you can only pass Collection<T> and T objects/variables while you pass Collection<String> and String in the fist call and Collection<Integer> and Integer in the second one.

With this custom interface, things are different :

interface ElementOp {
    <T> boolean apply(Collection<T> collection, T item);
}

This works :

elementOp.apply(idCodes, e.getIdCode());
elementOp.apply(weights, e.getWeight());

as contrary to BiFunction it may accept as parameters any variable declared with any class.
The thing to retain is that ElementOp is not a generic class.
T is indeed only a method scope generic that infers the type of the types of the passed arguments.


To address your requirement : invoking multiple times the same method (Collection.add() or Collection.remove()) but with args of different types (String or Integer), you don't want to use a generic BiFunction<T,Collection<T>, Boolean>.
The custom functional interface you introduced suits much better.

0
On

Well your first attempt did not work because when you have something like this:

<T> void visitElement(Element e, BiFunction<Collection<T>, T, Boolean> elementOp) {
    // some code        
}

All that visitElement knows about are T types; it knows that elementOp is of type BiFunction<Collection<T>, T, Boolean>; a T that will be inferred by the compiler.

I don't see any reason to introduce another interface here, when you can simply change the method a bit. Also notice that I've used BiConsumer and not BiFunction since you are not using the result anyway:

 void visitElement(T value, BiConsumer<Collection<T>, T> elementOp, Collection<T> elements) {
        elementOp.accept(elements, value);
 }

And use it:

    BiConsumer<Collection<String>, String> bi = Collection::remove;

    Element e = ...
    Collection<String> idCodes...;
    visitElement(e.getIdCode(), bi, idCodes);