JDK-8318298 : Add capturing factories to classes in java.util.function package
  • Type: CSR
  • Component: core-libs
  • Sub-Component: java.util
  • Priority: P4
  • Status: Closed
  • Resolution: Withdrawn
  • Submitted: 2023-10-17
  • Updated: 2023-10-23
  • Resolved: 2023-10-23
Related Reports
CSR :  
Description
Summary
-------

Introduce _capturing factories_ in classes in the `java.util.function` package.

Problem
-------

Lamdas and method references can often match several functional interfaces and so they have to be bound to a certain functional type using an explicit type declaration or a cast.

Consider the following class that contains a method inverting the value of a provided `boolean` parameter and two other methods that takes distinct functional parameters:

```
public class Capturing {

         static boolean not(boolean original) {
             return !original;
         }

        static <T,U> void foo(Function<T, Boolean> f)
            // Do stuff
        }

        static <T> void foo(Predicate<T> p) {
            // Do stuff
        }

}
```

We can turn a method reference or a lambda involving this method into several distinct functional types using explicit declaration as exemplified here:

    Function<Boolean, Boolean> function = Capturing::not;
    Predicate<Boolean> predicate = Capturing::not;
    UnaryOperator<Boolean> unaryOperator = Capturing::not;

    Function<Boolean, Boolean> functionLambda = b -> Capturing.not(b);

    Function<Boolean, String> composed = function.andThen(Objects::toString);

    Capturing.foo(function);

However, if we want to use local variable type inference using `var` declaration or if we want to use method reference composition fluently, we need to resort to explicit casting as these examples show:


    var ambiguous = Capturing::not; // Fails!
    var function = (Function<Boolean, Boolean>) Capturing::not;
    var predicate = (Predicate<Boolean>) Capturing::not;
    var unaryOperator = (UnaryOperator<Boolean>) Capturing::not;

    var functionLambda = (Function<Boolean, Boolean>) b -> Capturing.not(b);

    // Fluent, not using a previously captured functional type
    var composed = ((Function<Boolean, Boolean>) Capturing::not).andThen(Objects::toString);

    Capturing.foo((Function<Boolean, Boolean>) Capturing::not);

This can sometimes cause inconveniences and excessive code that is hard to read. There is a need for a better solution.

Solution
--------

Functional interfaces like `java.util.function.Function` receive a new type of static factory called a _capturing factory_ that allows binding and the engagement of type inference in a concise way. Here is an example of how such a factory might look like:

    /**
     * {@return a representation of the provided {@code uncaptured} lambda or method reference in the form of a Function}
     *
     * @param uncaptured to capture
     * @param <T> the type of the input to the function
     * @param <R> the type of the result of the function
     */
    static <T, R> Function<T, R> of(Function<T, R> uncaptured) {
        return uncaptured;
    }

At first glance, the method might look redundant. However, the introduction of functions like these allows us to substantially improve code readability in certain cases. Here is how `var` declarations and fluent composition could look like with the new factory methods:

    var function = Function.of(Capturing::not);
    var predicate = Predicate.of(Capturing::not);
    var unaryOperator = UnaryOperator.of(Capturing::not);

    var functionLambda = Function.of(b -> Capturing.not(b));

    // Fluent, not using a previously captured functional type
    var composed = Function.of(Capturing::not).andThen(Objects::toString);
    var composedLambda = Function.of((Boolean b) -> !b).andThen(Objects::toString);

    Capturing.foo(Function.of(Capturing::not));

As can be seen, there is no longer any need to resort to casting, and the generic types are inferred automatically by the compiler. In short, the code becomes much more readable and concise.

It is possible to add a capturing factory to all classes in the package or only to such functional interfaces that, in combination with at least another functional interface, introduce ambiguities AND to functional interfaces that support composition.

Specification
-------------

