Fork me on GitHub
Xu Lei's Techblog

徐磊的技术博客


  • 首页

  • 标签

  • 分类

  • 归档

  • 公益404

  • 支付Payment

Concurrency 高并发 - Future and CompletableFuture

发表于 2020-01-20

Future class provides a very easy way to execute async operations. As of AIO, Future class manages a seperated thread to do the IO operation asynchronously. CompletableFuture is introduced by JDK 1.8 as the enhanced Future async model. It brings the complex future tasks pipeline with the functional interfaces which are also introduced by JDK 1.8. To understand this blog better, the pre-condition is the knowledge of the functional interfaces of JDK 1.8 Java 8 Features.

Sample: com.snippet.concurrent.future of the Snippets repository in GitHub.

Future
Future is a interface which defines the basic operations to manage the asynchronous task, but Future itself cannot init a thread. Thus, let’s use FutureTask for the demo code. FutureTask implements RunnableFuture, while RunnableFuture is a interface which extends Runnable, Future, so FutureTask has all the Future async task features as well as the threading capability.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class AsyncCallableTask implements Callable<String> {
public String call() throws Exception { ... }
}

AsyncCallableTask task = new AsyncCallableTask();
FutureTask<String> futureTask = new FutureTask<>(task);
new Thread(futureTask).start();
boolean isDone = futureTask.isDone();
while(!isDone) {
System.out.println("isDone: " + isDone + ", waiting 4 seconds for the downstream service call, do something else. "
+ Thread.currentThread().getName());
Thread.sleep(4000);
isDone = futureTask.isDone();
Random r = new Random();
int randomTimeout = r.nextInt(10);
boolean isTimeout = randomTimeout > 8 ? true : false;
if (isTimeout) {
System.out.println("timeout " + randomTimeout + ", cancel job.");
futureTask.cancel(true);
break;
}
}
if (futureTask.isCancelled()) {
System.out.println("cancelled, isDone: " + futureTask.isDone() + ". " + Thread.currentThread().getName());
} else {
System.out.println("isDone: " + futureTask.isDone() + ", result: " + futureTask.get() + ". " + Thread.currentThread().getName());
}

When we initialize the FutureTask, it accepts a Callable task which is the task to be executed asynchronously. Then we create a thread for the FutureTask, and internally it will call the Callable task. futureTask.isDone() checks the current running task status, and if the task is completed, return true. In our use case, if it’s timeout, futureTask.cancel(true) attempts to cancel execution of this task. Finally futureTask.get() fetches result from the Callable task.

CompletableFuture
CompletableFuture is one of implementations of the Future interface. It has been introduced by JDK 1.8, so those methods of CompletableFuture are easily integrated with the functional interfaces. CompletableFuture provide a very easy way to chain those async tasks together as a complex async tasks pipeline.
a. CompletableFuture basic methods

