Search code examples
javalambdajava-8functional-interface

Why does forEach method accept lambda that invokes method with multiple arguments when Consumer only takes one argument?


I am playing with forEach on a List<String>, and I'm confused about why the following line is acceptable:

policies.forEach(policy -> test.addToDatabase(policy, stats));

Since forEach requires a Consumer, and the Consumer accept method only takes one argument, I don't understand why the call to addtoDatabase is acceptable, as it takes two arguments. See below for full test code. Note that I am only playing around here to learn, so this code is not meant to be perfect or elegant.

public class ConsumerTest {

    private Random random = new Random();

    public static void main(String[] args) {
        ConsumerTest test = new ConsumerTest();
        List<String> policies = new ArrayList<>();
        policies.add("11111");
        policies.add("22222");
        policies.add("33333");
        policies.add("44444");
        policies.add("55555");
        Stats stats = test.new Stats();
        policies.forEach(policy -> test.addToDatabase(policy, stats));
        System.out.println("Success count: " + stats.getSuccessCount() + "\nFailure count: " + stats.getFailureCount());
    }

    private void addToDatabase(String policy, Stats stats) {
        // simulate success/failure adding to DB with Random
        if (random.nextBoolean()) {
            stats.incrementSuccessCount();
            System.out.println("Success for Policy " + policy);
        } else {
            stats.incrementFailureCount();
            System.out.println("Failure for Policy " + policy);
        }
    }

    class Stats {
        private int successCount;
        private int failureCount;
        public void incrementSuccessCount() {
            successCount++;
        }
        public void incrementFailureCount() {
            failureCount++;
        }
        public int getSuccessCount() {
            return successCount;
        }
        public int getFailureCount() {
            return failureCount;
        }
    }
}

Solution

  • I am playing with forEach on a List of Strings, and I'm confused about why the following line is acceptable:

    policies.forEach(policy -> test.addToDatabase(policy, stats));

    It is. You confuse the parameter of the Iterable::forEach with the parameters of the statements inside the lambda expression. Since the only parameter inside the Iterable::forEach is Consumer<T> which is nothing than an implementation of the very same anonymous class:

    Consumer<String> consumer = new Consumer<>() {
        @Override
        public void accept(final String policy) {
            test.addToDatabase(policy, stats)
        }
    };
    
    policies.forEach(consumer);
    

    It is the same as:

    Consumer<String> consumer = policy -> test.addToDatabase(policy, stats);
    policies.forEach(consumer);
    

    What is inside doesn't matter - the number of passed parameters into Iterable::forEach remains only one:

    policies.forEach(policy -> {
        log.info("adding to database");
        test.addToDatabase(policy, stats);
        log.info("added to database");
    });
    

    There is theoretically an unlimited number of statements and variables you can work with. The only condition that whatever you use inside the lambda expression must be effectively final.