```

diff --git a/src/java.base/share/classes/java/util/function/BiConsumer.java b/src/java.base/share/classes/java/util/function/BiConsumer.java
index 61690e6471a7..8b2e75081f9c 100644
--- a/src/java.base/share/classes/java/util/function/BiConsumer.java
+++ b/src/java.base/share/classes/java/util/function/BiConsumer.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -72,4 +72,37 @@ default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after) {
             after.accept(l, r);
         };
     }
+
+    /**
+     * {@return a representation of the provided {@code uncaptured} lambda or method reference
+     * in the form of a {@code BiConsumer}}
+     * <p>
+     * This method is useful in cases where there is an ambiguity in a lambda or method reference
+     * or when using composition or fluent coding as shown in this example:
+     * {@snippet :
+     * void toConsole(long id, String message) {
+     *      System.out.format("%d = %s%n", id, message);
+     * }
+     *
+     * void toLogger(long id, String message) {
+     *      LOGGER.info(String.format("%d = %s", id, message));
+     * }
+     *
+     * // Capturing
+     * var con = BiConsumer.of(this::toConsole); // BiConsumer<Long, String>
+     *
+     *  // Fluent composition
+     * var composed = BiConsumer.of(this::toConsole)
+     *                    .andThen(this::toLogger);  // BiConsumer<Long, String>
+     *
+     * }
+     *
+     * @param uncaptured to capture
+     * @param <T> the type of the first argument to the operation
+     * @param <U> the type of the second argument to the operation
+     */
+    static <T, U> BiConsumer<T, U> of(BiConsumer<T, U> uncaptured) {
+        return Objects.requireNonNull(uncaptured);
+    }
+
 }
diff --git a/src/java.base/share/classes/java/util/function/BiFunction.java b/src/java.base/share/classes/java/util/function/BiFunction.java
index ef75901fb346..30545c855e9d 100644
--- a/src/java.base/share/classes/java/util/function/BiFunction.java
+++ b/src/java.base/share/classes/java/util/function/BiFunction.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2023, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -69,4 +69,30 @@ default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after)
         Objects.requireNonNull(after);
         return (T t, U u) -> after.apply(apply(t, u));
     }
+
+    /**
+     * {@return a representation of the provided {@code uncaptured} lambda or method reference
+     * in the form of a {@code BiFunction}}
+     * <p>
+     * This method is useful in cases where there is an ambiguity in a lambda or method reference
+     * or when using composition or fluent coding as shown in this example:
+     * {@snippet :
+     * // Resolve ambiguity
+     * var function = BiFunction.of(String::endsWith);   // BiFunction<String, String, Boolean>
+     * var predicate = BiPredicate.of(String::endsWith); // BiPredicate<String, String>
+     *
+     * // Fluent composition
+     * var chained = BiFunction.of(String::repeat)     // BiFunction<String, Integer, String>
+     *                   .andThen(String::length);     // Function<String, Integer>
+     * }
+     *
+     * @param uncaptured to capture
+     * @param <T> the type of the first argument to the function
+     * @param <U> the type of the second argument to the function
+     * @param <R> the type of the result of the function
+     */
+    static <T, U, R> BiFunction<T, U, R> of(BiFunction<T, U, R> uncaptured) {
+        return Objects.requireNonNull(uncaptured);
+    }
+
 }
diff --git a/src/java.base/share/classes/java/util/function/BiPredicate.java b/src/java.base/share/classes/java/util/function/BiPredicate.java
index a0f19b812d0e..df4d9f3d2f7b 100644
--- a/src/java.base/share/classes/java/util/function/BiPredicate.java
+++ b/src/java.base/share/classes/java/util/function/BiPredicate.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2023, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -104,4 +104,29 @@ default BiPredicate<T, U> or(BiPredicate<? super T, ? super U> other) {
         Objects.requireNonNull(other);
         return (T t, U u) -> test(t, u) || other.test(t, u);
     }
+
+    /**
+     * {@return a representation of the provided {@code uncaptured} lambda or method reference
+     * in the form of a {@code BinaryOperator}}
+     * <p>
+     * This method is useful in cases where there is an ambiguity in a lambda or method reference
+     * or when using composition or fluent coding as shown in this example:
+     * {@snippet :
+     * // Resolve ambiguity
+     * var biFunction = BiFunction.of(String::equals);     // BiFunction<String, Object, Boolean>
+     * var biPredicate = BiPredicate.of(String::equals);   // BiPredicate<Integer, Object>
+     *
+     * // Fluent composition
+     * var composed = BiPredicate.of(String::endsWith)     // BiPredicate<String, String>
+     *                    .or(String::startsWith);         // BiPredicate<String, String>
+     *}
+     *
+     * @param uncaptured to capture
+     * @param <T> the type of the first argument to the predicate
+     * @param <U> the type of the second argument the predicate
+     */
+    static <T, U> BiPredicate<T, U> of(BiPredicate<T, U> uncaptured) {
+        return Objects.requireNonNull(uncaptured);
+    }
+
 }
diff --git a/src/java.base/share/classes/java/util/function/BinaryOperator.java b/src/java.base/share/classes/java/util/function/BinaryOperator.java
index a0150589c808..e3df95747d30 100644
--- a/src/java.base/share/classes/java/util/function/BinaryOperator.java
+++ b/src/java.base/share/classes/java/util/function/BinaryOperator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2023, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -73,4 +73,27 @@ public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
         Objects.requireNonNull(comparator);
         return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
     }
+
+    /**
+     * {@return a representation of the provided {@code uncaptured} lambda or method reference
+     * in the form of a {@code BinaryOperator}}
+     * <p>
+     * This method is useful in cases where there is an ambiguity in a lambda or method reference
+     * or when using composition or fluent coding as shown in this example:
+     * {@snippet :
+     * // Resolve ambiguity
+     * var biFunction = BiFunction.of(Integer::sum);        // BiFunction<Integer, Integer, Integer>
+     * var unaryOperator = BinaryOperator.of(Integer::sum); // BinaryOperator<Integer>
+     *
+     * // Fluent composition
+     * var composed = BinaryOperator.of(Integer::sum)     // BinaryOperator<Integer>
+     *                    .andThen(Integer::toHexString); // BiFunction<Integer, Integer, String>
+     * }
+     *
+     * @param uncaptured to capture
+     * @param <T> the type of the operands and result of the operator
+     */
+    static <T> BinaryOperator<T> of(BinaryOperator<T> uncaptured) {
+        return Objects.requireNonNull(uncaptured);
+    }
 }
diff --git a/src/java.base/share/classes/java/util/function/Consumer.java b/src/java.base/share/classes/java/util/function/Consumer.java
index a2481fe4f41f..922ba19bd200 100644
--- a/src/java.base/share/classes/java/util/function/Consumer.java
+++ b/src/java.base/share/classes/java/util/function/Consumer.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2023, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -64,4 +64,28 @@ default Consumer<T> andThen(Consumer<? super T> after) {
         Objects.requireNonNull(after);
         return (T t) -> { accept(t); after.accept(t); };
     }
+
+    /**
+     * {@return a representation of the provided {@code uncaptured} lambda or method reference
+     * in the form of a {@code Consumer}}
+     * <p>
+     * This method is useful in cases where there is an ambiguity in a lambda or method reference
+     * or when using composition or fluent coding as shown in this example:
+     * {@snippet :
+     * List<String> list = new ArrayList<>();
+     *
+     * var con = Consumer.of(list::addLast); // Consumer<String>
+     *
+     * // Fluent composition
+     * var composed = Consumer.of(list::addLast)       // Consumer<String>
+     *                    .andThen(System.out::print); // Consumer<String>
+     *}
+     *
+     * @param uncaptured to capture
+     * @param <T> the type of the input to the operation
+     */
+    static <T> Consumer<T> of(Consumer<T> uncaptured) {
+        return Objects.requireNonNull(uncaptured);
+    }
+
 }
diff --git a/src/java.base/share/classes/java/util/function/Function.java b/src/java.base/share/classes/java/util/function/Function.java
index 7bcbfd06bb6a..82e454f8a958 100644
--- a/src/java.base/share/classes/java/util/function/Function.java
+++ b/src/java.base/share/classes/java/util/function/Function.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2023, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -97,4 +97,29 @@ default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
     static <T> Function<T, T> identity() {
         return t -> t;
     }
+
+    /**
+     * {@return a representation of the provided {@code uncaptured} lambda or method reference
+     * in the form of a {@code Function}}
+     * <p>
+     * This method is useful in cases where there is an ambiguity in a lambda or method reference
+     * or when using composition or fluent coding as shown in these examples:
+     * {@snippet :
+     * // Resolve ambiguity
+     * var function = Function.of(String::isEmpty); // Function<String, Boolean>
+     * var predicate = Predicate.of(String::isEmpty); // Predicate<String>
+     *
+     * // Fluent composition
+     * var chained = Function.of(String::length)       // Function<String, Integer>
+     *                   .andThen(Integer::byteValue); // Function<String, Byte>
+     * }
+     *
+     * @param uncaptured to capture
+     * @param <T> the type of the input to the function
+     * @param <R> the type of the result of the function
+     */
+    static <T, R> Function<T, R> of(Function<T, R> uncaptured) {
+        return Objects.requireNonNull(uncaptured);
+    }
+
 }
diff --git a/src/java.base/share/classes/java/util/function/Predicate.java b/src/java.base/share/classes/java/util/function/Predicate.java
index b34febbd6136..eb01270c2ca8 100644
--- a/src/java.base/share/classes/java/util/function/Predicate.java
+++ b/src/java.base/share/classes/java/util/function/Predicate.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2023, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -137,4 +137,28 @@ static <T> Predicate<T> not(Predicate<? super T> target) {
         Objects.requireNonNull(target);
         return (Predicate<T>)target.negate();
     }
+
+    /**
+     * {@return a representation of the provided {@code uncaptured} lambda or method reference
+     * in the form of a {@code Predicate}}
+     * <p>
+     * This method is useful in cases where there is an ambiguity of a lambda or method reference
+     * or when using composition and/or fluent coding as shown in this example:
+     * {@snippet :
+     * // Resolve ambiguity
+     * var function = Function.of(String::isEmpty);   // Function<String, Boolean>
+     * var predicate = Predicate.of(String::isEmpty); // Predicate<String>
+     *
+     * // Fluent composition
+     * var composed = Predicate.of(String::isEmpty)
+     *                   .or(s -> s.startsWith("*")); // Predicate<String>
+     * }
+     *
+     * @param uncaptured to capture
+     * @param <T> the type of the input to the predicate
+     */
+    static <T> Predicate<T> of(Predicate<T> uncaptured) {
+        return Objects.requireNonNull(uncaptured);
+    }
+
 }
diff --git a/src/java.base/share/classes/java/util/function/UnaryOperator.java b/src/java.base/share/classes/java/util/function/UnaryOperator.java
index c4aa749a68a0..6889998f924a 100644
--- a/src/java.base/share/classes/java/util/function/UnaryOperator.java
+++ b/src/java.base/share/classes/java/util/function/UnaryOperator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -24,6 +24,8 @@
  */
 package java.util.function;
 
+import java.util.Objects;
+
 /**
  * Represents an operation on a single operand that produces a result of the
  * same type as its operand.  This is a specialization of {@code Function} for
@@ -49,4 +51,46 @@ public interface UnaryOperator<T> extends Function<T, T> {
     static <T> UnaryOperator<T> identity() {
         return t -> t;
     }
+
+    /**
+     * Returns a composed function that first applies this unary operator to
+     * its input, and then applies the {@code after} unary function to the result.
+     * If evaluation of either function throws an exception, it is relayed to
+     * the caller of the composed function.
+     *
+     * @param after the unary operator to apply after this unary operator is applied
+     * @return a composed unary operator that first applies this function and then
+     * applies the {@code after} unary operator
+     * @throws NullPointerException if after is null
+     *
+     * @see Function#andThen(Function)
+     */
+    default UnaryOperator<T> andThenUnary(UnaryOperator<T> after) {
+        Objects.requireNonNull(after);
+        return (T t) -> after.apply(apply(t));
+    }
+
+    /**
+     * {@return a representation of the provided {@code uncaptured} lambda or method reference
+     * in the form of a {@code UnaryOperator}}
+     * <p>
+     * This method is useful in cases where there is an ambiguity of a lambda or method reference
+     * or when using composition and/or fluent coding as shown in this example:
+     * {@snippet :
+     * // Resolve ambiguity
+     * var function = Function.of(String::stripTrailing); // Function<String, String>
+     * var unaryOperator = UnaryOperator.of(String::stripTrailing); // UnaryOperator<String>
+     *
+     * // Fluent composition
+     * var composed = UnaryOperator.of(String::stripTrailing)
+     *                    .andThenUnary(String::stripIndent);  // UnaryOperator<String>
+     * }
+     *
+     * @param uncaptured to capture
+     * @param <T> the type of the operand and result of the operator
+     */
+    static <T> UnaryOperator<T> of(UnaryOperator<T> uncaptured) {
+        return Objects.requireNonNull(uncaptured);
+    }
+
 }

```

Comments
The PR also contains an added function `UnaryOperator::andThenUnary` which is related to the problem at hand but may be introduced under a separate PR/CSR.
17-10-2023