1
2
3
4
5
6
7
8
9
10
11
CompletableFuture<String> futureAsyncTask = CompletableFuture.supplyAsync(() -> {
try {
System.out.println("do something");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "ok";
});
System.out.println(futureAsyncTask.getNow("not_found"));
System.out.println(futureAsyncTask.join());

CompletableFuture.supplyAsync() accepts a Supplier functional interface which defines the task. Once the CompletableFuture is created, the task will be running in a seperated thread. futureAsyncTask.getNow("not_found") fetches the result from the task, and if the task is not completed, return the default result which is the arguments. futureAsyncTask.join() makes the main thread synchronized to wait and fetch the result until the async task is completed. The join() method is similar to the get() method, but join() method does not require to catch the checked Exception which may be throwed in the seperated thread specifically in the main thread. Here is one more example below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
List<String> visitList = Arrays.asList("Lasha", "Linzhi", "Yangzhuoyongcuo", "Yaluzangbujiang");
List<CompletableFuture<String>> futureAsyncTaskList = visitList.stream()
.map((String visit) -> CompletableFuture.supplyAsync(() -> {
Random r = new Random();
int time = (r.nextInt(5) + 1) * 1000;
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
return String.format("visit %s for %d hours, async task %s", visit, time/1000, Thread.currentThread().getName());}))
.collect(Collectors.toList());
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
futureAsyncTaskList.stream().map((CompletableFuture<String> task) -> task.getNow("not_ready")).forEach((String plan) -> System.out.println(plan));

b. Compare with Executors

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ExecutorService es = Executors.newCachedThreadPool();
List<Future<String>> futureList = visitList.stream()
.map((String visit) -> es.submit(() -> {
Random r = new Random();
int time = (r.nextInt(5) + 1) * 1000;
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
return String.format("visit %s for %d hours, async task %s", visit, time/1000, Thread.currentThread().getName());}))
.collect(Collectors.toList());
futureList.stream().map((Future<String> task) -> {
try {
return task.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return null;
}).forEach((String plan) -> System.out.println(plan));

c. CompletableFuture advanced features
As we mentioned earlier, CompletableFuture can build the complex future tasks pipeline.

1
2
3
4
5
6
7
8
9
CompletableFuture<String> futureWhenCompleteTask0 = CompletableFuture.supplyAsync(() -> {
try {
System.out.println("do something futureWhenCompleteTask0");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Singapore";
});

c.1 whenComplete()

1
2
3
4
5
6
7
8
9
10
CompletableFuture<String> futureWhenCompleteTask1 = futureWhenCompleteTask0.whenComplete((String result, Throwable e) -> {
try {
System.out.println("do something futureWhenCompleteTask1");
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println(String.format("now: %s -> future: China", result));
});
System.out.println("futureWhenCompleteTask1 final result: " + futureWhenCompleteTask1.join());

When futureWhenCompleteTask0 stage is completed, taking the result of first stage, the given action of futureWhenCompleteTask1 is getting called. But eventually the futureWhenCompleteTask1 result is still from futureWhenCompleteTask0, and the action of futureWhenCompleteTask1 can be considerd as the additional action after futureWhenCompleteTask0 produces result, because whenComplete() takes a BiConsumer as the action. Console log, now: Singapore -> future: China, futureWhenCompleteTask1 final result: Singapore.
c.2 thenApply()

1
2
3
4
5
6
7
8
9
10
11
CompletableFuture<String> futureWhenCompleteTask2 = futureWhenCompleteTask0.thenApply((String result) -> {
try {
System.out.println("do something futureWhenCompleteTask2");
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println(String.format("now: %s -> future: China", result));
return String.format("now: %s -> future: China", result);
});
System.out.println("futureWhenCompleteTask2 final result: " + futureWhenCompleteTask2.join());

Compare with the whenComplete() method, due to thenApply() taking a java.util.function.Function, thenApply() method returns the computed result. Console log, now: Singapore -> future: China, futureWhenCompleteTask2 final result: now: Singapore -> future: China.
c.3 thenCompose()

1
2
3
CompletableFuture<String> futureWhenCompleteTask3 = futureWhenCompleteTask0.thenCompose((String result) -> 
CompletableFuture.supplyAsync(() -> result + " -> Chengdu"));
System.out.println("futureWhenCompleteTask3 final result: " + futureWhenCompleteTask3.join());

Console log, futureWhenCompleteTask3 final result: Singapore -> Chengdu.
c.4. thenCombine()

1
2
3
CompletableFuture<String> futureWhenCompleteTask4 = futureWhenCompleteTask2.thenCombine(
CompletableFuture.supplyAsync(() -> "-Chengdu"), (String t2, String t4) -> t2 + t4);
System.out.println("futureWhenCompleteTask4 final result: " + futureWhenCompleteTask4.join());

futureWhenCompleteTask4 and futureWhenCompleteTask2 run concurrently, once both of the two tasks are completed, it returns the computed result. Console log, futureWhenCompleteTask4 final result: now: Singapore -> future: China-Chengdu.
c.5 more methods
thenAccept(), allOf() (similar to CountDownLatch or CyclicBarrier), anyOf(), etc.
d. why was CompletableFuture introduced by JDK 1.8?
As mentioned above, CompletableFuture provide a very easy way to build the complex future tasks pipeline such as IO tasks. Its default thread pool is ForkJoinPool which of the pool size is based on CPU cores. CompletableFuture provides multiple methods for pipeline such as whenComplete, thenApply, thenCompose, thenCombine, allOf, anyOf, etc.
e. Additional knowledge, thread pool size
CPU denseness task: thread pool size should be based on CPU cores.
IO intensive task: thread pool size should be based on CPU cores CPU utilization ratio (1 + thread waiting time/thread CPU holding time).
In summary, regarding the IO operations, the default ForkJoinPool cannot get the best performance, and the customized thread pool is recommended.

Concurrency 高并发 - CountDownLatch and CyclicBarrier

发表于 2020-01-19

Use Case 1: checkout of the shopping cart, calling merchant downstream services concurrently to get and confirm the latest bills, once confirm the total amount, calling multiple payment gateway APIs concurrently to get all the available payment approaches with enough balance.
Use Case 2: running race, 3 times, the winner is the one who get the minimum of time in total.

There are some use cases which require the running threads in certain order. CountDownLatch and CyclicBarrier are the tools to allow those threads waiting for each other.

Sample code: com.snippet.concurrent and com.snippet.test.concurrent of the Snippets repository in GitHub.

CountDownLatch
Thread groups are waiting for each other within multiple thread groups.

1
2
3
4
5
6
7
8
9
10
public class Player extends Thread {...}

CountDownLatch latch = new CountDownLatch(4);
new Player(latch, "merchant1").start();
new Player(latch, "merchant2").start();
new Player(latch, "merchant3").start();
new Player(latch, "merchant4").start();
latch.await();

System.out.println("countinue to do something else such as calling payment gateway");

The sample code above shows that it starts 4 threads concurrently, registers a CountDownLatch to count the 4 threads, and sets a barrier until all of the 4 running threads are finished.

1
2
3
4
5
6
7
8
public class Player extends Thread {
...
@Override
public void run() {
...complete the task...
latch.countDown();
}
}

Once each thread completes the task, at the end it calls latch.countDown() to count down or -1 of the CountDownLatch. CountDownLatch will remove the barrier of the thread group when the count of CountDownLatch == 0, then the main thread could continue to execute the rest of the code.

CyclicBarrier
Threads are waiting for each other within a single thread group. It can be recycling used.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private int currentTime = 0;
private int maximumScore = 0;
private boolean isOngoing = true;
private Map<String, Integer> scoreboard = new ConcurrentHashMap<>();

CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () -> {
System.out.println("CyclicBarrier executed, round " + ++currentTime);
isOngoing = true;
if (currentTime >= 4) {
scoreboard.entrySet().stream().sorted((Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) -> o1.getValue() == o2.getValue() ? 0 : (o1.getValue() > o2.getValue() ? -1 : 1)).forEach((Map.Entry<String, Integer> runner) -> {
if (runner.getValue() >= maximumScore) {
maximumScore = runner.getValue();
System.out.println("Announce Final Winner: " + runner.getKey() + ", winning times: " + runner.getValue());
}
});
}
});

Define a CyclicBarrier, here are two parameters of the one of constructors:
a. parties the number of threads that must invoke await before the barrier is tripped.
b. barrierAction the command to execute when the barrier is tripped, or null if there is no action.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
for (int i = 0; i < 3; i++) {
new Thread(()->{
scoreboard.put(Thread.currentThread().getName(), 0);
while (currentTime < 4) {
try {
Thread.sleep(new Random().nextInt(5) * 1000);
System.out.println(Thread.currentThread().getName() + " reached barrier.");
updateWinnerStatus(Thread.currentThread().getName());
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}, "runner" + i).start();
}

// pessimism lock
private synchronized void updateWinnerStatus(String runner) {
if (isOngoing) {
scoreboard.put(runner, scoreboard.get(runner) + 1);
System.out.println("winner: " + runner);
isOngoing = false;
}
}

In each thread, cyclicBarrier.await() will be called. Once the await() of all the 3 threads are called, each thread of those 3 can continue execute the rest of the code. CyclicBarrier is getting reset and it can be resued again.

CountDownLatch vs CyclicBarrier
a. In general, thread groups are waiting for each other, use CountDownLatch; within a group of threads, each thread is waiting for each other, use CyclicBarrier.
b. CountDownLatch is counting –, while CyclicBarrier is counting ++.
c. CountDownLatch can be used when the count reached 0 and it cannot be reset, while CyclicBarrier can be reset each time, reused multiple times.

Xu Lei Showcase - Seamless Super App Prototype

发表于 2018-04-15

(Declare: the Seamless Super App Prototype is my personal project, and it does not have any relationship with PayPal. I will remove the PayPal logo very soon.)
Note: The size of the demo video is about 100MB, so it could be better to watch it in the WiFi network environment. The demo video is tested against the Chrome, Firefox and Safari browers in Mac. You can stop to watch it by operating the video control panel. Demo steps: 1. Scan McDonald’s QR code for seamless payment, 2. Scan Pizza Hut QR code for seamless payment.

Scan to Pay for McDonald’s
Scan to Pay for Pizza Hut
Your browser or system may not support the media. Please use latest version of Chrome, Firefox or Safari in Mac.Your browser or system may not support the media. Please use latest version of Chrome, Firefox or Safari in Mac.

##Concept
Super App
Super App means opening for Integration, Integration and Integration!!!
(Refer to WeChat Little Program concept) The future apps allow users Use and Leave without downloading.
Seamless Authentication User Experience
Seamless Login for the Authenticated User. For example, the users logged in their mobile app, they can use the system anywhere such as mobile, web app at laptop, and so forth.
Blur the lines of payment between online and offline.
Blur the lines between different systems.

##Design
logic view
physical view

##Security

Security Concern
Security Challenge
Solution
QR Code The QR code is able to contain any information, valid or invalid. It will be risky to redirect user to the invalid site. Tokenized QR Code signed by the payment gateway (here is JWT, Json Web Token). HMAC digital signature within the token ensures token can be verified by issuer only, and no one is able to change the token information as only the payment gateway hold the HMAC signature key.
Payment Process Merchants should not play the role to conduct the payment process. For example, user buy $10 product, merchant might internally ask the payment gateway to charge the user $100 intently or by mistake. Merchant site natively direct the payment process to the payment gateway mobile app to allow users confirm the payment within the payment app. (Android prototype tech challenge: child thread call redirect to main thread). User is able to make a final verification of their payment detail before clicking the pay button. Payment gateway will fully control the payment process securely.
Payment Status Notification For the merchant perspective, it’s not secure to allow any client to notify and change the payment status. By design, the merchant portal just allows payment gateway as the payment authority to notify the payment result. Merchant will generate the receipt based on the payment status for the specific order.

##Extension
a. Seamless authentication. I am an authenticated user at the mobile app. I should be able to seamlessly get authenticated at my iPad app, Desktop Web System wherever I am required to be authenticated. SSO, single sign on, to be the legal entity in the system cross platforms, QR code can bridge the gap.
b. Seamless authorization with payment. Merchants generate the bill online and users just need to open the payment app, Scan the QR code on the web page and Pay. Blur the lines between the payment gateway and the merchant site. Username and Password are old days for authorization and authentication. Inconvenient and high risk to enter the credential through merchant site due to phishing sites.
c. Order ahead. For those used merchant tokens, they can be stored as the history or shortcut for the next time order ahead capability.
d. Default integration with verified government sites. User can easily pay for their tax, water bill, phone bill, and so forth. Scenario payment to change user’s daily lives, and increase DAU (Daily Active User).
e. Integration with loyalty systems, restaurant bookings, advance paying for carpark to reserve it before you reaching the place, paying for season parking, etc.

Json Web Token (JWT)

发表于 2018-03-12

Json Web Token (JWT) plays a signifiant role as the secure identity, especially in the distributed architecure such as SOA (Service Oriented Architecture) systems. Here I would like to introduce JWT by the following steps. 1. Brief introduction of JWT, 2. Benefit of JWT, 3. Sample implementation to use JWT in Java.

a. Brief introduction of JWT
I would like to introduce JWT briefly. JWT is a secure information transmitting way based on the open standard (RFC 7519) by a json object. It contains 3 parts, Header, Payload, Signature seperated by dots (.). Therefore, a typical JWT token looks like the following format. Header.Payload.Signature. Let’s break down to get the details of each part.
Header

1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

The header generally consists of two parts: the type of the token, and the hashing algorithm being used. Then it will be getting base64 encoded to form the header part of the JWT.
Note: Encoding is the public known way to tranfer the data from one format to another format. Therefore, the header is getting base64 encoded, which means the header part of JWT can be considered as the plain text. Thus, we should NOT store the sensitive information in the JWT header.
Payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"sub": "third party authorization",
"iss": "auth0",
"exp": "1520239160663"
"customclaims":
{
"id": 33,
"userName": "lei",
"role":
{
"roleName": "admin",
"privileges": ["read", "write", "delete"]
}
}
}

Payload contains three types of claims: registered, public, and private claims. They are statements about an entity. Registered claims are a set of predefined claims with three characters claim name such as iss (issuer), exp (expiration time), sub (subject), aud (audience). Public claims can be defined at will by those using JWTs. Private claims are the custom claims for information sharing between parties like “customclaims” in the sample above. Then it will be getting base64 encoded to form the payload part of the JWT.
Note: Similar to JWT header, the payload is getting base64 encoded, which means the payload part of JWT can be considered as the plain text. Thus, we should NOT store the sensitive information in the JWT payload.
Signature

1
2
3
4
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

Use the algorithm specified in the header to sign the encoded header, the encoded payload with a secret. The signature is used to verify the message wasn’t changed along the way. In the sample, we use HMAC SHA256 algorithm.
Note: Signature typically takes use of hash function which is one way encryption. It cannot be reverted back to get the secret by the signature. Any information in the token is getting changed, then it will result in generating different signature for the failed verification. Thus, the secret is very important for the verification, and it will be stored in the server side only securely. During verification, the server will re-caculate the singature by first two parts of the JWT, the base64 encoded header and the base64 encoded payload, with the secret which is only known by the server, then compare the re-caculated signature with the sigature of the JWT.
Json Web Token (JWT)

1
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhdXRoMCIsInN1YiI6InRoaXJkIHBhcnR5IGF1dGhvcml6YXRpb24iLCJjdXN0b21jbGFpbXMiOiJ7XCJpZFwiOjMzLFwidXNlck5hbWVcIjpcImxlaVwiLFwicm9sZVwiOntcInJvbGVOYW1lXCI6XCJhZG1pblwiLFwicHJpdmlsZWdlc1wiOltcInJlYWRcIixcIndyaXRlXCIsXCJkZWxldGVcIl19fSIsImV4cCI6MTUyMDcxNTcwODAzOH0.om5ZNtXDtsp6IUOErHcrE1kykiNEebjA55QCmxdWxnk

Therefore, the JWT like the sample above is the combination of the base64 encoded header, the base64 encoded payload, and the signiture signed with a secret.
Again, do note that with signed tokens, all the information contained within the token is exposed to users or other parties, even though they are unable to change it. This means you should not put secret information within the token.
As the general flow to use the JWT token, the user will login with the credential to get a JWT token, then the user can take use of the JWT token to access the protected resources. To find out more information about Json Web Token (JWT) or try out JWT token generation, decoding and verification online, browse the jwt.io website.

b. Benefit of JWT
1 High efficiency. The server do not need to have the database access for authentication, and instead, it just need to re-caculate the signature in memory for verification.
2 Cross-Origin Resource Sharing. JWT token provides the stateless way for authentication, so it could not be an issue to access the resources cross multiple services as it doesn’t use cookie and session, especially for the distributed architecture systems.
3 Light weight. JWT is a light weight format transmitting way between parties by the http request (mostly in the http header). It can exchange the necessary information within the JWT payload.

c Sample implementation to use JWT in Java
Here we use auth0 library for the implementation.
pom.xml

1
2
3
4
5
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>2.2.1</version>
</dependency>

Signing with a secret, return the JWT token as a String

1
2
3
4
5
6
7
8
9
10
11
public static<T> String sign(T object, long maxAge) throws Exception {
final JWTSigner signer = new JWTSigner(SECRET);
final Map<String, Object> claims = new HashMap<String, Object>();
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(object);
claims.put(CUSTOM_CLAIMS, jsonString);
claims.put(EXPIRATION_TIME, System.currentTimeMillis() + maxAge);
claims.put(ISSUER, "auth0");
claims.put(SUBJECT, "third party authorization");
return signer.sign(claims);
}

Unsigning with the same secret by signature verification

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static<T> T unsign(String jwt, Class<T> classT) throws Exception {
final JWTVerifier verifier = new JWTVerifier(SECRET);
final Map<String,Object> claims= verifier.verify(jwt);
if (claims.containsKey(EXPIRATION_TIME) && claims.containsKey(ISSUER)
&& claims.containsKey(SUBJECT) && claims.containsKey(CUSTOM_CLAIMS)) {
long exp = (long) claims.get(EXPIRATION_TIME);
System.out.println("EXPIRATION_TIME: " + sdf.format(new Date(exp)) + ", ISSUER: "
+ (String) claims.get(ISSUER) + ", SUBJECT: " + (String) claims.get(SUBJECT));
if (exp > System.currentTimeMillis()) {
String privateClaims = (String) claims.get(CUSTOM_CLAIMS);
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(privateClaims, classT);
}
}
return null;
}

Find more about the code snippets of JWT under the package com.snippet.jwt of the Snippet repository in GitHub.

The code snippets demo for the JWT token generation and verification.

1 login to get the JWT token within hateoas link
request of token generation
response of token generation
2 verify the token by getting the user information
token verification

Java 8 Features

发表于 2018-02-04

I would like to introduce the Java 8 features in a systematic way. Basically, Java 8 introduces the following new features. 1. functional interface, 2. lambda expression, 3. 6 types of pre-defined functional interface in Java 8, 4. stream API, 5. default method, 6. effective final, 7. method reference.

a. Functional Interface
Functional Interface is a single abstract method interface.

1
2
3
4
5
@FunctionalInterface
public interface Payment {
public void pay(ShoppingCart shoppingCart,
Predicate<Integer> isReceiptRequired);
}

The annotation FunctionalInterface checks against the class to comply with the rule of functional interface that it is a single abstract method interface. If we introduce another abstract method in the class, there will be compile error.

b. Lambda Expression

1
2
3
4
5
6
7
public void checkout() {
Payment cashPay = (ShoppingCart shoppingCart,
Predicate<Integer> isReceiptRequired) -> {
System.out.println("Cash is NOT support");
};
cashPay.pay(getShoppingCart(), (Integer amount) -> false);
}

Lambda Expression can be only used for Functional Interface. It uses the syntax -> to indicate the method body of the single abstract method interface. The left side of -> is the method parameters, and the right side of -> is the method implementation.

c. 6 Types of Functional Interface in Java 8
They are all under the package java.util.function.
A. Predicate, (I) -> boolean, replace if else
B. Function, (I) -> R, transformer, one type to another type
C. Consumer, (I) -> void, consumer the input data
D. Supplier, () -> new (), factory pattern
E. BinaryOperator, (I, I) -> I, reducers, accumulation
F. UnaryOperator, (I) -> I

1
2
3
4
5
6
7
8
9
10
11
12
13
public default Optional<Integer> calculateTotalAmount(ShoppingCart shoppingCart) {
Predicate<Product> filterCondition = (Product p) ->
StringUtils.isNotBlank(p.getId()) && p.getPrice() > 0;
Function<Product, Integer> mapToPrice = (Product p) -> p.getPrice() * p.getQuantity();
BinaryOperator<Integer> reduceCheckoutAmount =
(Integer price0, Integer price1) -> price0 + price1;
/**
* stream (for Collections) 1. filter, 2. map, 3. reduce.
* fork join pool with concurrent processing
*/
return shoppingCart.getProducts().stream().parallel()
.filter(filterCondition).map(mapToPrice).reduce(reduceCheckoutAmount);
}

Here we have 3 examples for Predicate, Function, BinaryOperator functional interfaces respectively. They are all functional interfaces, so we can represent the method body as the lambda expression.
Predicate is a functional interface which has a single method of return type boolean. The method implementation StringUtils.isNotBlank(p.getId()) && p.getPrice() > 0 will be either true or false. By the way, the return key word can be omitted for the lambda expression approach.
Function is a functional interface which has a single method of one type input, the different type of output. (Product p) -> p.getPrice() * p.getQuantity();, input is Product, but output is String which is different from the input type.
BinaryOperator is a functional interface which has a single method of two inputs and one output with the same type. It is similar to the reduce of the map-reduce concept, generally for accumulation. (Integer price0, Integer price1) -> price0 + price1;, two integers as the input, the output is an integer of the sum of the two input integers.

d. Stream API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public default String printReceipt(ShoppingCart shoppingCart) {
StringBuilder receipt = new StringBuilder();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
for(Category category : Category.values()) {
receipt.append("$$$$$$$$$").append(category.name()).append("$$$$$$$$$").append("\n");
shoppingCart.getProducts().stream()
.filter((Product p) -> p.getCategory() == category).sorted((Product o1, Product o2)
-> o1.getId().equals(o2.getId()) ? 0 : (o1.getPrice() > o2.getPrice() ? 1 : -1))
.forEach((Product p) -> receipt.append(p.getId()).append(" ").append(p.getName())
.append(" ").append("X").append(p.getQuantity()).append(" ").append("SGD")
.append(p.getPrice()).append("\n"));
}
receipt.append("^^^^^^^^^^^^^^^^^^^^").append("\n");
receipt.append("Checkout Total Amount: ").append(calculateTotalAmount(shoppingCart).get())
.append(" SGD").append("\n");
receipt.append("Transaction Time: ").append(sdf.format(new Date())).append("\n");
return receipt.toString();
}

For collections, Stream API allows to iterate each item of the collection, and enqueue each item within the pipeline to process, such as filter, for-each, map-reduce.
shoppingCart.getProducts().stream(), it iterates the list of Product as the stream pipeline, moreover, parallel() will take use of the fork join pool for concurrent processing, then calls the corresponding stream API (the functional interfaces play the role like the anonymous inner class, but they are different in their underline behaviours), filter which input is a Predicate, (see the code in section c) map which input is a Function, reduce which input is a BinaryOperator, and so forth.

e. Default Method
The code snippet in section c and section d has the method with the default key word. Before Java 8 release, in the interface, we can just define the abstract methods as the contract, and the class which implements the interface has the concrete implementation of the interface method.
Java 8 introduces the default method in the interface class for the default implementation.

f. Effective Final

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void checkout() {
System.out.println("---------Checkout with Cash---------");
int cash = 10;
Payment cashPay = (ShoppingCart shoppingCart, Predicate<Integer> isReceiptRequired) -> {
/**
* cannot change, effective final;
* local variable for lambda expression, auto change to final variable.
*
* cash = 2000;
*/
System.out.println("Cash is NOT support");
};
cashPay.pay(getShoppingCart(), (Integer amount) -> false);
System.out.println();
System.out.println("---------Checkout with Cashlessa 8---------");
payment.pay(getShoppingCart(), this::isAskingReceipt);
}

Local variable for lambda expression, it will be automatically changed to final variable.
In this example, the local variable int cash cannot be modified within the lambda expression (cash = 2000) as it is changed to the final variable automatically. Otherwise, there will be compile error in line 9 (code snippet in section f).

g. Method Reference

1
2
3
4
5
6
7
8
9
10
public void checkout() {
...
/**
* method reference
*/
payment.pay(getShoppingCart(), this::isAskingReceipt);
}
private boolean isAskingReceipt(Integer amount) {
return amount > 10;
}

In java 8, we can use the syntax :: to refer a method body.
In this example, the second input of the pay method is a Predicate. Here we use the method reference to indicate the method body which is the implemetation of the Predicate.

In summary, Java 8 provides us some flexible ways to return the complex logic within one line, and more significant, it introduces the concept of Functional Interface for the functional programming pattern.

The code snippets above are under the package com.snippet.java8.feature of the Snippets repository in GitHub. Feel free to create any GitHub issues against the repository if you have any questions or you want to correct the code snippet errors based on your understanding. Welcome.

Hello 你好 Hexo

发表于 2018-01-14 | 分类于 blog

使用Hexo搭建博客,很赞哦!

Hello World

发表于 2018-01-13

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

Xu Lei

Xu Lei

Internet|Software|Banking|Security, Java Latest Features, Spring Suite, Cryptography, Payments...

7 日志
1 分类
14 标签
© 2020 Xu Lei
本站访客数:
|
由 Xu Lei GitHub 强力驱动
|
主题 — NexT.Pisces v5.1.4