Java 8

Lambda Expressions

They are nameless functions.

Use

  1. To enable functional programming
  2. Write concise and readable code
  3. Use API effectively
  4. Parallel programming

How to write them

  1. No return type
  2. No modifier
  3. No name

Public String lengthOfString(String s) {

return s.length;

}

(s) -> {s.length;}

s -> s.length;

Functional Interface

An Interface that contains just one abstract method. It can contain any number of default and static methods. They are present in the java.util package.

Examples
Runnable, Callable, Comparable

You can declare an interface FI by @FunctionalInterface. This Is not necessary but it helps for reporting compile errors in case you use more than 1 abstract method or none of them. 

If another FI extends the interface then it needs to ensure only one method is present in the interface which will come from the parent.

Usage

  1. Comparator Interface to sort List – for descending order

Collections.sort(myList, new MyComparator());

Collections.sort(myList, ( i, j ) – > ( ( i > j ) ? -1 : ( i < j ) ? 1 : 0 );

  1. TreeSet/TreeMap sorting using Lambda Expression

TreeMap<Integer, String> tm = new TreeMap<>( new myComparator() );

TreeMap<Integer, String> tm = new TreeMap<>( ( i, j ) – > ( ( i > j ) ? -1 : ( i < j ) ? 1 : 0 );

TreeMap sorts according to keys.

  1. Custom objects sorting

Assume list of Employee objects

Collections.sort (

 listOfEmployee, (e1, e2) -> ( (e1.id>e2.id) ? 1: (e1.uid<e2.id) ? -1 : 0 ) )

Anonymous Inner class Class & Lambda Expressions

Anonymous Inner class class

Runnable r = new Runnable () {

public void run(){

….

}

};

Lambda Expressions

Runnable r = () -> {};

Differences

  1. Anonymous Inner class can be made for concrete class, abstract class, interfaces. λ only for interface (with just one method).
  2. Anonymous Inner class will have .class generated for it and not for λ
  3. “this” in Anonymous Inner class points to instance variables(variables in the class are wrapped). “this” in λ points to local variables. 

Class A {

Int c=1;

Runnable r = () -> {

 c=2;
S.O.P(c); // prints 2

};

  1. Memory for AIC is cleared in heap and for λ in the method area.

AIC are more powerful than λ

Closure

Here λ expression can access class variables. It can also access local variables of the function they are declared in. However, if they access local variables then local variables become final. 

So you can’t change their value. 

This is a closure 

However, if you don’t use local method level variables then you can change the variable.

Default Methods in Interfaces

Interfaces can have default methods.

If there is multiple inheritance in the class from two different interfaces with the same default method then there could be ambiguity.

class A implements iA, iB {

public void common () {

iA.super.common();

}

}

So we can resolve ambiguity by the above method of specifying which interface’s method to be used. 

However, if two interfaces with similar abstract methods are there then it will work.

But if two interfaces with similar abstract methods and conflicting return types in an error.

Interface with default methods != Abstract classes

  1. every variable in the interface will be public static final. 
  2. No instance variables.
  3. No constructors 
  4. Abstract class cant refer lambda expressions
  5. We can’t override object methods in interfaces

Static methods in Interfaces

Static methods are now allowed. 

However, they are never inherited in the implementation classes.

So we can have the same named function in those classes.

Also one can now have the main method in interfaces as it is also a static method. We can run the main method of the Interface.

Predicate Functional Interface

It is a functional interface used for boolean testing. It has only one abstract method “test” and it can take only one parameter.

Predicate<Integer> result = a -> (a > 10);

result.test(100); // true

Similar to an interface like this

BooleanInterface result = new BooleanInterface {

public boolean test(Integer a) {

If (a > 10)

return true;

return false;

}

}

There can be predicate joining.

  1. negate() – p1.negate();
  2. or() – p1.or(p2);
  3. and() – p1.and(p2);

So two conditions are tested based on these joins. These are default methods in Predicate Interface.

Predicate has a static method “isEqual()”

Predicate<Integer> p = Predicate.isEqual(10);

p.test(10); //true

Function Functional Interface

It is a functional interface in which we can have return type as anything. Predicate is a special type of function interface where return type is boolean. It has only one abstract method “apply” and it can take only one parameter.

Function<T,R> name;

Example – count spaces in string

Function<String,Integer> spaceCount = s -> ( s.length – s.replaceAll(“ ”).length);

spaceCount.apply(“Hi what’s going on”);

Additionally, you can change the values of the object using this as it acts as a function.

Function Chaining(default methods)

  1. andThen – f1.andThen(f2).apply(“”) – first f1 will be called then f2
  2. compose – f1.compose(f2).apply(“”) – first f2 will be called then f1

Static method – f1.identity().apply(“”) – just returns the same thing as input parameter

Consumer Functional Interface

It just “consumes”.It is similar to Function FI just that return type is void.

Void public accept(T t)

Consumer<Student> display  =  s->{ System.out.println( “Name” +  s.getName() );

System.out.println(“Grade” + s.getGrade() )  };

Consumer Chaining

  1. andThen – c1.andThen(c2).accept(t);

Supplier Functional Interface

It just “supplies”. It is similar to Function FI just that it has no argument but it has a return type.

public R get();

Supplier<Integer> currentDate = () -> ( return new Date(); )

currentDate.get();

No chaining is possible here.

BiPredicate Functional Interface

Similar to predicate but takes 2 arguments. It has the same chaining concept.

BiPredicate<Integer,Integer> isEven = (a,b)->((a+b)%2==0);

isEven.test(2,3);//false

BiFunction Functional Interface

Similar to Function but takes 2 arguments. It has the same chaining concept.

BiFunction<Integer,String,Student> productString = (a,b)->new Student(a,b);

productString .apply(2,”gaurav”);// return Student object

BiConsumer Functional Interface

Similar to Consumer but takes 2 arguments. It has the same chaining concept.

BiConsumer<Integer,String> display= (a,b)->{S.O.P(“roll no=” + a + “, name = ” + name)};

display.get(2,”gaurav”);//roll no=2, name=gaurav

Primitive Type Functional Interface

Due to autoboxing and autounboxing, there will be a performance hit for the above interfaces in case we have input and output as primitive types.

This is because there will always be conversion.

So we have primitive type interfaces IntPredicate, LongPredicate etc

IntFunction FI – accepts int

ToIntFunction FI- return int

IntToDouble FI- takes int and returns double

IntConsumer – accepts int

IntObjConsumer – accepts int and object type

IntSupplier – returns int

If input and output is of the same type use UnaryOperator FI instead of Function.

If all inputs and output are of the same type use BinaryOperator FI instead of Function

Double Colon Operator :: – Method Reference Operator

This can be used instead of a lambda expression for giving a reference of another function.

<Class name>::<method name>

Class Test{

Public void m1(){

Some lines here

}

PSVM(){

Test mytest = new Test();

Runnable r = () -> {};

Runnable r = mytest::m1;

}

  1. Static Method

ClassName::methodName

  1. Non Static Method

instanceOfClass::methodName

System.out::println
Lambda Alternative (s->System.out.println())

  1. Constructor

MyClass::new

  1. Super Method

super::someSuperClassMethod

Streams

To process a collection of objects we use streams.

Stream s = c.stream();

Stream – is an interface in java.util.stream package.

C – is any collection.

stream() – is a default function inside every collection.

  1. Filter – selects elements based on the predicate

public Stream filter(Predicate<T> p)

Stream s = c.stream().filter(i -> (i%2==0) );

  1. Map – map the value of every object in the collection to a new value/create a new object

public Stream map(Function<T,R> f)

Stream s = c.stream().map(i -> i * 2);

  1. Collect – collects elements from the stream and adds to a collection

List<Integer> s = c.stream().map(i -> i * 2).collect( Collectors.toList() );

List<String> s = c.stream().map(s-> s.toUppercase() ).collect( Collectors.toList() );

  1. Count – Counts the elements in the stream

Integer evenNoCount = c.stream().filter(i -> (i%2==0)).count();

  1. Sorted – Sorts a collection

List<Integer> sortedNo = noList.stream().sorted().collect( Collectors.toList());

sorted(Comparator comparator ) – sorted( (i,j)-> ( i.compareTo(j) ));

  1. Min and Max

Integer min= noList.stream().min((i,j) -> i.compareTo(j)).get();

Integer max= noList.stream().max((i,j) -> i.compareTo(j)).get();

  1. forEach

noList.stream().forEach(System.out::println);

noList.stream().forEach(s- > S.O.P(s));

  1. toArray

Integer ar[] = noList.stream().toArray(Integer[]::new);

  1. Stream.of – can be applied to an array

Stream s = Stream.of(“hi”, “my”, “name”, “gaurav”) ;

Date Time Api 

The date/time API, before Java 8, presented multiple design problems.

Among the problems is the fact that the Date and SimpleDateFormatter classes aren’t thread-safe. To address this issue, Joda-Time uses immutable classes for handling date and time.

The Date class doesn’t represent an actual date, but instead, it specifies an instant in time, with millisecond precision. The year in a Date starts from 1900, while most of the date operations usually use Epoch time which starts from January 1st, 1970.

Also, the day, month and year offset of a Date is counterintuitive. Days start at 0, while month begins at 1. To access any of them, we must use the Calendar class. Joda-Time offers a clean and fluent API for handling dates and time.

  1. LocalDate
  2. LocalTime
  3. LocalDAteTime
  4. ZoneId
  5. Period

Map And Flat Map

Stream.flatMap, as it can be guessed by its name, is the combination of a map and a flat operation. That means that you first apply a function to your elements, and then flatten it. Stream.map only applies a function to the stream without flattening the stream.

To understand what flattening a stream consists in, consider a structure like [ [1,2,3],[4,5,6],[7,8,9] ] which has “two levels”. Flattening this means transforming it in a “one level” structure : [ 1,2,3,4,5,6,7,8,9 ].

Map – One to One

List<String> myList = Stream.of(“a”, “b”)  .map(String::toUpperCase) .collect(Collectors.toList()); // [a , b]

FlatMap – One to Many – Map + Flat

List<List<String>> list = Arrays.asList(

  Arrays.asList(“a”),

  Arrays.asList(“b”));

System.out.println(list  .stream()  .flatMap(Collection::stream)) //[a. b]

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *