Java 8 Features
Some of the important Java 8 features are:
- JavaFX
JavaFX is a set of graphics and media packages that enables developers to design, create, test, debug,
and deploy rich client applications that operate consistently across diverse platforms.
- Lambda expressions
A lambda expression is an anonymous function. A function that doesn’t have a name and doesn’t belong
to any class.
//Syntax of lambda expression
(parameter_list) -> {function_body}
(x, y) -> x + y lambda expression takes two arguments x and y and returns the sum of these
Lambda expression only has body and parameter list.
1.No name – function is anonymous so we don’t care about the name
2. Parameter list
3. Body – This is the main part of the function.
4. No return type. The java 8 compiler is able to infer the return type by checking the code. you need not
to mention it explicitly.
Functional interface = An interface with only single abstract method (exemple: Runnable, callable,
ActionListener etc).
To use lambda expression, you need to either create your own functional interface or use the pre-
defined functional interface provided by Java
To use function interface:
Pre Java 8: We create anonymous inner classes.
Post Java 8: You can use lambda expression instead of anonymous inner classes.
Pre Java 8:
b.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
System.out.println("Hello World!");
}
});
Post Java 8:
b.addActionListener(e -> System.out.println("Hello World!"));
Exemplu 1: Java Lambda Expression with no parameter
@FunctionalInterface
interface MyFunctionalInterface {
//A method with no parameter
public String sayHello();
}
// lambda expression
MyFunctionalInterface msg = () -> {
return "Hello";
};
System.out.println(msg.sayHello());
Exemplu 2: Java Lambda Expression with single parameter
@FunctionalInterface
interface MyFunctionalInterface {
//A method with single parameter
public int incrementByFive(int a);
}
MyFunctionalInterface f = (num) -> num+5;
System.out.println(f.incrementByFive(22))
Exemplu 3: Iterating Map using for each and Lambda expression
Map<String, Integer> prices = new HashMap<>();
prices.put("Apple", 50);
prices.put("Orange", 20);
prices.forEach((k,v)->System.out.println("Fruit: " + k + ", Price: " + v));
- Method references
Method reference is a shorthand notation of a lambda expression to call a method
If my lambda expression is:
str -> System.out.println(str)
I can replace it with a method reference:
System.out::println
The :: operator is used in method reference to separate the class or object from the method name
1. Method reference to an instance method of an object
@FunctionalInterface
interface MyInterface{
void display();
}
public class Example {
public void myMethod(){
System.out.println("Instance Method");
}
public static void main(String[] args) {
Example obj = new Example();
// Method reference using the object of the class
MyInterface ref = obj::myMethod;
// Calling the method of functional interface
ref.display();
}
}
2. Method reference to a static method of a class
Interface BiFunction<T,U,R>
Type Parameters:
T - the type of the first argument to the function
U - the type of the second argument to the function
R - the type of the result of the function
import java.util.function.BiFunction;
class Multiplication{
public static int multiply(int a, int b){
return a*b;
}
}
public class Example {
public static void main(String[] args) {
BiFunction<Integer, Integer, Integer> product = Multiplication::multiply;
int pr = product.apply(11, 5);
System.out.println("Product of given number is: "+pr);
}
}
3. Method reference to an instance method of an arbitrary object of a particular type
String[] stringArray = { "Steve", "Rick", "Aditya"};
Arrays.sort(stringArray, String::compareToIgnoreCase);
for(String str: stringArray){
System.out.println(str);
}
Old way:
String[] names = { "Steve", "Rick", "Aditya"};
Arrays.sort(names, new Comparator<String>() {
@Override
public int compare(String str1, String str2) {
return str1.compareToIgnoreCase(str2);
}
});
for(String str: names){
System.out.println(str);
}
4. Method reference to a constructor
@FunctionalInterface
interface MyInterface{
Hello display(String say);
}
class Hello{
public Hello(String say){
System.out.print(say);
}
}
public class Example {
public static void main(String[] args) {
//Method reference to a constructor
MyInterface ref = Hello::new;
ref.display("Hello World!");
}
}
Java Functional Interfaces
An interface with only single abstract method is called functional interface.
The functional interface should have Only one abstract method. Along with the one abstract method,
they can have any number of default and static methods.
To use lambda expression in Java, you need to either create your own functional interface or use the pre
defined functional interface provided by Java
Example 1: Creating your own functional interface
@FunctionalInterface
interface MyFunctionalInterface {
public int addMethod(int a, int b);
}
public class BeginnersBookClass {
public static void main(String args[]) {
// lambda expression
MyFunctionalInterface sum = (a, b) -> a + b;
System.out.println("Result: "+sum.addMethod(12, 100));
}
}
Example 2: Using predefined functional interface
import java.util.function.IntBinaryOperator;
public class BeginnersBookClass {
public static void main(String args[]) {
// lambda expression
IntBinaryOperator sum = (a, b) -> a + b;
System.out.println("Result: " + sum.applyAsInt(12, 100));
}
}
Interface Changes – default method and static method
Java 8 allows the interfaces to have default and static methods.
The reason we have default methods in interfaces is to allow the developers to add new methods to the
interfaces without affecting the classes that implements these interfaces.
Reason:
If we add a new method to the XYZInterface, we have to change the code in all the classes that
implements the interface. Maybe we talk about hundreds of classes.
Static methods in interfaces are similar to the default methods except that we cannot override these
methods in the classes that implements these interfaces.
Example: Default method in Interface
interface MyInterface{
/* This is a default method so we need not
* to implement this method in the implementation
* classes
*/
default void newDefaultMethod(){
System.out.println("Newly added default method");
}
/* Already existing public and abstract method
* We must need to implement this method in
* implementation classes.
*/
void existingMethod(String str);
}
public class Example implements MyInterface{
// implementing abstract method
public void existingMethod(String str){
System.out.println("String is: "+str);
}
public static void main(String[] args) {
Example obj = new Example();
//calling the default method of interface
obj.newDefaultMethod();
//calling the abstract method of interface
obj.existingMethod("Java 8 is easy to learn");
}
}
Example 2: Static method in Interface
Since these methods are static, we cannot override them in the implementation classes.
Similar to default methods, we need to implement these methods in
implementation classes so we can safely add them to the existing interfaces.
!!! Nu am inteles cele 2. Nu se bat cap in cap?
interface MyInterface{
default void newMethod(){
System.out.println("Newly added default method");
}
static void anotherStaticMethod(){
System.out.println("Newly added static method");
}
void existingMethod(String str);
}
public class Example implements MyInterface{
// implementing abstract method
public void existingMethod(String str){
System.out.println("String is: "+str);
}
public static void main(String[] args) {
Example obj = new Example();
//calling the default method of interface
obj.newMethod();
//calling the static method of interface
MyInterface.anotherStaticMethod();
//calling the abstract method of interface
obj.existingMethod("Java 8 is easy to learn");
}
}
Now the differences between interfaces and abstract classes: abstract class can have constructor while
in interfaces we can’t have constructors.
The multiple inheritance problem can occur, when we have two interfaces with the default methods of
same signature.
interface MyInterface{
default void newMethod(){
System.out.println("Newly added default method");
}
}
interface MyInterface2{
default void newMethod(){
System.out.println("Newly added default method");
}
}
public class Example implements MyInterface, MyInterface2{
….. obj.newMethod();
}
To solve this problem, we can implement this method in the implementation class like this:
public class Example implements MyInterface, MyInterface2{
/ /Implementation of duplicate default method
public void newMethod(){
System.out.println("Implementation of default method");
}
Stream API
By using streams we can perform various aggregate operations on the data returned from collections,
arrays, Input/Output operations
List<String> names = new ArrayList<String>();
names.add("Ajeet");
names.add("Negan");
names.add("Aditya");
names.add("Steve");
//Old style
int count = 0;
for (String str : names) {
if (str.length() < 6)
count++;
}
//Using Stream and Lambda expression
long count = names.stream().filter(str->str.length()<6).count();
In the second example, the stream() method returns a stream of all the names, the filter() method
returns another stream of names with length less than 6, the count() method reduces this stream to the
result. All these operations are happening parallelly which means we are able to parallelize the code
with the help of streams. Parallel execution of operations using stream is faster than sequential
execution without using streams.
How to work with Stream in Java
1. Create a stream
2. Perform intermediate operations on the initial stream to transform it into another
3. Perform terminal operation
Java Stream Features
1. Stream does not store the elements.
2. The aggregate operations that we perform on the collection, array, or any data source do not
change the data of the source.
3. All the stream operations are lazy - they are not executed until they are needed.
Example 1: Iterating and displaying selected integers
Stream.iterate(1, count->count+1)
.filter(number->number%3==0)
.limit(6)
.forEach(System.out::println);
Example 2: Concatenating two streams
List<String> alphabets = Arrays.asList("A","B","C");
List<String> names = Arrays.asList("Sansa","Jon","Arya");
//creating two streams from the two lists and concatenating them into one
Stream<String> opstream = Stream.concat(alphabets.stream(), names.stream());
//displaying the elements of the concatenated stream
opstream.forEach(str->System.out.print(str+" "));
Stream Filter
The filter() is an intermediate operation that reads the data from a stream and returns a new stream
after transforming the data based on the given condition.
List<String> names = Arrays.asList("Melisandre","Sansa”);
//Creating the stream of all names
Stream<String> allNames = names.stream();
//Creating another stream by filtering long names using filter()
Stream<String> longNames = allNames.filter(str -> str.length() > 6);
Example 1: Stream filter() and collect()
List<String> names = Arrays.asList("Melisandre","Sansa","Jon");
List<String> longnames = names.stream() // converting the list to stream
.filter(str -> str.length() > 6) //filter the stream to create a new stream
.collect(Collectors.toList()); //collect the final stream and convert toList
longnames.forEach(System.out::println);
Example 2: Stream filter() with multiple conditions
List<String> longnames = names.stream()
.filter(str -> str.length() > 6 && str.length() < 8) //Multiple conditions
.collect(Collectors.toList());
Example 3: Stream filter() and map()
List<Integer> num = Arrays.asList(1,2,3,4,5,6);
List<Integer> squares = num.stream()
.map(n -> n * n)
.collect(Collectors.toList());
System.out.println(squares);
Output: [1, 4, 9, …]
forEach
forEach method to iterate over collections and Streams.
Map<Integer, String> hmap = new HashMap<Integer, String>();
hmap.put(1, "Monkey");
hmap.put(2, "Dog");
hmap.forEach((key,value)->System.out.println(key+" - "+value));
hmap.forEach((key,value)->{
if(key == 4
|| "Cat".equals(value)) { System.out.println(value); }
});
forEach to iterate a List
List<String> fruits = new ArrayList<String>();
fruits.add("Apple");
//lambda expression in forEach Method
fruits.forEach(str->System.out.println(str));
forEach method to iterate a Stream
List<String> names = new ArrayList<String>();
names.add("Maggie");
names.stream() //creating stream
.filter(f->f.startsWith("M")) //filtering names that starts with M
.forEach(System.out::println); //displaying the stream using forEach
Stream forEachOrdered()
when working with parallel streams, you would always want to use the forEachOrdered() method when
the order matters to you, as this method guarantees that the order of elements would be same as the
source
names.stream()
.filter(f->f.startsWith("M"))
.parallel()
.forEachOrdered(n->System.out.println(n));
Stream Collectors Class
Collectors is a final class that extends the Object class.
We are grouping the elements of a list using groupingBy() method of Collectors class and printing the
occurrences of each element in the list:
List<String> names =
Arrays.asList("Jon", "Ajeet", "Steve",
"Ajeet", "Jon", "Ajeet");
Map<String, Long> map =
names.stream().collect(
Collectors.groupingBy(Function.identity(), Collectors.counting())
);
System.out.println(map);
Output: {Steve=1, Jon=2, Ajeet=3}
Stream Collectors example of fetching data as List
studentlist.add(new Student(11,"Jon",22));
studentlist.add(new Student(22,"Steve",18));
List<String> names = studentlist.stream()
.map(n->n.name)
.collect(Collectors.toList());
System.out.println(names);
Output: [Jon, Steve]
Collecting Data as Set
//Fetching student data as a Set
Set<Student> students = studentlist.stream()
.filter(n-> n.id>22)
.collect(Collectors.toSet());
//Iterating Set
for(Student stu : students) {
System.out.println(stu.id+" "+stu.name+" "+stu.age);
}
StringJoiner
class StringJoiner
Using this class we can join more than one strings with the specified delimiter, we can also provide
prefix and suffix to the final string while joining multiple strings.
// Passing Hyphen(-) as delimiter
StringJoiner mystring = new StringJoiner("-");
// Joining multiple strings by using add() method
mystring.add("Logan");
mystring.add("Magneto");
System.out.println(mystring);
Output: Logan-Magneto
Adding prefix and suffix to the output String
StringJoiner mystring = new StringJoiner(",", "(", ")");
mystring.add("Negan");
mystring.add("Rick");
Output: (Negan,Rick)
Merging two StringJoiner objects
StringJoiner mystring = new StringJoiner(",", "(", ")");
mystring.add("Negan");
mystring.add("Rick");
System.out.println("First String: "+mystring);
StringJoiner myanotherstring = new StringJoiner("-", "pre", "suff");
myanotherstring.add("Sansa");
myanotherstring.add("Imp");
System.out.println("Second String: "+myanotherstring);
/* Merging both the strings
* The important point to note here is that the output string will be
* having the delimiter prefix and suffix of the first string “(“ and
“)” (the string
* which is calling the merge method of StringJoiner)
*/
StringJoiner mergedString = mystring.merge(myanotherstring);
System.out.println(mergedString);
First String: (Negan,Rick)
Second String: preSansa-Impsuff
(Negan,Rick,Sansa-Imp)
Optional Class
Optional class in java.util package. This class is introduced to avoid NullPointerException that we
frequently encounters if we do not perform null checks in our code.
Old style:
String[] str = new String[10];
//Getting the substring
String str2 = str[9].substring(2, 5);
System.out.print(str2);
Output:
Exception in thread "main" java.lang.NullPointerException
at Example.main(Example.java:5)
Optional.ofNullable() method of the Optional class, returns a Non-empty Optional if the given
object has a value, otherwise it returns an empty Optional.
by using isPresent() method we can check whether the particular Optional object(or instance) is
empty or no-empty.
String[] str = new String[10];
Optional<String> isNull = Optional.ofNullable(str[9]);
if(isNull.isPresent()){
//Getting the substring
String str2 = str[9].substring(2, 5);
//Displaying substring
System.out.print("Substring is: "+ str2);
}
else{
System.out.println("Can’t get the substr from an empty strng");
}
Arrays Parallel Sort
in the Arrays class a new method parallelSort() introduced to support the parallel sorting of array
elements.
1.The given array is divided into the sub arrays and the sub arrays are further divided into
the their sub arrays, this happens until the sub array reaches a minimum granularity.
2. The sub arrays are sorted individually by multiple threads. The parallel sort uses Fork/Join
Framework for sorting sub arrays parallelly.
3. The sorted sub arrays are merged
The parallelSort() method uses the concept of multithreading which makes it much faster compared to
the normal sort when there are lot of elements.
Sorting Primitive Data types with Parallel Sort
int numbers[] = {22, 89, 1, 32, 19, 5};
//Parallel Sort method for sorting int array
Arrays.parallelSort(numbers);
//converting the array to stream and displaying using forEach
Arrays.stream(numbers).forEach(n->System.out.print(n+" "));
Output: 1 5 19 22 32 89