Last updated: November 15, 2020 8:44 PM
Concept
Functional programming is a programming paradigm - a style of building the structure and elements of computer programs.
The flag that you handle function programming: Familiar to use Stream API and lambda expressions, keep the stream idea in mind and use the idea in coding work.
Imperative programming: focus on HOW to do: you must tell the detail steps to describe how to make the work done.
Functional programming: focus on WHAT to do: tell Java what you need.
Lambda return an new instance of a specific type
Consider the following equal codes in imperative and functional programming:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Imperative programming");
}
}).start();
new Thread(() -> System.out.println("Functional programming")).start();
If we refactor these code, there will be
Runnable target =
new Runnable() {
@Override
public void run() {
System.out.println("Imperative programming");
}
};
new Thread(target).start();
Runnable target1 = () -> System.out.println("Functional programming");
new Thread(target1).start();
Or, you can create a object instance to hold the value returned by the lambda.
Object target1 = (Runnable) () -> System.out.println("Functional programming");
new Thread((Runnable) target1).start();
The lambda return a new instance of a specific type : Runnable object name target1.
JDK8 new feature: default method in Interface
The annotation @FunctionalInterface is default annotation(you can emit), but highly recommend to put this annotation above the interface. That can remind you.
@FunctionalInterface
interface InterfaceDemo {
int doubleNum(int i);
default int add(int x, int y) { return x + y; }
}
Different expression of lambda
// with brackets in variable
InterfaceDemo i1 = (i) -> i * 2;
// most common expression
InterfaceDemo i2 = i -> i * 2;
// with type declairtion
InteInterfaceDemorface1 i3 = (int i) -> i * 2;
// expression way
InterfaceDemo i4 =
(int i) -> {
return i * 2;
};
Important function interface
Function<T, R>
Example 1:
interface IMoneyFormat {
String format(int i);
}
class MyMoney {
private final int money;
public MyMoney(int money) {
this.money = money;
}
public void printMoney(IMoneyFormat moneyFormat) {
System.out.println("My Saving: " + moneyFormat.format(this.money));
}
}
public class FunctionInterfaceDemo {
public static void main(String[] args) {
MyMoney mm = new MyMoney(9999999);
mm.printMoney(i -> new DecimalFormat("#,###").format(i));
}
}
Example 1 output is: My Saving: 9,999,999
.
The lambda does not care about the interface’s detail or name. It only concern about the input and output. In example 1, the lambda only need to know the input is an int, the return value is a String.
Base on this, we can refactor the printMoney function to example 2:
public void printMoney(Function<Integer, String> moneyFormat) {
System.out.println("My Saving: " + moneyFormat.apply(this.money));
}
in example 2, we now can delete the interface IMoneyFormat, instead, we can use Function interface which JDK8 provided. The Function interface can support Chain calls.
MyMoney mm = new MyMoney(9999999);
Function<Integer, String> moneyFormat = i -> new DecimalFormat("#,###").format(i);
// Function interface chain calls
mm.printMoney(moneyFormat.andThen(s -> "CAD " + s));
}
The output is My Saving: CAD 9,999,999
.
Predicate
// Predicate interface
Predicate<Integer> predicate = i -> i > 0;
System.out.println(predicate.test(-9));
IntPredicate predicate1 = i -> i > 0;
System.out.println(predicate1.test(-8));
The output is
false
false
The best practice is use IntPredicate.
Consumer
// Consumer interface
Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("Input data");
Method References
The code
Consumer<String> consumer = s -> System.out.println(s);
can refactor to a method reference way:
Consumer<String> consumer = System.out::println;
Reference to a static method
The format is ContainingClass::staticMethodName
.
For example:
class Dog {
private String name = "Andrew";
public static void bark(Dog dog) {
System.out.println(dog + " is barking.");
}
@Override
public String toString() {
return this.name;
}
}
public class MethodRefrenceDemo {
public static void main(String[] args) {
// Reference to a static method
Consumer<Dog> consumer1 = Dog::bark;
Dog dog = new Dog();
consumer1.accept(dog);
}
}
The output is: Andrew is barking.
Reference to an instance method of a particular object
The calling format is containingObject::instanceMethodName
.
Add a non-static method in class Dog.
public int eat(int num) {
System.out.println("eat " + num + " kg food.");
this.food -= num;
return this.food;
}
// Reference to an instance method of a particular object
Function<Integer,Integer> function = dog::eat;
System.out.println("left " + function.apply(2)+" kg");
The output is:
eat 2 kg food.
left 8 kg
The function input and output type is the same, can refactor to:
UnaryOperator<Integer> function = dog::eat;
Further, refactor to:
IntUnaryOperator function = dog::eat;
System.out.println("left " + function.applyAsInt(2)+" kg");
Reference to an instance method of an arbitrary object of a particular type (Class Name Method Reference)
// Class Name Method Reference
BiFunction<Dog, Integer, Integer> eatFunction = Dog::eat;
System.out.println("left " + eatFunction.apply(dog, 2) + " kg.");
Reference to a constructor
// Reference to a constructor
Supplier<Dog> supplier = Dog::new;
System.out.println("Create a new object: " + supplier.get());
// Reference to a constructor with parameter
Function<String, Dog> function1 = Dog::new;
System.out.println("Create a new object: " + function1.apply("New Dog"));
This work is licensed under a Creative Commons Attribution 4.0 International License.