1. OverviewNow that Java 8 has reached wide usage, patterns and best practices have begun to emerge for some of its headlining features. In this tutorial, we'll take a closer look at functional interfaces and lambda expressions. Show
2. Prefer Standard Functional InterfacesFunctional interfaces, which are gathered in the java.util.function package, satisfy most developers' needs in providing target types for lambda expressions and method references. Each of these interfaces is general and abstract, making them easy to adapt to almost any lambda expression. Developers should explore this package before creating new functional interfaces. Let's consider an interface Foo:
In addition, we have a method add() in some class UseFoo, which takes this interface as a parameter:
To execute it, we would write:
If we look closer, we'll see that Foo is nothing more than a function that accepts one argument and produces a result. Java 8 already provides such an interface in Function<T,R> from the java.util.function package. Now we can remove interface Foo completely and change our code to:
To execute this, we can write:
3. Use the @FunctionalInterface AnnotationNow let's annotate our functional interfaces with @FunctionalInterface. At first, this annotation seems to be useless. Even without it, our interface will be treated as functional as long as it has just one abstract method. However, let's imagine a big project with several interfaces; it's hard to control everything manually. An interface, which was designed to be functional, could accidentally be changed by adding another abstract method/methods, rendering it unusable as a functional interface. By using the @FunctionalInterface annotation, the compiler will trigger an error in response to any attempt to break the predefined structure of a functional interface. It is also a very handy tool to make our application architecture easier to understand for other developers. So we can use this:
Instead of just:
4. Don't Overuse Default Methods in Functional InterfacesWe can easily add default methods to the functional interface. This is acceptable to the functional interface contract as long as there is only one abstract method declaration:
Functional interfaces can be extended by other functional interfaces if their abstract methods have the same signature:
Just as with regular interfaces, extending different functional interfaces with the same default method can be problematic. For example, let's add the defaultCommon() method to the Bar and Baz interfaces:
In this case, we'll get a compile-time error:
To fix this, the defaultCommon() method should be overridden in the FooExtended interface. We can provide a custom implementation of this method; however, we can also reuse the implementation from the parent interface:
It's important to note that we have to be careful. Adding too many default methods to the interface is not a very good architectural decision. This should be considered a compromise, only to be used when required for upgrading existing interfaces without breaking backward compatibility. 5. Instantiate Functional Interfaces With Lambda ExpressionsThe compiler will allow us to use an inner class to instantiate a functional interface; however, this can lead to very verbose code. We should prefer to use lambda expressions:
Over an inner class:
The lambda expression approach can be used for any suitable interface from old libraries. It is usable for interfaces like Runnable, Comparator, and so on; however, this doesn't mean that we should review our whole older code base and change everything. 6. Avoid Overloading Methods With Functional Interfaces as ParametersWe should use methods with different names to avoid collisions:
At first glance, this seems reasonable, but any attempt to execute either of the ProcessorImpl‘s methods:
Ends with an error with the following message:
To solve this problem, we have two options. The first option is to use methods with different names:
The second option is to perform casting manually, which is not preferred:
7. Don’t Treat Lambda Expressions as Inner ClassesDespite our previous example, where we essentially substituted inner class by a lambda expression, the two concepts are different in an important way: scope. When we use an inner class, it creates a new scope. We can hide local variables from the enclosing scope by instantiating new local variables with the same names. We can also use the keyword this inside our inner class as a reference to its instance. Lambda expressions, however, work with enclosing scope. We can’t hide variables from the enclosing scope inside the lambda’s body. In this case, the keyword this is a reference to an enclosing instance. For example, in the class UseFoo, we have an instance variable value:
Then in some method of this class, place the following code and execute this method:
If we execute the scopeExperiment() method, we'll get the following result: Results: resultIC = Inner class value, resultLambda = Enclosing scope value As we can see, by calling this.value in IC, we can access a local variable from its instance. In the case of the lambda, this.value call gives us access to the variable value, which is defined in the UseFoo class, but not to the variable value defined inside the lambda's body. 8. Keep Lambda Expressions Short and Self-explanatoryIf possible, we should use one line constructions instead of a large block of code. Remember, lambdas should be an expression, not a narrative. Despite its concise syntax, lambdas should specifically express the functionality they provide. This is mainly stylistic advice, as performance will not change drastically. In general, however, it is much easier to understand and to work with such code. This can be achieved in many ways; let's have a closer look. 8.1. Avoid Blocks of Code in Lambda's BodyIn an ideal situation, lambdas should be written in one line of code. With this approach, the lambda is a self-explanatory construction, which declares what action should be executed with what data (in the case of lambdas with parameters). If we have a large block of code, the lambda's functionality is not immediately clear. With this in mind, do the following:
Instead of:
It is important to note, we shouldn't use this “one-line lambda” rule as dogma. If we have two or three lines in lambda's definition, it may not be valuable to extract that code into another method. 8.2. Avoid Specifying Parameter TypesA compiler, in most cases, is able to resolve the type of lambda parameters with the help of type inference. Consequently, adding a type to the parameters is optional and can be omitted. We can do this:
Instead of this:
8.3. Avoid Parentheses Around a Single ParameterLambda syntax only requires parentheses around more than one parameter, or when there is no parameter at all. That's why it's safe to make our code a little bit shorter, and to exclude parentheses when there is only one parameter. So we can do this:
Instead of this:
8.4. Avoid Return Statement and BracesBraces and return statements are optional in one-line lambda bodies. This means that they can be omitted for clarity and conciseness. We can do this:
Instead of this:
8.5. Use Method ReferencesVery often, even in our previous examples, lambda expressions just call methods which are already implemented elsewhere. In this situation, it is very useful to use another Java 8 feature, method references. The lambda expression would be:
We could substitute it with:
This is not always shorter, but it makes the code more readable. 9. Use “Effectively Final” VariablesAccessing a non-final variable inside lambda expressions will cause a compile-time error, but that doesn’t mean that we should mark every target variable as final. According to the “effectively final” concept, a compiler treats every variable as final as long as it is assigned only once. It's safe to use such variables inside lambdas because the compiler will control their state and trigger a compile-time error immediately after any attempt to change them. For example, the following code will not compile:
The compiler will inform us that:
This approach should simplify the process of making lambda execution thread-safe. 10. Protect Object Variables From MutationOne of the main purposes of lambdas is use in parallel computing, which means that they're really helpful when it comes to thread-safety. The “effectively final” paradigm helps a lot here, but not in every case. Lambdas can't change a value of an object from enclosing scope. But in the case of mutable object variables, a state could be changed inside lambda expressions. Consider the following code:
This code is legal, as total variable remains “effectively final,” but will the object it references have the same state after execution of the lambda? No! Keep this example as a reminder to avoid code that can cause unexpected mutations. 11. ConclusionIn this article, we explored some of the best practices and pitfalls in Java 8's lambda expressions and functional interfaces. Despite the utility and power of these new features, they are just tools. Every developer should pay attention while using them. The complete source code for the example is available in this GitHub project. This is a Maven and Eclipse project, so it can be imported and used as is. What is the act of creating an object called?The correct answer is Instantiation Key Points. Instantiation: The new keyword is a Java operator that creates the object. The new keyword is a Java operator that creates the object. this is also known as instantiating a class. Initialization.
What is required for an interface method that has a body?What is required for an interface method that has a body? The method header must begin with the key word default.
When an relationship exists between objects the specialized object?When an "is a" relationship exists between objects, it means the specialized object has all the characteristics of the general object, plus additional characteristics that make it special.
Which of the following is the operator used to determine whether an object?The java “instanceof” operator is used to test whether the object is an instance of the specified type (class or subclass or interface).
|