Functional interfaces

The argument to the method should be java.util.function.Consumer. This is an interface that requires the accept method to be defined, and this method is void. The lambda expression or a class that implements this interface will consume the argument of the accept method and does not produce anything.

There are several other interfaces defined in that package, each serving as a functional interface used to describe some method arguments that can be given as lambda expressions in the actual parameters.

For example, the opposite of Consumer is Supplier. This interface has a method named get that does not need any argument but gives some Object as a return value.

If there is an argument and also a returned value, the interface is called Function. If the returned value has to be the same type as the argument, then the UnaryOperator interface is our friend. Similarly, there is a BinaryOperator interface, which returns an object of the same type as the arguments. Just as we got from Function to UnaryOperator, we can see that in the other direction, there is also BiFunction in case the arguments and the return values do not share the type.

These interfaces are not defined independently of each other. If a method requires Function and we have UnaryOperator to pass, it should not be a problem. UnaryOperator is nothing else but Function that has the same type of arguments. A method that can work with Function, which accepts an object and returns an object, should not have a problem if they have the same type. Those can be, but need not be, different.
To let that happen, the UnaryOperator interface extends Function and thus can be used in the place of Function.

The interfaces in this class we met so far are defined using generics. Because generic types cannot be primitives, the interfaces that operate on primitive values should be defined separately. Predicate, for example, is an interface that defines booleantest(T t). It is a function that returns a boolean value and is used many times in stream methods.

There are also interfaces, such as BooleanSupplier, DoubleConsumer, DoubleToIntFunction, and more, that work with primitive boolean, double, and int. The number of possible combinations of the different argument types and return values is infinite... almost.

Fun fact: To be very precise, it is not infinite. A method can have at most 254 arguments. This limit is specified in the JVM and not in the Java language specification. Of course, one is useless without the other. There are 8 primitive types (plus Object, plus the possibility that there are less than 254 arguments), which means that the total number of possible functional interfaces is 10254, give or take, a few magnitudes. Practically, infinite!

We should not expect to have all the possible interfaces defined in the JDK in this package. These are only those interfaces that are the most useful. There is no interface, for example, that uses short or char. If we need anything like that, then we can define the interface in our code. Or just think hard and find out how to use an already defined one. (I have never used the short type during my professional carrier. It was never needed.)

How are these functional interfaces used in streams? The Stream interface defines the methods that have some functional interface types as arguments. For example, the allMatch method has a Predicate argument and returns a Boolean value, which is true if all the elements in the stream match Predicate. In other words, this method returns true if and only if Predicate, supplied as an argument, returns true for each and every element of the stream.

In the following code, we will rewrite some of the methods that we implemented in our sample code using loops to use streams, and through these examples, we will discuss the most important methods that streams provide. We saved up two classes, ProductsCheckerCollector and ProductInformationCollector, to demonstrate the stream usage. We can start with these. ProductsCheckerCollector goes through all the products that are contained in the Order and collects the annotations that are listed in the products. Each product may contain zero, one, or many annotations. These are available in a list. The same annotation may be referenced multiple times. To avoid duplicates, we use HashSet, which will contain only one instance of the elements even if there are multiple instances in the products:

public class ProductsCheckerCollector { 

private final ProductInformationCollector pic;
public ProductsCheckerCollector(@Autowired
ProductInformationCollector pic) { this.pic = pic; }

public Set<Class<? extends Annotation>>
getProductAnnotations(Order order) {
Map<OrderItem, ProductInformation> piMap =
pic.collectProductInformation(order);
final Set<Class<? extends Annotation>>
annotations = new HashSet<>();
for (OrderItem item : order.getItems()) {
final ProductInformation pi = piMap.get(item);
if (pi != null && pi.getCheck() != null) {
for (Class<? extends Annotation> check :
pi.getCheck()) {
annotations.addAll(pi.getCheck());
}
}
return annotations;
}
}

Now, let's see how this method looks when we recode it using streams:

public Set<Class<? extends Annotation>> 
getProductAnnotations(Order order) {
Map<OrderItem, ProductInformation> piMap =
pic.collectProductInformation(order);

return order.getItems().stream()
.map(piMap::get)
.filter(Objects::nonNull)
.peek(pi -> {
if (pi.getCheck() == null) {
log.info("Product {} has no annotation",
pi.getId());
}
})
.filter(pi -> pi.getCheck() != null)
.peek(pi -> log.info("Product {} is annotated with class {}", pi.getId(), pi.getCheck()))
.flatMap(pi -> pi.getCheck().stream())
.collect(Collectors.toSet());
}

The major work of the method gets into a single, though huge, stream expression. We will cover the elements of the expression in the coming pages. List returned by order.getItems is converted calling the stream method:

returnorder.getItems().stream()

As we have already mentioned it briefly, the stream method is part of the Collection interface. Any class that implements the Collection interface will have this method, even those that were implemented before streams were introduced in Java 8. This is because the stream method is implemented in the interface as a default method. This way, if we happen to implement a class implementing this interface, even if we do not need streams, we get it for free as an extra.

The default methods in Java 8 were introduced to support backward compatibility of interfaces. Some of the interfaces of the JDK were to be modified to support lambda and functional programming. One example is the stream method. With the pre-Java 8 feature set, the classes implementing some of the modified interfaces should have been modified. They would have been required to implement the new method. Such a change is not backward compatible, and Java as a language and JDK was paying keen attention to be backward compatible. Thus, default methods were introduced. These let a developer extend an interface and still keep it backward compatible, providing a default implementation for the methods, which are new.
Contrary to this philosophy, brand new functional interfaces of Java 8 JDK also have default methods, though, having no prior version in the JDK, they have nothing to be compatible with. In Java 9, interfaces were also extended and now they can contain not only default and static methods but also private methods. This way, interfaces became kind of equivalent to abstract classes, though there are no fields in an interface except constant static fields. This interface functionality open up is a much criticized feature, which just poses the programming style and structural issues that other languages allowing multiple class inheritance face. Java was avoiding this till Java 8 and Java 9.
What is the take-away from this? Be careful with default methods and also with private methods in interfaces. Use them wisely if at all.

The elements of this stream are OrderItem objects. We need ProductInformation for each OrderItem.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.186.178