Java 8
Lambda Expressions
They are nameless functions.
Use
- To enable functional programming
- Write concise and readable code
- Use API effectively
- Parallel programming
How to write them
- No return type
- No modifier
- 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
- 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 );
- 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.
- 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
- Anonymous Inner class can be made for concrete class, abstract class, interfaces. λ only for interface (with just one method).
- Anonymous Inner class will have .class generated for it and not for λ
- “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
};
- 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
- every variable in the interface will be public static final.
- No instance variables.
- No constructors
- Abstract class cant refer lambda expressions
- 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.
- negate() – p1.negate();
- or() – p1.or(p2);
- 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)
- andThen – f1.andThen(f2).apply(“”) – first f1 will be called then f2
- 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
- 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;
}
- Static Method
ClassName::methodName
- Non Static Method
instanceOfClass::methodName
System.out::println
Lambda Alternative (s->System.out.println())
- Constructor
MyClass::new
- 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.
- Filter – selects elements based on the predicate
public Stream filter(Predicate<T> p)
Stream s = c.stream().filter(i -> (i%2==0) );
- 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);
- 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() );
- Count – Counts the elements in the stream
Integer evenNoCount = c.stream().filter(i -> (i%2==0)).count();
- Sorted – Sorts a collection
List<Integer> sortedNo = noList.stream().sorted().collect( Collectors.toList());
sorted(Comparator comparator ) – sorted( (i,j)-> ( i.compareTo(j) ));
- 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();
- forEach
noList.stream().forEach(System.out::println);
noList.stream().forEach(s- > S.O.P(s));
- toArray
Integer ar[] = noList.stream().toArray(Integer[]::new);
- 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.
- LocalDate
- LocalTime
- LocalDAteTime
- ZoneId
- 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]