1

I'm trying to create a functional interface in Java with a single method (of course) that can take any type of parameter(s) and return any data type (i.e. a generic method).

This is what I have so far:

Calculator.java

public interface Calculator<T> {
    T operation(T n1, T... n2); //this is where the optional parameter comes in
}

Main.java

public static void main(String[] args) {
    Calculator<Integer> addition = (n1, n2) -> n1 + n2; //this gives an error
}

The error says:

bad operand types for binary operator '+'

  • Is it possible to create a generic functional interface with optional parameter(s) in Java?
  • If so, what am I doing wrong?
dreamcrash
  • 47,137
  • 25
  • 94
  • 117
gustavozapata
  • 429
  • 2
  • 7
  • 13

2 Answers2

5
public interface Calculator<T> {
    T operation(T n1, T .. n2); //this is where the optional parameter comes in
}

The error comes from the fact that you are trying to apply the operator + to a Integer and an Array of Integers. The following, for instance

public interface Calculator<T> {
    T operation(T n1, T n2); //this is where the optional parameter comes in
}

would work fine, since you would be applying the operator + to two Integers.

If you want to keep the same Interface then you need to change you code in the main to:

public static void main(String[] args) {
    Calculator<Integer> addition = (n1, n2) -> n1 + Arrays.stream(n2).reduce(0, Integer::sum);
}

Is it possible to create a generic functional interface with optional parameter(s) in Java?

From this SO Thread one can read:

varargs could do that (in a way). Other than that, all variables in the declaration of the method must be supplied. If you want a variable to be optional, you can overload the method using a signature which doesn't require the parameter.

That being said, what you could do is something like:

public interface Calculator<T> {
    T operation(T ...n);
}

In this way, the method operation can accept 0, 1 ... N elements and even null.

Then in your main:

Calculator<Integer> addition = n -> (n == null) ? 0 : 
                                    Arrays.stream(n).reduce(0, Integer::sum);

A running Example:

public class Main {
    public static void main(String[] args) {
        Calculator<Integer> addition = n -> (n == null) ? 0 : Arrays.stream(n).reduce(0, Integer::sum);
        System.out.println(addition.operation(1, 2));
        System.out.println(addition.operation(1));
        System.out.println(addition.operation());
        System.out.println(addition.operation(null));
    }
}

Output:

3 // 1 + 2
1 // 1
0 // empty array 
0 // null
dreamcrash
  • 47,137
  • 25
  • 94
  • 117
3

As shown in this answer, you can implement your interface by lambda expressions processing the varargs parameters, e.g. with a Stream. But you can also design an interface for associative operations providing default methods in addition to the two argument version:

interface Calculator<T> {
    T operation(T n1, T n2);
    default T operation(T first, T... args) {
        return Stream.concat(Stream.of(first), Arrays.stream(args))
            .reduce(this::operation).orElseThrow();
    }
}

Note that the method signature operation(T first, T... args) enforces that there is at least one element, which allows to use reduce without an identity element or fallback value for empty streams.

You can use it like

Calculator<Integer> addition = (n1, n2) -> n1 + n2;
Integer sum = addition.operation(1, 2, 3, 4);

But the operation must be associative, like addition, multiplication, or string concatenation, etc. If you want to support non-associative operations, those operations would have to override operation(T first, T... args) with an appropriate implementation. Overriding it doesn’t work with lambda expressions. The alternative would be to have an interface for arbitrary calculations and a specialization for associative operations:

interface Calculator<T> {
    T operation(T first, T... args);
}
interface AssociativeOp<T> extends Calculator<T> {
    T operation(T n1, T n2);
    @Override
    default T operation(T first, T... args) {
        return Stream.concat(Stream.of(first), Arrays.stream(args))
            .reduce(this::operation).orElseThrow();
    }
}

Then, non-associative operations can implement Calculator<T> via a lambda expression handling all arguments while associative operations can be implemented like:

Calculator<Integer> addition = (AssociativeOp<Integer>)(n1, n2) -> n1 + n2;

The necessity for a type cast could be eliminated by a factory method, using type inference for its parameter. However, when we are at this point, we could remember that there is already a functional interface type for operations with two arguments which the examples are even using, implicitly. So another alternative would be:

interface Calculator<T> {
    T operation(T first, T... args);

    static <U> Calculator<U> associative(BinaryOperator<U> op) {
        return (first,other) -> Stream.concat(Stream.of(first), Arrays.stream(other))
            .reduce(op).orElseThrow();
    }
}

This still allows arbitrary implementations via lambda expression while associative operations can be implemented with a simplified:

Calculator<Integer> addition = Calculator.associative((n1, n2) -> n1 + n2);

(Or Calculator.associative(Integer::sum))

Holger
  • 285,553
  • 42
  • 434
  • 765