Reputation: 1133
I am new to Java 8, just want to ask what is the difference in performance between the two code snippets below.
I know both work, but I am wondering why the Java team created Consumer::andThen
method when I can use the normal approach.
List<String> list = Arrays.asList("aaa","cccc","bbbb");
List<String> list2 = new ArrayList<>();
//Approach 1
list.stream().forEach(x -> {
list2.add(x);
System.out.println(x);
});
//Approach 2
Consumer<String> c1 = s -> list2.add(s);
Consumer<String> c2 = s -> System.out.println(s);
list.stream().forEach(c1.andThen(c2));
IMO, approach1 is better, why approach2 again? If both are same, then why andThen() method is created? I guess there must be reason they've created.
In other words, when exactly is the andThen()
method really helpful?
Upvotes: 5
Views: 5436
Reputation: 12738
There are two questions in the original post:
Performance-wise, I don't think it would be better; considering the time classic foreach loop have existed, optimisation of memory usage and other aspects may long have been there; and the overhead of creating other references in Consumer, so no, it's not faster/lighter.
So why? It's not about if it's faster, but about a paradigm shift, marking one new phase of Java when you can start thinking from the functional way. Before, functional perspective/way of programming or thinking in Java is not natively supported, but now it is. A "pipeline stream" way of processing data now is syntactically possible and readable. So it's an effort to achieve syntax and semantics consistency, so you can write in functional way, and others can understand because it can be read in a fluent way, with proper keyword assembling corresponding meaning. To know that how you read affects how you think, and if you cannot write/read it in a functional way, you cannot think so in Java. So it's a step towards making Java a multi-paradigm language.
(Apache Camel does pipeline processing in its concept even before that, even without this; but that's a rare example in a language like Java where functional was not there from the beginning).
Imagine now you can write code:
// functional and easy to read
readInput().forEach(convertToUpperCase().andThen().store());
vs.
// faster but not functional
readInput().forEach(word -> {
String upperCase = convertToUpperCase();
store(upperCase);
}
That's just one simple example so the difference is small; but I imagine there could be code much easier to read in functional way and you could understand the whole process just by reading it, vs. traditional Java non-functional way where it's obfuscate to understand what is happening without fully diving into code across several places.
Not knowing much about functional programming but the style is simple and beautiful even with my current shallow experience: one element being transformed/joined with others/grouped, among other processing, then result.
Upvotes: 0
Reputation: 129
The most obvious use case seems to the ability to combine your consumers as you wish while making each one of them re-usable. It does just make the code cleaner and more "functional ready". It will not be much useful if you don't need to re-use the same consumer logic elsewhere.
Upvotes: -1
Reputation: 10681
Just as a side note, there is another approach possible:
Approach 3
list.stream()
.peek(System.out::println)
.forEach(list2::add);
or even better:
List<String> list2 = list.stream()
.peek(System.out::println)
.collect(Collectors.toList());
Performance-wise all the approaches are comparable, but the last one is the most "functional". The problem with the previous ones is the insertion into the second list inside the stream. This is a side-effect, which is considered a bad practice. From this perspective, the approach with the two consumers is the worst in my opinion, since the side effect is the most hidden.
Upvotes: 3
Reputation: 393841
I agree that is makes less sense to create a separate Consumer<String>
instance for each statement just for the sake of using andThen
.
However, if you already have two Consumer<String>
instances (c1
and c2
), and you need to execute both, you could either write:
list.forEach(x -> {
c1.accept(x);
c2.accept(x);
});
or
list.forEach(c1.andThen(c2));
In this case, the latter is clearly cleaner.
EDIT:
I'd like to expand on the case when you create your own Consumer
s (i.e. not re-using pre-existing ones). When you assign a lambda expression to a Consumer
variable, you can use that Consumer
instance in multiple places instead of duplicating the lambda expression. That is true for any functional interface.
Once you decide to use a Consumer
variable, the decision of whether to have a single Consumer
implementing complex logic or 2 simpler Consumer
s, each implementing part of the logic, depends on whether there is a need to be able to execute just part of the logic in some cases (or changing the order in which parts of the logic are executed).
Having c1
and c2
allows you either of the following options:
list.forEach(c1);
list.forEach(c2);
list.forEach(c1.andThen(c2));
list.forEach(c2.andThen(c1));
If you had a single consumer that does the logic of both c1
and c2
, you have less flexibility.
Upvotes: 13