0

I been looking at java FunctionalInterface java.util.function.Consumer which have the source code

package java.util.function;
import java.util.Objects;

public interface Consumer<T> {
    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }

}

so if I call consumer1.andThen(consumer2)... andThen(consumerN).accept(); it seem to me that this is effectively creating N + (N - 1) of Consumer Objects. Please let me know if I am using it wrong or am I not supposed to use it that way.

Here is my test code to test the issue. Am I missing something??

public class Test1 {
    public static int c = 0;

    public static int acceptC = 0;

    public Test1(){
        ++c;
        System.out.println("new Object Created = "+c);
    }

    public void accept(int i){
        acceptC++;
        System.out.println("accept call "+ acceptC);
    }

    public Test1 andThen(Test1 after) {
       return new Test1(){
        @Override
        public void accept(int i) {
            acceptC++;
            System.out.println("accept call in temp = " +acceptC) ;
            Test1.this.accept(++i); after.accept(++i);
        } 

        };
    }

    public static void main(String args[]){
        Test1 t1 = new Test1();
        Test1 t2 = new Test1();
        Test1 t3 = new Test1();
        Test1 t4 = new Test1();
        Test1 t5 = new Test1();

        t1.andThen(t2).andThen(t3).andThen(t4).andThen(t5).accept(0);
        System.out.println("RESULT total Test Object created ="+Test1.c);
    }
}

I get the Out Put

new Object Created = 1
new Object Created = 2
new Object Created = 3
new Object Created = 4
new Object Created = 5
new Object Created = 6
new Object Created = 7
new Object Created = 8
new Object Created = 9
accept call in temp = 1
accept call in temp = 2
accept call in temp = 3
accept call in temp = 4
accept call 5
accept call 6
accept call 7
accept call 8
accept call 9
RESULT total Test Object created =9

I know this does not matter a lot unless you are processing a lots data that way.. but just wanted to know

ypwong
  • 9
  • 3

1 Answers1

1

It is intentionally unspecified whether a new object will be created when evaluating a lambda expression to an instance of a functional interface, see also Does a lambda expression create an object on the heap every time it's executed?.

Therefore, we can’t make a general statement about the number of object instances being created for this operation, especially for repeated executions of the same code, where reusable instances might exist (and that’s the scenario where it might matter). Further, the exact implementation of Consumer.andThen is not fixed and doesn’t have to look like the posted code. It’s also important that default methods are overridable, so if the first Consumer you’re calling andThen on is not the result of a lambda expression, but a custom implementation, it could change the entire outcome dramatically.

Practically, with the current implementation, the lambda expression (T t) -> { accept(t); after.accept(t); } will capture two references to other Consumer instances and therefore evaluate to a new Consumer instance each time, just like with your concrete class variant. So this chain of andThen calls will create an unbalanced tree of Consumer instances, like in your variant.

This, however, does not imply that all leaf consumer instances will be new instances. In your example, all leaf functions have the form acceptC++; System.out.println("accept call "+ acceptC);, which would imply capturing this to be able to modify acceptC, so an equivalent lambda expression would evaluate to new instances each time. But this is not a real life scenario. In practice, you would use andThen to combine different consumers, so some of them might be capturing, others might not, so some of them might get reused while others don’t.

That’s all for the current implementation. A future implementation might fold different but identical lambda expressions to a single instance or recognize when a lambda expression only captures references to reusable lambda instances to make it reusable as well. This could have a cascading effect, reducing the number of instances dramatically.

You said that you are already aware that the number of objects doesn’t matter that much. But it’s worth noting that an excessive use of andThen creates a deep unbalanced tree of consumers whose execution of the accept method may require significant stack depth.

Holger
  • 285,553
  • 42
  • 434
  • 765