Hi Friends,
After a long time, I came back here to make you aware of a feature available in Java 8. Today we are going to learn about Function Composition.
WHAT ?
As is it said, Function Composition is a process to combine two more functions to become a new function.
HOW ?
There are two methods provided to compose/combine functions, and it is available in Function interface. Those methods are andThen and compose. Both methods accept one parameter, of type Function.
WHY TWO ?
It’s true that both methods do the same job. The only difference between these methods is in the order they execute the functions. To be more precise, the compose method executes the parameter function first and the caller function last, but the andThen method executes the caller function first and the parameter function last.
See what javadoc says,
compose
default Function<V,R> compose(Function<? super V,? extends T> before)
Returns a composed function that first applies the before function to its input, and then applies this function to the result. If evaluation of either function throws an exception, it is relayed to the caller of the composed function.
andThen
default Function<T,V> andThen(Function<? super R,? extends V> after)
Returns a composed function that first applies this function to its input, and then applies the after function to the result. If evaluation of either function throws an exception, it is relayed to the caller of the composed function.
EXAMPLES
Hope all of you caught up so far. Let’s move on to some examples.
Function quote = s -> "'" + s + "'";
Function toString = Object::toString;
System.out.println(quote.compose(toString).apply(20));
System.out.println(toString.andThen(quote).apply(20));
Output
'20'
'20'
Here, both compose and andThen give the same result, as there’s no much difference in applying quotes before toString and after toString. So here it works fine. Let’s look into another example.
Function multipleBy2 = e -> e * 2;
Function square = e -> e * e;
System.out.println(multipleBy2.compose(square).apply(10));
System.out.println(multipleBy2.andThen(square).apply(10));
Output
200 // (10*10)^2
400 // (10*2)^2
Alas, both results are not same. What happens here is, It’s apparent that in the first operation, square is applied first, then multipleBy2 applied; while in the second operation, multipleBy2 is applied first, then square applied. This is how the compose method and the andThen method works.
MORE EXAMPLES
Now let’s take the leverage of function composition to another level.
Article.java
class Article {
private String author;
private String name;
private List<String> tags;
private Integer likes;
private Integer comments;
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getTags() {
return tags;
}
public void setTags(List<String> tags) {
this.tags = tags;
}
public Integer getLikes() {
return likes;
}
public void setLikes(Integer likes) {
this.likes = likes;
}
public Integer getComments() {
return comments;
}
public void setComments(Integer comments) {
this.comments = comments;
}
CompositionExample.java
public class CompositionExample {
static List<Article> articles = new ArrayList<>();
private static void constructArticles() {
Article article1 = new Article();
article1.setAuthor("Steve");
article1.setName("Java Collections");
article1.setTags(Arrays.asList("Java", "Collections", "Java 8"));
article1.setLikes(15);
article1.setComments(29);
Article article2 = new Article();
article2.setAuthor("Steve");
article2.setName("Spring Boot");
article2.setTags(Arrays.asList("Java", "Microservices", "Spring Boot"));
article2.setLikes(12);
article2.setComments(15);
Article article3 = new Article();
article3.setAuthor("Steve");
article3.setName("Apache Kafka");
article3.setTags(Arrays.asList("Kafka", "Stream"));
article3.setLikes(9);
article3.setComments(5);
Article article4 = new Article();
article4.setAuthor("Mark");
article4.setName("Hibernate");
article4.setTags(Arrays.asList("Java", "Hibernate", "ORM"));
article4.setLikes(28);
article4.setComments(41);
Article article5 = new Article();
article5.setAuthor("Mark");
article5.setName("Struts");
article5.setTags(Arrays.asList("Apache", "Struts"));
article5.setLikes(52);
article5.setComments(37);
articles.add(article1);
articles.add(article2);
articles.add(article3);
articles.add(article4);
articles.add(article5);
}
}
The method getMostLikedByAuthor()
private static Optional<Article> getMostLikedByAuthor(final String author, final List<Article> articleList) {
BiFunction<String, List<Article>, List<Article>> byAuthor = (name, articles) -> articles.stream()
.filter(a -> a.getAuthor().equals(name)).collect(Collectors.toList());
Function<List<Article>, Optional<Article>> mostLiked = articles -> articles.stream()
.max(Comparator.comparing(Article::getLikes));
return byAuthor.andThen(mostLiked).apply(author, articleList);
}
The method getLeastCommented()
private static Optional<Article> getLeastCommented(final String tag, final List<Article> articleList) {
BiFunction<String, List<Article>, List<Article>> byTag = (name, articles) -> articles.stream()
.filter(a -> a.getTags().contains(name)).collect(Collectors.toList());
Function<List<Article>, Optional<Article>> leastCommented = articles -> articles.stream()
.min(Comparator.comparing(Article::getComments));
return byTag.andThen(leastCommented).apply(tag, articleList);
}
The main() method
public static void main(String [] args) {
constructArticles();
Optional<Article> mostLikedByAuthor = getMostLikedByAuthor("Steve", articles);
if (mostLikedByAuthor.isPresent()) {
System.out.println("Most liked article written by 'Steve' is: " + mostLikedByAuthor.get().getName());
}
Optional<Article> leastCommentedByTag = getLeastCommented("Java", articles);
if (leastCommentedByTag.isPresent()) {
System.out.println("Least commented article tagged with 'Java' is: " + leastCommentedByTag.get().getName());
}
}
See the entire class below.
package com.petrosocial.viewer.xml.PdfExport;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
public class CompositionExample {
static List<Article> articles = new ArrayList<>();
private static void constructArticles() {
Article article1 = new Article();
article1.setAuthor("Steve");
article1.setName("Java Collections");
article1.setTags(Arrays.asList("Java", "Collections", "Java 8"));
article1.setLikes(15);
article1.setComments(29);
Article article2 = new Article();
article2.setAuthor("Steve");
article2.setName("Spring Boot");
article2.setTags(Arrays.asList("Java", "Microservices", "Spring Boot"));
article2.setLikes(12);
article2.setComments(15);
Article article3 = new Article();
article3.setAuthor("Steve");
article3.setName("Apache Kafka");
article3.setTags(Arrays.asList("Kafka", "Stream"));
article3.setLikes(9);
article3.setComments(5);
Article article4 = new Article();
article4.setAuthor("Mark");
article4.setName("Hibernate");
article4.setTags(Arrays.asList("Java", "Hibernate", "ORM"));
article4.setLikes(28);
article4.setComments(41);
Article article5 = new Article();
article5.setAuthor("Mark");
article5.setName("Struts");
article5.setTags(Arrays.asList("Apache", "Struts"));
article5.setLikes(52);
article5.setComments(37);
articles.add(article1);
articles.add(article2);
articles.add(article3);
articles.add(article4);
articles.add(article5);
}
private static Optional<Article> getMostLikedByAuthor(final String author, final List<Article> articleList) {
BiFunction<String, List<Article>, List<Article>> byAuthor = (name, articles) -> articles.stream()
.filter(a -> a.getAuthor().equals(name)).collect(Collectors.toList());
Function<List<Article>, Optional<Article>> mostLiked = articles -> articles.stream()
.max(Comparator.comparing(Article::getLikes));
return byAuthor.andThen(mostLiked).apply(author, articleList);
}
private static Optional<Article> getLeastCommented(final String tag, final List<Article> articleList) {
BiFunction<String, List<Article>, List<Article>> byTag = (name, articles) -> articles.stream()
.filter(a -> a.getTags().contains(name)).collect(Collectors.toList());
Function<List<Article>, Optional<Article>> leastCommented = articles -> articles.stream()
.min(Comparator.comparing(Article::getComments));
return byTag.andThen(leastCommented).apply(tag, articleList);
}
public static void main(String [] args) {
constructArticles();
Optional<Article> mostLikedByAuthor = getMostLikedByAuthor("Steve", articles);
if (mostLikedByAuthor.isPresent()) {
System.out.println("Most liked article written by 'Steve' is: " + mostLikedByAuthor.get().getName());
}
Optional<Article> leastCommentedByTag = getLeastCommented("Java", articles);
if (leastCommentedByTag.isPresent()) {
System.out.println("Least commented article tagged with 'Java' is: " + leastCommentedByTag.get().getName());
}
}
}
class Article {
private String author;
private String name;
private List<String> tags;
private Integer likes;
private Integer comments;
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getTags() {
return tags;
}
public void setTags(List<String> tags) {
this.tags = tags;
}
public Integer getLikes() {
return likes;
}
public void setLikes(Integer likes) {
this.likes = likes;
}
public Integer getComments() {
return comments;
}
public void setComments(Integer comments) {
this.comments = comments;
}
}
Now, let’s execute this code.
Output
Most liked article written by 'Steve' is: Java Collections
Least commented article tagged with 'Java' is: Spring Boot
That’s it. Now you have moved towards the end of this article and you are on the verge of experiencing function composition. Try it and have fun.
Thank you.