Java 8 Specific Best Practices to secure Application
Use of Optional Keyword
5.1 What Optional is?
‘Optional’ is a keyword introduced in Java8 which has the potential of removing the in-famous NullPointerException. Essentially, it is a wrapper class which contains an optional value and hence it can either contain an object or it can be empty.
Lets start using a simple use case where in we see that before Java 8, any number of operations involving accessing an object’s methods or properties could lead to a NullPointerException. A classic example of this is –
String ISBNNumber = book.getPublisher().getISBN().toUpperCase();
If we want to ensure that we don’t get the NullPointerException then we need to write our code as under –
if (book != null) {
Publisher publisher = book.getPublisher();
If (publisher != null) {
String isBNNumber = publisher.getISBN();
If (isBNNumber != null) {
String ISBNNumber = isBNNumber.toUpperCase();
}
}
}
As seen above, if we want to ensure that there is no NullPointerException we need to follow this cumbersome coding practice. To avoid such scenario, in Java 8 we have the Optional class which should be used only for return types as under –
private Optional<Publisher> getPublisher() {
... ...
... ...
... ...
};
Returning Optional adds explicitly the possibility that there might not be a publisher for a given book. Hence it is advised only to use the Optional keyword for Return types. The caller needs to develop his/her code as under –
Optional< Publisher > optionalPub = book.getPublisher ();
if (optionalPub.isPresent()) {
String isBNNumber = maybeisBNNumber.get();
... use isBNNumber...
}
else {
... deal as if the isBN number is not present ...
}
What Optional is trying to resolve?
• Attempt is to reduce the number of NullPointerException.
What Optional is not trying to resolve?
• It shouldn’t and can’t be used to avoid all NullPointerException wherever they exist. Mandatory input parameters for methods and constructors still need to be checked for null.
• Optional doesn’t help to convey the meaning absent value. The caller needs to go thru the documentation of the methods in-order to deal with an absent value.
5.2 Lambda Expression
What is Lambda Expression?
Lambda expression is one of the most important features introduced in Java 8. Lambda expressions enables functional programming in java and makes the developer’s life simple. With the help of Lambda expression, we can refer to a final variable. Lambda expression throws a compilation error, if a variable is assigned a value the second time.
Features –
lambda expressions provide the following features -
• Enable to treat functionality as a method argument, or code as data.
• A function that can be created without belonging to any class.
• A lambda expression can be passed around as if it was an object and executed on demand
Syntax –
In Java lambda is expressed by the ‘->’ symbol. Some commonly used syntax is –
lambda operator -> body
Where the lambda operator can be any one of these –
• No param –
() -> System.out.println("No param lambda expression");
One param –
(param1) -> System.out.println("One param lambda expression - " +param1);
Two param –
(param1, param2) -> System.out.println("One param lambda expression – [" +param1 +”]:[”+ param2+”]”);
Sample code –
Simple Lambda usage -
import java.util.ArrayList;
class DemoLambda {
public static void main(String args[]) {
ArrayList<String> arrStrList = new ArrayList<String>();
arrStrList.add(“Tom”);
arrStrList.add(“Dick”);
arrStrList.add(“Harry”);
arrStrList.add(“Peter”);
arrStrList.forEach(name -> System.out.println(name));
}
}
Some Best practices using Lambda –
Prefer usage of standard functional interface
Functional Interface which are part of java.util.function package are ready to satisfy the needs of the developers by providing the required target types for lambda expressions. Each of these interfaces are generic and abstract in nature, thus it’s easy to adapt to almost any lambda expression. Developers are advised to explore this package before creating new functional interfaces.
Consider an interface InterFoo:
@FunctionalInterface
public interface InterFoo {
String methodFooBar(String string);
}
and a method addBar() in some class UseInterFoo, accepting this as an input parameter –
public String addBar(String string, InterFoo foo) {
return foo. methodFooBar (string);
}
In order to execute this, one should write –
InterFoo impFoo = param -> param + " from lambda expression";
String result = useInterFoo.add("Message ", impFoo);
Take a close look at this and we can see that InterFoo is nothing but a function that accepts one argument and produces a result. Java 8 provides such an interface in Function<T, R> from the java.util.function package. We can now we can remove interface InterFoo completely and change our code to:
public String addBar(String str, Function<String, String> funct) {
return funct.apply(str);
}
In order to execute this, we can write –
Function<String, String> functn = parameter -> parameter + " from lambda expression";
String result = useFoo.add("Message - ", functn);
5.3 Use the Annotation - @FunctionalInterface
Developers are advised to annotate the functional interfaces using annotation - @FunctionalInterface.
This might seem to be useless as because without this, the interface is still treated as functional interface, if it has just one abstract method. Having said this, lets think of a large project with several interfaces – it would be difficult to control these things manually. E.g. an interface, which is initially designed to be functional, could accidentally be changed by adding another abstract method and hence would be it unusable as a functional interface. If we use the @FunctionalInterface annotation, the compiler will trigger an error if an attempt is made to break the predefined structure of a functional interface. Its always advised to use –
@FunctionalInterface
public interface InterFoo {
String method();
}
instead of just:
public interface InterFoo {
String method();
}
Use of defaults
Developers are advised not to add too many default methods in the interface as it is not a good architecture. It’s more of a compromise and should only to be used when required, for upgrading existing interfaces without breaking backward compatibility.
Create instances of Functional interfaces using Lambda Expression
Java 8 compiler allows us to use an inner class to instantiate a functional interface. However, this can lead to very verbose code. In such cases it is advised to prefer lambda expressions:
InterFoo interFoo = param -> param + " from interFoo ";
over an inner class:
FoK foKByIC = new FoK() {
@Override
public String method(String string) {
return string + " from FoK";
}
};
This approach can be used for any interface from old libraries e.g. interfaces like Runnable, Comparator are good cases. Having said that it is never advised to any old codebase and start changing everything.
Avoid method overloading while using Functional interfaces as parameters
It is always advised to have methods with different names to avoid collisions. Let’s have a look at an example:
public interface DummyAdder {
String join(Function<String, String> f);
void join(Consumer<Integer> f);
}
public class DummyAdderImpl implements DummyAdder {
@Override
public String join (Function<String, String> f) {
return f.apply("Something related to customer");
}
@Override
public void join (Consumer<Integer> f) {}
}
Nothing seems to be wrong here but while we try to execute any of DummyAdderImpl methods -
String r = dummyAdderImpl.add(a -> a + " from lambda");
we see an error with the following message:
reference to add is ambiguous both method
Now we have two solutions to this problem –
1. First option is to have the methods with separate names as under:
String joinWithFunction(Function<String, String> f);
void joinWithConsumer(Consumer<Integer> f);
2. Second option is to do manual casting. This is not preferred option.
String r = DummyAdder.join ((Function) a -> a + " from lambda");
5.4 Java Streams Best Practices:
You should have at most one stream method call per line. This will make stream operations like map, filter and collect easily recognizable.
// BAD CODE:
strings.stream().filter(s -> s.length() > 2).sorted()
.map(s -> s.substring(0, 2)).collect(Collectors.toList());
// GOOD CODE:
strings.stream()
.filter(s -> s.length() > 2)
.sorted()
.map(s -> s.substring(0, 2))
.collect(Collectors.toList());
You should import static all of the standard stream related methods. This will make code shorter, easier to read and easier to understand by removing all unnecessary visual noise.
// BAD CODE:
strings.stream()
.sorted(Comparator.reverseOrder())
.limit(10)
.collect(Collectors.toMap(Function.identity(), String::length));
// GOOD CODE:
strings.stream()
.sorted(reverseOrder())
.limit(10)
.collect(toMap(identity(), String::length));
You should prefer method references to lambdas.
// AVOID:
strings.stream()
.map(s -> s.length())
.collect(toList());
// PREFER:
strings.stream()
.map(String::length)
.collect(toList());
Method references are easier to read since we avoid all the visual noise generated by -> and () operators. They are also handled more efficiently by current version of Java. Lambda expressions like s -> s.length() are compiled to a private static method and an invoke dynamic instruction.
// s -> s.lenght() is translated into:
private static Integer lambda$main$0(String s) {
return s.length();
}
Method references are compiled to only invokedynamic instruction.
You should use methods from Class<T> to filter stream elements by a type and to cast stream elements to a type.
Stream<Object> objects = Stream.of(
"a string",
42,
new String[] { "an array" },
"another string");
List<String> strings = objects
.filter(String.class::isInstance)
.map(String.class::cast)
.collect(toList());
Also rember that Class<T>::isInstance only checks if the value can be assigned to a variable of type T. For example Object.class.isInstance("foo") returns true because string "foo" can be assigned to a variable of type Object. If you want to check that stream elements have exactly type T you must use expression:
.filter(x -> (x != null) && x.getClass().equals(T.class))
Give meaningful names to frequently used collector expressions. In most cases this means extracting collector expression into its own method.
// USED FROM TIME TO TIME:
Map<Integer, Entity> entityById = entities.stream()
.collect(toMap(Entity::getId, identity()));
// USED FREQUENTLY:
Map<Integer, Entity> entityById = entities.stream()
.collect(ExtraCollectors.toByIdMap());
private static class ExtraCollectors {
public static Collector<Entity,?,Map<Integer,Entity>> toByIdMap() {
return Collectors.toMap(Entity::getId, identity());
}
}
You may also consider using static import for your own frequently used collectors.
Use the following pattern when you sort stream values at hoc:
List<Student> result = students.stream()
.sorted(
comparing(Student::getSurname)
.thenComparing(Student::getName, reverseOrder())
.thenComparing(Student::getAge)
.thenComparing(Student::getId, reverseOrder())
)
.collect(toList());
Notice how we used reverseOrder() to reverse order of sorting by name and id. Also bear in mind that it is always a good idea to extract complicated comparers to its own method or a final field.
Use IntStream, LongStream and DoubleStream when working with primitive types. They are faster (they avoid boxing) and easier to use (they add useful methods like sum).
Stream<String> strings = Stream.of("a", "foo", "bar", "baz");
double averageLength = strings
.mapToInt(String::length)
.summaryStatistics()
.getAverage();
Use mapTo[Int|Long|Double] and mapToObj to convert between a stream and a specialized primitive stream.
Also learn about static helper methods exposed by specialized stream classes:
// prints: 0 1 2 3 4 5 6 7 8 9
IntStream.range(0, 10)
.forEach(System.out::println);
// prints: 1 2 4 8 16 32 64 128 256 512
IntStream.iterate(1, i -> 2*i)
.limit(10)
.forEach(System.out::println);
ThreadLocalRandom random = ThreadLocalRandom.current();
// prints: -376368599 2112239618
// just to demo generate method:
IntStream.generate(random::nextInt)
.limit(2)
.forEach(System.out::println);
// prints: -1134353240 2007034835
// stream of random int's - more idiomatic way:
random.ints()
.limit(2)
.forEach(System.out::println);
Avoid using peek(). Try to make your streams free of side-effects.
5.5 Java Lambda Expressions and Functional Interface Best Practices
1. Prefer Standard Functional Interfaces
Functional 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.
Consider an interface Foo:
@FunctionalInterface
public interface Foo {
String method(String string);
}
and a method add() in some class UseFoo, which takes this interface as a parameter:
public String add(String string, Foo foo) {
return foo.method(string);
}
To execute it, you would write:
Foo foo = parameter -> parameter + " from lambda";
String result = useFoo.add("Message ", foo);
Look closer and you will 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:
public String add(String string, Function<String, String> fn) {
return fn.apply(string);
}
To execute this, we can write:
Function<String, String> fn =
parameter -> parameter + " from lambda";
String result = useFoo.add("Message ", fn);
2. Use the @FunctionalInterface Annotation
Annotate your functional interfaces with @FunctionalInterface. At first, this annotation seems to be useless. Even without it, your interface will be treated as functional as long as it has just one abstract method.
But 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 of other abstract method/methods, rendering it unusable as a functional interface.
But 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 your application architecture easier to understand for other developers.
So, use this:
@FunctionalInterface
public interface Foo {
String method();
}
instead of just:
public interface Foo {
String method();
}
3. Don’t Overuse Default Methods in Functional Interfaces
You 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:
@FunctionalInterface
public interface Foo {
String method();
default void defaultMethod() {}
}
Functional interfaces can be extended by other functional interfaces if their abstract methods have the same signature. For example:
@FunctionalInterface
public interface FooExtended extends Baz, Bar {}
@FunctionalInterface
public interface Baz {
String method();
default void defaultBaz() {}
}
@FunctionalInterface
public interface Bar {
String method();
default void defaultBar() {}
}
Just as with regular interfaces, extending different functional interfaces with the same default method can be problematic. For example, assume that interfaces Bar and Baz both have a default method defaultCommon(). In this case, you will get a compile-time error:
interface Foo inherits unrelated defaults for defaultCommon() from types Baz and Bar...
To fix this, defaultCommon() method should be overridden in the Foo interface. You can, of course, provide a custom implementation of this method. But if you want to use one of the parent interfaces’ implementations (for example, from the Baz interface), add following line of code to the defaultCommon() method’s body:
Baz.super.defaultCommon();
But be careful. Adding too many default methods to the interface is not a very good architectural decision. It is should be viewed as a compromise, only to be used when required, for upgrading existing interfaces without breaking backward compatibility.
4. Instantiate Functional Interfaces with Lambda Expressions
The compiler will allow you to use an inner class to instantiate a functional interface. However, this can lead to very verbose code. You should prefer lambda expressions:
Foo foo = parameter -> parameter + " from Foo";
over an inner class:
Foo fooByIC = new Foo() {
@Override
public String method(String string) {
return string + " from Foo";
}
};
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 you should review your whole older codebase and change everything.
5. Avoid Overloading Methods with Functional Interfaces as Parameters
Use methods with different names to avoid collisions; let’s look at an example:
public interface Adder {
String add(Function<String, String> f);
void add(Consumer<Integer> f);
}
public class AdderImpl implements Adder {
@Override
public String add(Function<String, String> f) {
return f.apply("Something ");
}
@Override
public void add(Consumer<Integer> f) {}
}
At first glance, this seems reasonable. But any attempt to execute any of AdderImpl’s methods:
1
String r = adderImpl.add(a -> a + " from lambda");
ends with an error with the following message:
reference to add is ambiguous both method
add(java.util.function.Function<java.lang.String,java.lang.String>)
in fiandlambdas.AdderImpl and method
add(java.util.function.Consumer<java.lang.Integer>)
in fiandlambdas.AdderImpl match
To solve this problem, you have two options. The first is to use methods with different names:
String addWithFunction(Function<String, String> f);
void addWithConsumer(Consumer<Integer> f);
The second is to perform casting manually. This is not preferred.
String r = Adder.add((Function) a -> a + " from lambda");
6. Don’t Treat Lambda Expressions as Inner Classes
Despite our previous example, where we essentially substituted inner class by a lambda expression, the two concepts are different in an important way: scope.
When you use an inner class, it creates a new scope. You can overwrite local variables from the enclosing scope by instantiating new local variables with the same names. You can also use the keyword this inside your inner class as a reference to its instance.
However, lambda expressions work with enclosing scope. You can’t overwrite 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 you have an instance variable value:
1
private String value = "Enclosing scope value";
Then in some method of this class place the following code and execute this method.
public String scopeExperiment() {
Foo fooIC = new Foo() {
String value = "Inner class value";
@Override
public String method(String string) {
return this.value;
}
};
String resultIC = fooIC.method("");
Foo fooLambda = parameter -> {
String value = "Lambda value";
return this.value;
};
String resultLambda = fooLambda.method("");
return "Results: resultIC = " + resultIC +
", resultLambda = " + resultLambda;
}
If you execute the scopeExperiment() method, you will get the following result: Results: resultIC = Inner class value, resultLambda = Enclosing scope value
As you can see, by calling this.value in IC, you can access a local variable from its instance. But in the case of the lambda, this.value call gives you access to the variable value which is defined in the UseFoo class, but not to the variable value defined inside the lambda’s body.
7. Keep Lambda Expressions Short And Self-explanatory
If possible, 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 precisely 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.
7.1. Avoid Blocks of Code in Lambda’s Body
In 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 you have a large block of code, the lambda’s functionality is not immediately clear.
With this in mind, do the following:
Foo foo = parameter -> buildString(parameter);
private String buildString(String parameter) {
String result = "Something " + parameter;
//many lines of code
return result;
}
instead of:
Foo foo = parameter -> { String result = "Something " + parameter;
//many lines of code
return result;
};
However, please don’t use this “one-line lambda” rule as dogma. If you have two or three lines in lambda’s definition, it may not be valuable to extract that code into another method.
7.2. Avoid Specifying Parameter Types
A compiler in most cases is able to resolve the type of lambda parameters with the help of type inference. Therefore, adding a type to the parameters is optional and can be omitted.
Do this:
(a, b) -> a.toLowerCase() + b.toLowerCase();
instead of this:
(String a, String b) -> a.toLowerCase() + b.toLowerCase();
7.3. Avoid Parentheses Around a Single Parameter
Lambda syntax requires parentheses only around more than one parameter or when there is no parameter at all. That is why it is safe to make your code a little bit shorter and to exclude parentheses when there is only one parameter.
So, do this:
a -> a.toLowerCase();
instead of this:
(a) -> a.toLowerCase();
7.4. Avoid Return Statement and Braces
Braces and return statements are optional in one-line lambda bodies. This means, that they can be omitted for clarity and conciseness.
Do this:
a -> a.toLowerCase();
instead of this:
a -> {return a.toLowerCase()};
7.5. Use Method References
Very 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.
So, the lambda expression:
a -> a.toLowerCase();
could be substituted by:
String::toLowerCase;
This is not always shorter, but it makes the code more readable.
8. Use “Effectively Final” Variables
Accessing a non-final variable inside lambda expressions will cause the compile-time error. But it doesn’t mean that you 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 is 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:
public void method() {
String localVariable = "Local";
Foo foo = parameter -> {
String localVariable = parameter;
return localVariable;
};
}
The compiler will inform you that:
1
Variable 'localVariable' is already defined in the scope.
This approach should simplify the process of making lambda execution thread-safe.
10. Protect Object Variables from Mutation
One 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:
int[] total = new int[1];
Runnable r = () -> total[0]++;
r.run();
This code is legal, as total variable remains “effectively final”. But will the object it references to have the same state after execution of the lambda? No!
Keep this example as a reminder to avoid code that can cause unexpected mutations.
5.6 Optional – use and best practices
Before Java 8 (Style 1)
Employee employee = employeeServive.getEmployee();
if(employee!=null){
System.out.println(employee.getId());
}
After Java 8 (Style 2)
Optional<Employee> employeeOptional = Optional.ofNullable(employeeService.getEmployee());
if(employeeOptional.isPresent()){
Employee employee = employeeOptional.get();
System.out.println(employee.getId());
}
Method result
Before Java 8, receiving a null value from a method was ambiguous. It could mean there’s nothing to return or that an error occurred during execution. Besides, developers tended to forget verifying if the result was null, which led to nasty NullPointerException at runtime. Optional solves both problems by providing a convenient way to force the user of the method to check its self-explanatory output.
Collection wrapper
While putting a nullable method result inside Optional is advisable, the rule doesn’t apply when the output is a collection or an array. In case when there’s no element to return, an empty instance is superior to empty Optional and null as it conveys all necessary information. Optional doesn’t provide any additional value and only complicates client code, hence it should be avoided. Here’s a bad practice sample:
Optional<List<Item>> itemsOptional = getItems();
if (itemsOptional.isPresent()) { // do we really need this?
itemsOptional.get().forEach(item -> {
// process item
});
} else {
// the result is empty
}
As long as the getItems() method returned the unwrapped list instance, client code could get rid of one condition check and simply iterate over the collection. If we want to verify the absence of the result, each collection has the isEmpty() method and in case of arrays, the length property can be used. On the whole, Optional adds unnecessary complexity.
Constructor and method parameters
Although it might be tempting to consider Optional for not mandatory method parameters, such a solution pale in comparison with other possible alternatives. To illustrate the problem, examine the following constructor declaration:
public SystemMessage(String title, String content, Optional<Attachment> attachment) {
// assigning field values
}
At first glance it may look as a right design decision. After all, we explicitly marked the attachment parameter as optional. However, as for calling the constructor, client code can become a little bit clumsy.
SystemMessage withoutAttachment = new SystemMessage("title", "content", Optional.empty());
Attachment attachment = new Attachment();
SystemMessage withAttachment = new SystemMessage("title", "content", Optional.ofNullable(attachment));
Instead of providing clarity, the factory methods of the Optional class only distract the reader. Note there’s only one optional parameter, but imagine having two or three.
When a method can accept optional parameters, it’s preferable to adopt the well-proven approach and design such case using method overloading. In the example of the SystemMessage class, declaring two separate constructors are superior to using Optional.
public SystemMessage(String title, String content) {
this(title, content, null);
}
public SystemMessage(String title, String content, Attachment attachment) {
// assigning field values
}
That change makes client code much simpler and easier to read.
SystemMessage withoutAttachment = new SystemMessage("title", "content");
Attachment attachment = new Attachment();
SystemMessage withAttachment = new SystemMessage("title", "content", attachment);
Consider the following example:
public class OrderProcessor {
private Optional<SmsNotifier> smsNotifier = Optional.empty();
public void process(final Order order) {
// some processing logic here
smsNotifier.ifPresent(n -> n.sendConfirmation(order));
}
public void setSmsNotifier(SmsNotifier smsNotifier) {
this.smsNotifier = Optional.ofNullable(smsNotifier);
}
}
If a nullable field were used, we’d risk someone, while extending the class, would unknowingly use the field without verifying its presence. In the world dominated by dependency injection frameworks, a multitude of developers automatically assume if there’s a field in a business logic class then it must be set by a container. The Optional type is highly expressive in this situation and prevents automatic behavior from occurring.
As usual in programming, there’s no one best way to tackle the problem. Another possible solution for optional dependencies is the Null Object pattern, which in some cases may be preferable.
Not a silver bullet
Although in many situations the temptation to use Optional may be strong, the original idea behind the type wasn’t to create an all-purpose replacement for every nullable value. Before applying the type, all possible alternatives should be considered as overusing of Optional may lead to introducing new burdensome code smells.
Comments
Post a Comment