2352. Java 8 - New FeaturesLambda, Stream, and Optional
New features in java 8.
1. Overview
JAVA 8 is a major feature release of JAVA programming language development. Its initial version was released on 18 March 2014. With the Java 8 release, Java provided supports for functional programming, new JavaScript engine, new APIs for date time manipulation, new streaming API, etc.
New Features:
Lambda expression
− Adds functional processing capability to Java.Method references
− Referencing functions by their names instead of invoking them directly. Using functions as parameter.Default method
− Interface to have default method implementation.New tools
− New compiler tools and utilities are added like ‘jdeps’ to figure out dependencies.Stream API
− New stream API to facilitate pipeline processing.Date Time API
− Improved date time API.Optional
− Emphasis on best practices to handle null values properly.Nashorn JavaScript Engine
− A Java-based engine to execute JavaScript code.
2. Lambda Expressions
Lambda expressions are introduced in Java 8 and are touted to be the biggest feature of Java 8. Lambda expression facilitates functional programming, and simplifies the development a lot.
2.1 Syntax
A lambda expression is characterized by the following syntax.
parameter -> expression body
Optional type declaration
− No need to declare the type of a parameter. The compiler can inference the same from the value of the parameter.Optional parenthesis around parameter
− No need to declare a single parameter in parenthesis. For multiple parameters, parentheses are required.Optional curly braces
− No need to use curly braces in expression body if the body contains a single statement.Optional return keyword
− The compiler automatically returns the value if the body has a single expression to return the value. Curly braces are required to indicate that expression returns a value.
2.2 Example
// interface with a single method only
public interface MathOperation {
int operate(int a, int b);
}
// interface with a single method only
public interface GreetingService {
void sayMessage(String message);
}
public class Calculator {
public int operate(int a, int b, MathOperation mathOperation) {
return mathOperation.operate(a, b);
}
}
public static void main(String args[]) {
// with type declaration
MathOperation addition = (int a, int b) -> a + b;
// with out type declaration
MathOperation subtraction = (a, b) -> a - b;
// with return statement along with curly braces
MathOperation multiplication = (int a, int b) -> { return a * b; };
// without return statement and without curly braces
MathOperation division = (int a, int b) -> a / b;
Calculator calculator = new Calculator();
System.out.println("10 + 5 = " + calculator.operate(10, 5, addition));
System.out.println("10 - 5 = " + calculator.operate(10, 5, subtraction));
System.out.println("10 x 5 = " + calculator.operate(10, 5, multiplication));
System.out.println("10 / 5 = " + calculator.operate(10, 5, division));
// without parenthesis
GreetingService greetService1 = message ->
System.out.println("Hello " + message);
// with parenthesis
GreetingService greetService2 = (message) ->
System.out.println("Hello " + message);
greetService1.sayMessage("Java8");
greetService2.sayMessage("Lambda");
}
Output.
10 + 5 = 15
10 - 5 = 5
10 x 5 = 50
10 / 5 = 2
Hello Java8
Hello Lambda
3. Method References
Method references help to point to methods by their names. A method reference is described using ::
symbol. A method reference can be used to point the following types of methods:
- Static methods
- Instance methods
- Constructors using new operator (TreeSet::new)
Example
public class MethodReferencesExample {
public static void main(String args[]) {
List<Integer> nums = Arrays.asList(1,2,3,4,5,6);
nums.forEach(System.out::println);
}
}
Output.
1
2
3
4
5
6
4. Functional Interfaces
Functional interfaces have a single
functionality to exhibit. For example, a Comparable interface with a single method ‘compareTo’ is used for comparison purpose. Java 8 has defined a lot of functional interfaces to be used extensively in lambda expressions. Following is the list of functional interfaces defined in java.util.Function
package.
Predicate
Example
public class FunctionalInterfaceExample {
public static void main(String args[]) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
// Predicate<Integer> predicate = n -> true
// n is passed as parameter to test method of Predicate interface
// test method will always return true no matter what value n has.
System.out.print("Print all numbers: ");
//pass n as parameter
evaluate(list, n->true);
// Predicate<Integer> predicate1 = n -> n%2 == 0
// n is passed as parameter to test method of Predicate interface
// test method will return true if n%2 comes to be zero
System.out.print("Print even numbers: ");
//evaluate(list, n-> n%2 == 0 );
evaluate(list, new evenPredicate());
// Predicate<Integer> predicate2 = n -> n > 3
// n is passed as parameter to test method of Predicate interface
// test method will return true if n is greater than 3.
System.out.print("Print numbers greater than 3: ");
evaluate(list, n-> n > 3 );
}
public static void evaluate(List<Integer> list, Predicate<Integer> predicate) {
for(Integer num: list) {
if (predicate.test(num)) {
System.out.print(num + ",");
}
}
System.out.println();
}
private static class evenPredicate implements Predicate<Integer> {
@Override
public boolean test(Integer num) {
return num % 2 == 0;
}
}
}
Output.
Print all numbers: 1,2,3,4,5,6,7,8,9,
Print even numbers: 2,4,6,8,
Print numbers greater than 3: 4,5,6,7,8,9,
5. Default Methods
Java provides a facility to create default methods inside the interface. Methods which are defined inside the interface and tagged with default are known as default methods. These methods are non-abstract methods.
public class DefaultMethodExample {
public static void main(String args[]) {
Vehicle vehicle = new Car();
vehicle.print();
}
}
interface Vehicle {
// default method
default void print() {
System.out.println("I am a vehicle!");
}
// static method
static void blowHorn() {
System.out.println("Blowing horn!!!");
}
}
interface FourWheeler {
default void print() {
System.out.println("I am a four wheeler!");
}
}
class Car implements Vehicle, FourWheeler {
public void print() {
Vehicle.super.print();
FourWheeler.super.print();
Vehicle.blowHorn();
System.out.println("I am a car!");
}
}
Output.
I am a vehicle!
I am a four wheeler!
Blowing horn!!!
I am a car!
6. Streams
6.1 What is Stream?
Stream represents a sequence of objects from a source, which supports aggregate operations. Following are the characteristics of a Stream:
Sequence of elements
− A stream provides a set of elements of specific type in a sequential manner. A stream gets/computes elements on demand. It never stores the elements.Source
− Stream takes Collections, Arrays, or I/O resources as input source.Aggregate operations
− Stream supports aggregate operations like filter, map, limit, reduce, find, match, and so on.Pipelining
− Most of the stream operations return stream itself so that their result can be pipelined. These operations are called intermediate operations and their function is to take input, process them, and return output to the target.collect()
method is a terminal operation which is normally present at the end of the pipelining operation to mark the end of the stream.Automatic iterations
− Stream operations do the iterations internally over the source elements provided, in contrast to Collections where explicit iteration is required.
6.2 Generating Streams
With Java 8, Collection interface has two methods to generate a Stream.
stream()
− Returns asequential stream
considering collection as its source.parallelStream()
− Returns aparallel stream
considering collection as its source.
List<String> names = Arrays.asList("Johnny", "", "Peter", "Sean", "", "George");
List<String> filtered = names.stream().filter(name -> !name.isEmpty()).collect(Collectors.toList());
List<String> filtered2 = names.parallelStream().filter(name -> !name.isEmpty()).collect(Collectors.toList());
// names = [Johnny, , Peter, Sean, , George]
// filtered = [Johnny, Peter, Sean, George]
// filtered2 = [Johnny, Peter, Sean, George]
6.3 Stream Methods
forEach
- iterate each element of the streammap
- map each element to its corresponding resultfilter
- eliminate elements based on a criterialimit
- reduce the size of the streamsorted
- sort the elements in stream
Example
private static void streamMethods() {
List<Integer> nums = Arrays.asList(3, 7, 1, 8, 2, 4, 9, 5, 6);
// forEach
System.out.println("forEach");
nums.stream().forEach(System.out::println);
List<Integer> result;
// map
System.out.print("map: ");
result = nums.stream().map(i -> i * i).collect(Collectors.toList());
System.out.println(result);
// filter
System.out.print("filter: ");
result = nums.stream().filter(i -> i > 4).collect(Collectors.toList());
System.out.println(result);
// limit
System.out.print("limit: ");
result = nums.stream().limit(3).collect(Collectors.toList());
System.out.println(result);
// sorted
System.out.print("sorted: ");
result = nums.stream().sorted().collect(Collectors.toList());
System.out.println(result);
}
Output.
forEach
3
7
1
8
2
4
9
5
6
map: [9, 49, 1, 64, 4, 16, 81, 25, 36]
filter: [7, 8, 9, 5, 6]
limit: [3, 7, 1]
sorted: [1, 2, 3, 4, 5, 6, 7, 8, 9]
6.4 Pipeline
Pipe the methods.
private static void pipeline() {
// pipelining
List<Integer> nums = Arrays.asList(3, 7, 1, 8, 2, 9, 5, 6);
System.out.print("pipelining: ");
List<Integer> result = nums.stream().sorted().filter(i -> i > 4).limit(3).map(i->i*i).collect(Collectors.toList());
System.out.println(result);
// sorted: [1,2,3,4,5,6,7,8,9]
// filter: [5,6,7,8,9]
// limit: [5,6,7]
// map: [25,36,49]
// forEach: [25,36,49]
}
Output.
pipelining: [25, 36, 49]
6.5 Collectors
Collectors are used to combine the result of processing on the elements of a stream. Collectors can be used to return a list or a string.
private static void collectors() {
List<String> names = Arrays.asList("Johnny", "", "Peter", "Sean", "", "George");
// convert stream to list
List<String> filtered = names.stream().filter(name -> !name.isEmpty()).collect(Collectors.toList());
System.out.println("Filtered List: " + filtered);
// convert list to string with common as delimiter
String merged = names.stream().filter(name -> !name.isEmpty()).collect(Collectors.joining(", "));
System.out.println("Merged String: " + merged);
}
Output.
Filtered List: [Johnny, Peter, Sean, George]
Merged String: Johnny, Peter, Sean, George
6.6 Statistics
With Java 8, statistics collectors are introduced to calculate all statistics when stream processing is being done.
private static void statistics() {
List<Integer> nums = Arrays.asList(3, 7, 1, 8, 2, 9, 5, 6);
IntSummaryStatistics stats = nums.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("Maximum number in List : " + stats.getMax());
System.out.println("Minimum number in List : " + stats.getMin());
System.out.println("Sum of all numbers : " + stats.getSum());
System.out.println("Average of all numbers : " + stats.getAverage());
}
Output.
Maximum number in List : 9
Minimum number in List : 1
Sum of all numbers : 41
Average of all numbers : 5.125
7. Optional Class
Optional
is a container object used to contain not-null objects. Optional object is used to represent null with absent value. This class has various utility methods to facilitate code to handle values as ‘available’ or ‘not available’ instead of checking null values. It is introduced in Java 8 and is similar to what Optional is in Guava.
public class OptionalClassExample {
public static void main(String args[]) {
Integer value1 = null;
Integer value2 = new Integer(10);
//Optional.ofNullable - allows passed parameter to be null.
Optional<Integer> a = Optional.ofNullable(value1);
//Optional.of - throws NullPointerException if passed parameter is null
Optional<Integer> b = Optional.of(value2);
OptionalSum os = new OptionalSum();
System.out.println(os.sum(a,b));
}
}
class OptionalSum {
public Integer sum(Optional<Integer> a, Optional<Integer> b) {
//Optional.isPresent - checks the value is present or not
System.out.println("First parameter is present: " + a.isPresent());
System.out.println("Second parameter is present: " + b.isPresent());
//Optional.orElse - returns the value if present otherwise returns
//the default value passed.
Integer value1 = a.orElse(new Integer(0));
//Optional.get - gets the value, value should be present
Integer value2 = b.get();
return value1 + value2;
}
}
Output.
First parameter is present: false
Second parameter is present: true
10
8. Nashorn JavaScript
With Java 8, Nashorn
, a much improved javascript engine is introduced, to replace the existing Rhino
. Nashorn provides 2 to 10 times better performance, as it directly compiles the code in memory and passes the bytecode to JVM.
8.1 jjs
For Nashorn engine, JAVA 8 introduces a new command line tool, jjs
, to execute javascript codes at console.
Create a javascript file name ‘sample.js’ as follows.
// sample.js
print('Hello World from javascript!');
Use jjs to execute this js file.
jjs sample.js
Output.
Hello World from javascript!
8.2 Calling JavaScript from Java
Using ScriptEngineManager, JavaScript code can be called and interpreted in Java.
public class NashornExample {
// Call javascript from java with ScriptEngineManager
public static void main(String args[]) {
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine nashorn = scriptEngineManager.getEngineByName("nashorn");
String name = "Johnny";
Integer result = null;
try {
// call eval without return value
nashorn.eval("print('" + name + "')");
// call eval with return value
result = (Integer) nashorn.eval("10 + 2");
} catch(ScriptException e) {
System.out.println("Error executing script: "+ e.getMessage());
}
System.out.println(result.toString());
}
}
Output.
Johnny
12
8.3 Calling Java from JavaScript
Create javascript file named ‘calljava.js’ as follows.
// calljava.js
var BigDecimal = Java.type('java.math.BigDecimal');
function calculate(amount, percentage) {
var result = new BigDecimal(amount).multiply(new BigDecimal( )).divide(
new BigDecimal("100"), 2, BigDecimal.ROUND_HALF_EVEN);
return result.toPlainString();
}
var result = calculate(568000000000000000023,13.9);
print(result);
Run it with jjs.
> jjs calljava.js
78952000000000002017.94
9. New Date/Time API
With Java 8, a new Date-Time API is introduced to cover the following drawbacks of old date-time API.
Not thread safe
− java.util.Date is not thread safe, thus developers have to deal with concurrency issue while using date. The new date-time API is immutable and does not have setter methods.Poor design
− Default Date starts from 1900, month starts from 1, and day starts from 0, so no uniformity. The old API had less direct methods for date operations. The new API provides numerous utility methods for such operations.Difficult time zone handling
− Developers had to write a lot of code to deal with timezone issues. The new API has been developed keeping domain-specific design in mind.
Java 8 introduces a new date-time API under the package java.time. Following are some of the important classes introduced in java.time package.
Local
− Simplified date-time API with no complexity of timezone handling.Zoned
− Specialized date-time API to deal with various timezones.
9.1 Local Date-Time API
Following three classes simplify the development where timezones are not required.
- LocalDate
- LocalTime
- LocalDateTime
// Local Date-Time API
private static void testLocalDateTime() {
// Get the current date and time
LocalDateTime currentDT = LocalDateTime.now();
System.out.println("Current DateTime: " + currentDT);
LocalDate date1 = currentDT.toLocalDate();
System.out.println("Current Date: " + date1);
Month month = currentDT.getMonth();
int day = currentDT.getDayOfMonth();
int seconds = currentDT.getSecond();
System.out.println("Month: " + month +", day: " + day +", seconds: " + seconds);
// Update year and month
LocalDateTime date2 = currentDT.withDayOfMonth(10).withYear(2012);
System.out.println("New DateTime: " + date2);
// Local Date
LocalDate date = LocalDate.of(2014, Month.DECEMBER, 12);
System.out.println("New Local Date: " + date);
// 22 hour 15 minutes
LocalTime time = LocalTime.of(22, 15);
System.out.println("New Local Time: " + time);
// Parse a string
LocalTime timeParse = LocalTime.parse("20:15:30");
System.out.println("Time from String: " + timeParse);
}
Output.
Current DateTime: 2019-04-14T10:15:37.325
Current Date: 2019-04-14
Month: APRIL, day: 14, seconds: 37
New DateTime: 2012-04-10T10:15:37.325
New Local Date: 2014-12-12
New Local Time: 22:15
Time from String: 20:15:30
9.2 Zoned Date-Time API
Zoned date-time API is to be used when time zone
is to be considered.
// Zoned Date-Time API
public static void testZonedDateTime() {
// Get the current date and time
ZonedDateTime zdt = ZonedDateTime.parse("2007-12-03T10:15:30+05:30[Asia/Shanghai]");
System.out.println("Zone DateTime: " + zdt);
System.out.println("Zone Id: " + zdt.getZone());
ZoneId id = ZoneId.of("Europe/Paris");
System.out.println("ZoneId: " + id);
ZoneId currentZone = ZoneId.systemDefault();
System.out.println("CurrentZone: " + currentZone);
}
Output.
Zone DateTime: 2007-12-03T10:15:30+08:00[Asia/Shanghai]
Zone Id: Asia/Shanghai
ZoneId: Europe/Paris
CurrentZone: America/Los_Angeles
9.3 Chrono Units Enum
java.time.temporal.ChronoUnit enum is added in Java 8 to replace the integer values used in old API to represent day, month, etc. Let us see them in action.
// Chrono Units Enum
public static void testChromoUnits() {
// Get the current date
LocalDate today = LocalDate.now();
System.out.println("Current date: " + today);
// Add 1 week to the current date
LocalDate nextWeek = today.plus(1, ChronoUnit.WEEKS);
System.out.println("Next week: " + nextWeek);
// Add 1 month to the current date
LocalDate nextMonth = today.plus(1, ChronoUnit.MONTHS);
System.out.println("Next month: " + nextMonth);
// Add 1 year to the current date
LocalDate nextYear = today.plus(1, ChronoUnit.YEARS);
System.out.println("Next year: " + nextYear);
// Add 10 years to the current date
LocalDate nextDecade = today.plus(1, ChronoUnit.DECADES);
System.out.println("Date after ten year: " + nextDecade);
}
Output.
Current date: 2019-04-14
Next week: 2019-04-21
Next month: 2019-05-14
Next year: 2020-04-14
Date after ten year: 2029-04-14
9.4 Period and Duration
With Java 8, two specialized classes are introduced to deal with the time differences.
Period
− It deals with date based amount of time.Duration
− It deals with time based amount of time.
// Period
public static void testPeriod() {
//Get the current date
LocalDate date1 = LocalDate.now();
System.out.println("Current date: " + date1);
//add 1 month to the current date
LocalDate date2 = date1.plus(1, ChronoUnit.MONTHS);
System.out.println("Next month: " + date2);
Period period = Period.between(date2, date1);
System.out.println("Period: " + period);
}
// Duration
public static void testDuration() {
LocalTime time1 = LocalTime.now();
Duration twoHours = Duration.ofHours(2);
LocalTime time2 = time1.plus(twoHours);
Duration duration = Duration.between(time1, time2);
System.out.println("Duration: " + duration);
}
Output.
Current date: 2019-04-14
Next month: 2019-05-14
Period: P-1M
Duration: PT2H
9.5 Temporal Adjusters
TemporalAdjuster is used to perform the date mathematics. For example, get the “Second Saturday of the Month” or “Next Tuesday”.
// Temporal Adjusters
public static void testAdjusters() {
//Get the current date
LocalDate localDate = LocalDate.now();
System.out.println("Current date: " + localDate);
//get the next tuesday
LocalDate nextTuesday = localDate.with(TemporalAdjusters.next(DayOfWeek.TUESDAY));
System.out.println("Next Tuesday on : " + nextTuesday);
//get the second saturday of next month
LocalDate firstInYear = LocalDate.of(localDate.getYear(), localDate.getMonth(), 1);
LocalDate secondSaturday = firstInYear.with(TemporalAdjusters.nextOrSame(
DayOfWeek.SATURDAY)).with(TemporalAdjusters.next(DayOfWeek.SATURDAY));
System.out.println("Second Saturday on : " + secondSaturday);
}
Output.
Current date: 2019-04-14
Next Tuesday on : 2019-04-16
Second Saturday on : 2019-04-13
9.6 Backward Compatibility
A toInstant()
method is added to the original Date and Calendar objects, which can be used to convert them to the new Date-Time API. Use an ofInstant(Insant,ZoneId)
method to get a LocalDateTime or ZonedDateTime object.
// Backward Compatibility with ofInstant
public static void testBackwardCompatability() {
//Get the current date
Date currentDate = new Date();
System.out.println("Current date: " + currentDate);
//Get the instant of current date in terms of milliseconds
Instant now = currentDate.toInstant();
ZoneId currentZone = ZoneId.systemDefault();
LocalDateTime localDateTime = LocalDateTime.ofInstant(now, currentZone);
System.out.println("Local date: " + localDateTime);
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(now, currentZone);
System.out.println("Zoned date: " + zonedDateTime);
}
Output.
Current date: Sun Apr 14 10:30:19 PDT 2019
Local date: 2019-04-14T10:30:19.572
Zoned date: 2019-04-14T10:30:19.572-07:00[America/Los_Angeles]
10. Base64
Java 8 now has inbuilt encoder and decoder for Base64 encoding. In Java 8, we can use three types of Base64 encoding.
Simple
− Output is mapped to a set of characters lying inA-Za-z0-9+/
. The encoder does not add any line feed in output, and the decoder rejects any character other than A-Za-z0-9+/.URL
− Output is mapped to set of characters lying inA-Za-z0-9+_
. Output is URL and filename safe.MIME
− Output is mapped to MIME friendly format. Output is represented in lines of no more than 76 characters each, and uses a carriage return ‘\r’ followed by a linefeed ‘\n’ as the line separator. No line separator is present to the end of the encoded output.
Example
public static void main(String args[]) {
try {
// Original
String original = "jojozhuang.github.io?java8";
System.out.println("Original String: " + original);
// Encode using basic encoder
String strEncoded = Base64.getEncoder().encodeToString(
original.getBytes("utf-8"));
System.out.println("Base64 Encoded String (Basic) :" + strEncoded);
// Decode
byte[] base64decodedBytes = Base64.getDecoder().decode(strEncoded);
System.out.println("Decoded String: " + new String(base64decodedBytes, "utf-8"));
strEncoded = Base64.getUrlEncoder().encodeToString(
original.getBytes("utf-8"));
System.out.println("Base64 Encoded String (URL) :" + strEncoded);
// MIME Example
String uuid = UUID.randomUUID().toString();
System.out.println("Original UUID: " + uuid);
byte[] mimeBytes = uuid.getBytes("utf-8");
String mimeEncodedString = Base64.getMimeEncoder().encodeToString(mimeBytes);
System.out.println("Base64 Encoded String (MIME) :" + mimeEncodedString);
base64decodedBytes = Base64.getDecoder().decode(mimeEncodedString);
System.out.println("Decoded UUID: " + new String(base64decodedBytes, "utf-8"));
} catch(UnsupportedEncodingException e) {
System.out.println("Error :" + e.getMessage());
}
}
Output.
Original String: jojozhuang.github.io?java8
Base64 Encoded String (Basic) :am9qb3podWFuZy5naXRodWIuaW8/amF2YTg=
Decoded String: jojozhuang.github.io?java8
Base64 Encoded String (URL) :am9qb3podWFuZy5naXRodWIuaW8_amF2YTg=
Original UUID: 565ce125-6615-441e-8719-f7cd721e0077
Base64 Encoded String (MIME) :NTY1Y2UxMjUtNjYxNS00NDFlLTg3MTktZjdjZDcyMWUwMDc3
Decoded UUID: 565ce125-6615-441e-8719-f7cd721e0077