The BinaryOperator interface in Java is a powerful tool for functional programming. As a functional interface, BinaryOperator represents an operation upon two operands of the same type, producing a result of that same type. In this article, we will explore how Java’s BinaryOperator can be leveraged for cleaner and more declarative code.
The BinaryOperator interface was introduced in Java 8 along with lambda expressions and stream APIs. It resides in the java.util.function package and takes two generic arguments of the same type T. BinaryOperator extends the BiFunction interface but constrains the parameter types and return type to all be identical. This makes BinaryOperator well-suited for use cases like mathematical operations, reductions, and finding min/max based on comparators.
Some key methods provided by the BinaryOperator interface include:
- apply() - Applies the lambda operation on two operands
- minBy() - Returns a minimum value based on a comparator
- maxBy() - Returns a maximum value based on a comparator
By using Java’s BinaryOperator functional interface, developers can write concise yet declarative functional code. In the rest of this article, we will explore BinaryOperator usage in more depth with code examples.
Introduction to BinaryOperator
The BinaryOperator
For example:
1BinaryOperator<Integer> add = (x, y) -> x + y;
2
3Integer result = add.apply(2, 3); //result is 5
The key features of BinaryOperator include:
Functional interface - Has a single abstract apply() method, allowing lambda expressions.
Same argument and return types - Operates on and returns the same generic type T.
Extends BiFunction - Inherits default and-then() method from BiFunction.
Specialized types - IntBinaryOperator, LongBinaryOperator exist for primitives.
Compared to BiFunction which can take different argument types, BinaryOperator constraints the types to be identical. This makes it convenient for mathematical and reduction operations.
BinaryOperator Methods
The BinaryOperator interface contains three main methods:
apply()
This abstract method accepts two parameters of type T and produces a result of type T. Lambda expressions or method references implement the apply() logic.
For example:
1BinaryOperator<String> concat = (str1, str2) -> str1 + str2;
2
3String result = concat.apply("Hello", "IToolkit"); //"HelloIToolkit"
minBy()
This static method accepts a Comparator and returns a BinaryOperator that returns the minimum of two values as defined by the Comparator.
For example:
1Comparator<Integer> cmp = (a, b) -> a.compareTo(b);
2
3BinaryOperator<Integer> min = BinaryOperator.minBy(cmp);
4
5Integer minVal = min.apply(5, 3); // 3
maxBy()
Similar to minBy(), but returns a BinaryOperator that returns the maximum of two values as defined by the passed Comparator.
For example:
1Comparator<String> cmp = (a, b) -> b.length() - a.length();
2
3BinaryOperator<String> max = BinaryOperator.maxBy(cmp);
4
5String maxStr = max.apply("Apple", "Banana"); // "Banana"
Using BinaryOperators
There are a few common ways BinaryOperators can be used:
With Lambda Expressions
1BinaryOperator<Long> addLongs = (x, y) -> x + y;
2
3long sum = addLongs.apply(5L, 10L); //15
With Method References
1BinaryOperator<String> concat = String::concat;
2
3String result = concat.apply("Hello ", "IToolkit!"); //"Hello IToolkit!"
Passing As Parameters
Can be passed to higher-order functions:
1public static <T> T reduce(List<T> list, T identity, BinaryOperator<T> accumulator){
2 T result = identity;
3 for(T t : list){
4 result = accumulator.apply(result, t);
5 }
6 return result;
7}
8
9Integer sum = reduce(Arrays.asList(1, 2, 3), 0, Integer::sum);
Type-Specific BinaryOperators
For better performance with primitive types, specialized BinaryOperator versions exist:
- IntBinaryOperator
- LongBinaryOperator
- DoubleBinaryOperator
These should be used instead of boxed BinaryOperators when operating on primitive types.
For example:
1IntBinaryOperator add = (x, y) -> x + y;
2int sum = add.applyAsInt(3, 5); // 8
BinaryOperator Code Examples
Here are some examples demonstrating common use cases:
Basic Usage
1// String concatenation
2BinaryOperator<String> concat = String::concat;
3String result = concat.apply("Hello ", "IToolkit!");
4
5// Numeric addition
6BinaryOperator<Integer> add = (x, y) -> x + y;
7int sum = add.apply(3, 5);
Finding Min/Max
1// Find min length string
2Comparator<String> cmp = Comparator.comparingInt(String::length);
3BinaryOperator<String> min = BinaryOperator.minBy(cmp);
4String minStr = min.apply("Apple", "Banana");
5
6// Find max length string
7BinaryOperator<String> max = BinaryOperator.maxBy(cmp);
8String maxStr = max.apply("Apple", "Banana");
Reducing Streams
1Integer sum = list.stream()
2 .reduce(0, (a, b) -> a + b);
3
4// With method reference
5Integer sum = list.stream()
6 .reduce(0, Integer::sum);
Primitive Types
1IntBinaryOperator add = (x, y) -> x + y;
2int sum = add.applyAsInt(3, 5);
3
4LongBinaryOperator longAdd = (x, y) -> x + y;
5long longSum = longAdd.applyAsLong(10000, 20000);
When to Use BinaryOperator
BinaryOperator is useful in these cases:
- Reducing parallel streams - concurrency safe
- Mathematical operations on the same types
- Finding min/max based on custom comparators
- Any generic functional operation on two same-typed operands
It provides a simple functional interface for these symmetric binary operations.
Conclusion
The BinaryOperator interface is a handy functional interface for operating on two values of the same type. It reduces boilerplate code over anonymous inner classes.
Common use cases include reducing collections, mathematical operations, finding min/max values based on custom comparisons, and eliminating null checks.
By leveraging BinaryOperator in Java 8 code alongside streams and lambdas, you can write very concise yet readable functional-style code.