This article will take you to the next level in java. Are you ready!
This article is basically a set of examples of multiple tools provided by Java 8. Those tools have helped to make programming way more easier and optimal(even in terms of hardware resources), using a declarative approach. Hence, we don't have to tell many details, but only what we want to achieve by our code. I should highlight also the fact that this pack of tools is something that a java developer "must" know.
-
Stream:
In this section we will dive into the world of streams. We will understand them, how they work, and why we need them in the first place.
-
Lazy expressions:
By learning more about lazy expressions and precisely lambdas and references in this section you will be able to make your code so lazy. Also will make the use of your hardware optimal.
-
Functional interfaces:
This is actually the main section in which you will be able to see real example of the implementation of code and you will understand how to use all what you have learnt. Still more concrete examples will be in the last section.
-
Optional:
This brief section will take you back to bad memory of your first steps learning java, and exactly to the scary
NullPointerException
. But, this time you will be able to take control over every thing with no worries of any unpredictable null value problems.
Finally, you will work on real examples and learn some specific tricks that will alter your programming style to the best!
To understand exactly why I choose to follow this planing You need to understand the relation between those parts. Because, this selection of topics doesn't recover every thing about java and functional programming. Still, it will be your main support to learn more.
Java 8 Stream is a sequence of elements from certain source, on which we can perform basic aggregate operations. This source can be a collection, array or a stream - this is called pip-lining. In contrast to collections, streams aren't data structure. They allow to extract the required value only when we need it. Also they support lazy evaluation and were initially created to be used with lambdas(refer to the second part). They are contained under the package java.util.stream. There is a very large set of operations to perform on a stream. The majority of them return a stream so that we can perform more operations. Moreover streams don't store data nor changes. They just perform iteration internally once and then return a result. So we say that they are consumable and functional in nature. One more powerful point for stream is that they are possibly unbounded (unlimited, so we don't have to precis a certain size for a stream). Thus, you can perform operations on the first and last elements of a stream with no worries for its size or the order of elements because they also keep the same order of elements as the source.
This article will just give some important and highly used examples of functions and operations. If you were looking for more operations that java offers, you can look for them here.
In a nutshell:
Collection | Stream |
---|---|
under the java.util package | under the java.util.stream package |
in memory data structure | only for processing data |
all values must be computed then stored (eager) | allow operations on only the required values (lazy) |
we can perform operations as much as we want on collection | the elements of a stream are only visited once during the life of a stream (if you want to visit them again use an other stream!) |
must have certain size | doesn't need a fixed size. Yet, we can get the first/last elements |
In the next example we gonna use the following import:
import java.util.List;
If you take this list of friends:
List<String> friends = List.of("Mohamed","Abderrahmane","Ajar","Omar","Youssef");
Imagine we want to print the length of each name. Normally, your code would be something like this:
int[] r = new int[5];
for (int i = 0;i<5;i++) {
r[i] = friends.get(i).length();
System.out.println(r[i]);
}
I think this code is clear for you. But other method to do the same thing with no need to declare any variable is the following:
friends
.stream()//create stream of friends
.forEach(n->System.out.println( n.length()));//Then print successively each one of them
Please refer to the next section to understand what are these expressions: t -> expression
.
Lazy expressions or lazy evaluation is a strategy of coding and also a mindset. You will find a lot of complex explanations to what exactly a lazy evaluation is. Yet, it is all about how to make your code lazier, and by this I mean optimal. This could be done by postpone the execution of a function until we really need it be executed. Java is lazy when it comes to the evaluation of logical arguments,e.g. in f1() || f2()
, the call of f2()
is never performed if f1()
returns a boolean true. Still, a method is executed as soon as it is called. On the other hand, if a lazy method wasn't called, then it will just lay around whithout doing anything. However, we are often tempted towards writing a code which is executed eagerly, because it is easy to write and to understand the logic behind it. But sometimes it is just a waste of time and hardware resources if a method doesn't use all the passed arguments. In this section we gonna focus exclusively on lambdas, method reference, and how they make your code lazier.
- Method reference
Method reference refers to a method from class or object using class::methodName
type syntax. There is different types of available method references in java 8. For example:
Explanation | Form | Example |
---|---|---|
Reference to static method | Class::staticMethodName | Math::max equivalent to Math.max(x,y) |
Reference to instance method from instance | ClassInstance::instanceMethodName | System.out::println equivalent to System.out.println(x) |
Reference to instance method from class type | Class::instanceMethodName | String::length equivalent to str.length() |
Reference to constructor | Class::new | ArrayList::new equivalent to new ArrayList() |
- Lambda expression
Lambda expression are also called anonymous function, i.e., a function with no name and any identifier.
They are nameless functions given as constant values, and written exactly in the place where it’s needed, typically as a parameter to some other function. They are writing in this form:(parameters) -> expression
; which means that expression
is preformed when it is called, on the value that takes the variables parameters
.( For example, this expression (x, y) -> x + y
returns the result of the addition of x
and y
, also this expression (x) -> System.out.println(x)
prints the value given to the argument x
;
In the example above .forEach(n->System.out.println( n.length()));
means that we gonna perform the action between brackets on each element of our stream, which is to print them one by one.
Moreover, the code above also can be written as following with some small changes:
friends
.stream()
.map(String::length)//Map each name to its length. As result we get a stream of lengths.
.forEach(System.out::println);//Print each element of the stream.
It's time to dive more in one of the main innovations that java 8 has come with.
- Overall, Functional interfaces are interfaces that permit exactly one abstract method inside them, in addition to as many as you like of default methods. Since they are not abstract because they have an implementation (I should also highlight the fact that default functions are a revolutionary addition in java 8). Functional interfaces are also named Single Abstract Method interfaces (SAM Interfaces). In Java 8, functional interfaces can be represented using lambda expressions, method reference and constructor references as well. It is optional to add
@FunctionalInterface
annotation to a functional interface to define one. For example we can have something like this:
@FunctionalInterface
public interface MyInterface
{
public void job();//one abstract method
default void fct1(){//default method
//Method body
}
default void fct2(){//default method
//Method body
}
static void fct(){//default static method
//Method body
}
}
- Default methods are a related topic. Since, they are methods that can have an implementation into an interface! This implementation by default can be either kept as it is, or overridden. We can make a default method by adding the keyword
default
or, and this is so cool, by making itstatic
with no need to add thedefault
keyword. I won't dive into more details that you can look for later(believe me this new feature is awesome so please check the references), but a good question to ask is what also they provide so that I putted them into this section? Well, because default methods enable the lambdas functionality in java. Take a look to this example fromjava.lang.Iterable
:
//This method allow you to perform action on collection
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
/*
Before java 8 you have to iterate on your collection using the loop for. But now you can do something like this (I reused the first example here):
*/
friends
.stream()
.forEach(System.out::println);//to print all the friends names.
//Or;
friends
.stream()
.forEach(f -> System.out.println(f));
- Also,
java.util
has a set of pre-implimented functional interfaces like the following examples:
Functional interface | Definition |
---|---|
Function < TypeOfInput,TypeOfReturn> | Represents a function that accepts one argument and produces a result. |
Consumer < TypeOfInput> | Represents an operation that accepts a single input argument and returns no result. |
Supplier< TypeOfInput> | Represents a supplier of results. |
Predicate< TypeOfInput> | Represents a predicate (boolean-valued function) of one argument. |
I will just give an exhaustive example of implementation of a function
. Everything else goes the same way. Yet, if you want the full list you can check the provided documentation of Oracle.
/*
This example represente a method that increment its int argument by one.
*/
//It would be something like this:
int increment(int i){
return i+1;
}
/*
With functional interface "Function" it becomes:
Don't forget to import:
import java.util.function.Function;
*/
Function<Integer, Integer> increment = i -> ++i;
If you want more easy examples for this topic you can check my github repository.
When I was a 'newbie' to java I hated it because of one scary exception that keeps popping up on my screen: The java famous NullPointerException
. To be honest it was because some stupid, missed yet required initial values, so java is not the one to blame here. Later, I learnt about the class Optional
of the package java.util
. Optional, is in fact a value that can be null or not null. You have a huge set of tools and methods to manipulate it. If it was present, if you want to throw a specific exception, if you want to get the value, or else you have methods to do so in this class.
So let us go directly to see some examples:
/*In all the example you can change the value 'null' and the SomeUserCostumizedOrAnyException by something else.
*/
/*
If you want to print a value when only it is present.
*/
System.out.println(Optional
.ofNullable(null)
.orElseGet(() -> "value to show by default"));
/*
If you want to print a value when only it is present, or just throw an exception.
*/
// method reference
System.out.println(Optional.ofNullable(null).orElseThrow(SomeUserCostumizedOrAnyException::new));
//Lambda
System.out.println(Optional.ofNullable(null)
.orElseThrow(()-> new SomeUserCostumizedOrAnyException("the default value is missed")));
/*
If you want to check of the value is present and then print the String email(you have first change null by something like this "xyz@example.com")
*/
Optional.ofNullable(null).ifPresentOrElse(v->
System.out.println("Sending email to "+v+"."),
() -> System.out.println("Cannot send emails!")
);
/*
A last comparison, in case of you want to just get the value.
*/
//normal method(imperative approach in which you declare all your variables and explicitly the logic of how to get the wanted result)
String s="present value";
if(s==null){
System.out.println("s is null");
} else {
System.out.println(s);
}
//new method with Optional (declarative approach in which you just describe what you want as result exactly)
System.out.println(Optional.ofNullable("present value").orElse("s is null"));
//Or just simply
System.out.println(Optional.of("present value").get());
The main goal of this article was to teach you about declarative approach of programming in java. I hope I achieved this goal! But before to wrap up, I just want to remind you of the fact that this should serve as an opening and the given article and examples shouldn't be the last thing you see. So, please keep practicing to learn.
In this article you learnt:
Section | Importance |
---|---|
Streams | Streams make your code lazy by only using the required values in a data source. To do so, you have a powerful set of aggregate methods to use |
Lazy expressions | Lambdas and method reference change your coding style to be more declarative and also lazier |
Functional Interfaces | This part serves as the theory behind functional programming in java and some good examples to understand it. |
Optional | This class works lot with streams and functional interfaces, in case of null values. It will allow you to avoid the struggle of facing the NullPointerException every time you execute your code. |
Thank you for reading.
Oracle Documentation: