Features that make your Java code adhere to modern standards
Java went real quick from 1.8 to 17 and this year version 18 is slated for release. In this article, we will try to cover the features that were released from the 1.8th version till the 17th.
This is going to be a lengthy list, so let’s not spend too much time on setting up the stage.
Please do note that the below list doesn’t cover all the features and changes that were introduced in each release but the ones that would be most helpful in typical programming cases and makes programming more interesting and easy.
Java 8
Lambda:
Lambda expressions let us use functionality as a method argument. Let’s take a look at the code before Java 8 when we had to create an anonymous class to implement a simple interface.
Thread t = new Thread( new Runnable() {
public void run() {
System.out.println("Start thread");
}
});
Using lambda expressions, we can express the instance of a class in a single line
Thread t1 = new Thread(() -> {
System.out.println("Start 1st thread");
});
Functional Interface:
A functional Interface is an Interface that has only one abstract method. Additionally, the Interface can have a default and a static method but only one abstract method.
We have covered these features in length in a previous article, please refer to that article for examples.
Stream
Stream is an interface in the java.util.stream package that provides ways to perform sequential and parallel operations on collections.
stream() method of Collection Interface returns back a stream of elements of the given collection which are of type Stream.
The stream interface supports many operations that are needed to filter or map or find an aggregate result of the elements in a stream.
In the example below, we call stream() method on a List and then pass a Predicate (Functional Interface) to the anyMatch() method that returns a boolean.
List<String> colors = new ArrayList<>(Arrays._asList_("Green","Yellow","Red"));
boolean isTrue = colors.stream().anyMatch(x -> x.equals("Red"));
System.out.println(isTrue);
Optional
Optional creates a container object which may or may not contain a non-null value. Using Optional we can check whether a value is non-null (present) or not, we use isPresent() to validate that.
In the sample code below, we use findAny() method of Stream Interface which returns back an Optional object. Based on the value of the Optional instance we can execute the corresponding logic.
To perform a similar operation like below in the Java versions before 8, we would have to perform an explicit null check on the String.
List<String> colors = new ArrayList<>(Arrays._asList_("Green","Yellow","Red"));
Optional<String> color = colors.stream().filter(x -> x.equals("Black")).findAny();
System.out.println(color.isPresent()? color.get() : "Not Found");
Changes in java.util.concurrent package:
Interface CompletionStage and Class CompletableFuture and CompletionException were introduced in Java 8.
CompletableFuture helps to perform asynchronous tasks that are much more flexible and enhanced than the Future object that existed before Java 8.
Since we have already covered the concurrent package and Future in a previous article, we will not get into analyzing a sample code in the current article.
Method References (::)
Double colons can be used to invoke an existing method instead of using lambda expressions.
Method References can be used to refer to static methods and instance methods. Let’s check an example where we refer to a static method.
First, let’s implement a method invocation using a lambda expression,
public class Main {
public static void main(String[] args) {
List<Integer> locList = **new** ArrayList<>(Arrays._asList_(1,2,5,6));
boolean isFound = locList.stream().anyMatch(l -> _isEqual_(l));
System.out.println(isFound);
}
public static <T> boolean isEqual(T a) {
Predicate<T> isEqual = (x) -> x.equals(5);
return isEqual.test(a);
}
}
isEqual() is a static method in class Main so instead of using a lambda expression to invoke the method, we can simply use the method reference to get the job done.
public class Main {
public static void main(String[] args) {
List<Integer> locList = new ArrayList<>(Arrays._asList_(1,2,5,6));
boolean isFound = locList.stream().anyMatch(Main::_isEqual_);
System.out.println(isFound);
}
public static <T> boolean isEqual(T a) {
Predicate<T> isEqual = (x) -> x.equals(5);
return isEqual.test(a);
}
}
Java 9
Stream.ofNullable
Since Java 8, we could create a stream of elements using Stream.of(Object… values), from Java 9 we can use Stream.ofNullable on a single element to create a stream or return an empty stream.
List<Location> locList = **new** ArrayList<>(Arrays._asList_(loc, loc1, loc2));
Stream._ofNullable_(locList);
Private Methods in Interface
Java 8 introduced the concepts of default methods in Interface. If there is more than one default method that wants to share the same logic then that common logic can be implemented using private methods.
Java Modules
Java module deserves an article of its own but let’s take a quick look at what it is. Java module is a technique by which packages and their related resources can be grouped together. A module contains a module descriptor that would specify the module name, dependent modules, packages that are available for other modules, etc.
One of the end goals is to reduce the size of our application at runtime. If we are not using GUI, we need not add that module as a dependency in our application.
Java 10
local variable type inference
From Java 10 onwards we can initialize non-null local variables and assign them to type var. The type of the variable is decided from the context.
Sample usage is shown below,
public class Sample {
public static void main(String[] args) {
var x = 10;
var y = 11;
System.out.println(x + y);
var obj = new TryVar();
System.out.println(obj.getName());
var c; //Illegal, Compiler will throw an error -> "Cannot use 'var' on variable without initializer"
}
}
class TryVar {
String name;
public String getName() {
return "my name";
}
}
Java 11
new method in Optional
Under the Java 8 features, we learned how to use Optional to replace explicit null check using Optional.isPresent() method.
Since Java 11, we can do a similar check using Optional.isEmpty() method which also returns a boolean based on it is null or non-null;
Java 12
Collectors.teeing()
teeing() returns a Collector that is a composite of two downstream collectors.
Let’s check an example. In this example, we will use a class named Location which has two variables x and y. In the teeing example, we will fetch the number of records that have the accepted value for x and the accepted value for y. (Logic wouldn’t make any sense but let’s just focus on how teeing works 😝 )
//Create three instances of Location
Location loc = new Location(2.2, 3.6);
Location loc1 = new Location(2.4, 3.3);
Location loc2 = new Location(2.3, 3.2);
// Add the reference variables to an ArrayList
List<Location> locList = **new** ArrayList<>(Arrays._asList_(loc, loc1, loc2));
//Count records that has correct x, Count the records that has correct y and create a new Location object
var result = locList.stream().collect(Collectors._teeing_(Collectors._filtering_(Location :: isXFound, Collectors._counting_()),Collectors._filtering_(Location :: isYFound, Collectors._counting_()), Location :: new ));
//Print the Result
System.out.println("Records with correct X coordinate " + result.getX() + " :: Records with correct Y coordinate " + result.getY());
Class Location looks like below,
Java 13
Text Blocks
Text Blocks minimize the Java syntax that is needed to represent a multi-line string.
It could be used in place of any string that is conventionally added within double-quotes.
Before Text Blocks, if we had to print a multi-line string, we would need to use delimiters, concatenations, etc.
For example, the below code would give us the complete string in a single line
System.out.print("Hey There "
+ "What's up?? "
+ "How was your vacation?? "
+ ";)");
output
Hey There What's up?? How was your vacation?? ;)
To print them in the next line we will have to modify the above code to the one given below,
System.out.print("Hey There \n"
+ "What's up?? \n"
+ "How was your vacation?? \n"
+ ";)");
Using Text Blocks we can rewrite the above code to the one given below,
System.out.print("""
Hey There
What's up??
How was your vacation??
;)
""");
Text Blocks can also be used in place of standard strings.
For example, both strings shown below have the same meaning
_//Text Block
printMsg_ ("""
Print This!! """);
// String in Double Quotes
_printMsg_("Print this!");
Java 14
Records
Records is a restricted form of class that is ideal for POJOs. A standard data-carrier-class would have a few private fields along with constructors and getters/setters.
Let’s check an example of a simple data-carrier class that has two members,
public class Location {
double x;
double y;
public Location(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
}
We can rewrite the above class using a Record using the below-given code
record NewLocation( **double** x, **double** y) {}
A Record will acquire the getters and constructors at runtime. It also gets the equals(), hashcode() and toString() methods.
Java 15
Sealed Class / Interface
sealed is a modifier we can add to a Class and Interface. A sealed class can decide the subclasses that can extend it and stop other classes from doing so.
To declare a Class / Interface as sealed, we need to add the “sealed” modifier and then add extends or implements, if applicable, followed by “permits” clause. “permits” is followed by the name of the subclasses that are allowed to extend it.
Other conditions are that, the subclass that’s extending a sealed class can be 1. final — which stops it from further extensions,
- sealed — which lets the sealed subclass define it’s own permitted subclasses,
- non-sealed — let’s any arbtrary class extend it.
For example, if Location is a sealed class, then we can declare it as,
public sealed class Location permits SubLocation
and the Sublocation class could be declared as,
public final class SubLocation extends Location{}
or
public sealed class SubLocation extends Location permits AnotherClass{}
or
public non-sealed class SubLocation extends Location{}
Java 16
Stream.toList()
From Java 16, we can collect the result from stream directly to a list instead of using Collectors.toList().
List<Integer> collectedList = locList.stream().filter( l -> l >=2).collect(Collectors._toList_());
The above code will yield the same result as the one given below
List<Integer> collectedListNew = locList.stream().filter( l -> l >=2).toList();
Java 17
Pattern matching for switch
Instead of just using constant values in switch cases we can now use patterns as case labels.
Top comments (0)