2023/7/10

UUID

UUID: Universally Unique Identifier 通用唯一識別碼,是 128 位元的識別碼,被開源軟體基金會 (Open Software Foundation, OSF) 的組織使用在分散式計算環境 (Distributed Computing Environment, DCE)。用途是讓分散式系統中的元素,能有唯一的辨識資訊,不需要透過一個集中的系統辨識資訊的唯一性。2005年7月,RFC4122 定義了 UUID 的 URN name space,並制定 UUID 的規格。

結構

UUID 的結構為 16 個 8 bits 的 byte array,總共是 32 個 16 進位的數字,以 "8-4-4-4-12" 的形式表示

xxxxxxxx-xxxx-Bxxx-Axxx-xxxxxxxxxxxx

Variant

其中 A 代表 UUID variants 欄位,目前的規格使用 variant 2

Name binary bits description
Apollo NCS Variant 0XXX 1988 年 Apollo 系統使用
OSF DCE Variant 10XX RFC 4122/DCE 1.1 UUIDs
Microsoft COM/DCOM Variant 110X reserved, Microsoft Corporation backward compatibility
Reserved Variant 未定義 所有未定義的格式

B 代表 Version,以一整個 byte 的值代表 UUID 的版本

目前 Version 有 1,2,3,4,5 五種,最常使用的是隨機生成的 Version 4。

  • Time-Based (UUIDv1)
  • DCE Security (UUIDv2)
  • Name Based (UUIDv3 and UUIDv5)
  • Random (UUIDv4)

Version

Version 1

根據時間, Version, Variant 產生前四個部分的值,根據 Mac Address 產生 Node。這個版本的 UUID 優點是依照時間順序產生,因此排序速度比較快,但也比較有可能會產生相同的 UUID。

如果是真實的 MAC Address,可以追蹤到原始產生 UUID 的電腦,但這部分也可以用亂數產生。

98a956f7-0188-1000-88fc-6650c08fc392

98a956f7: Low Time
0188: Mid Time
1000: High Time and Version
88fc: Clock Sequence and Variant
6650c08fc392: Node

Version 2

比較少被使用,規格定義於 DCE 1.1: Authentication and Security Services - Privilege (Authorisation) Services

Version 3, 5

根據 namespace 及唯一的 name 產生,當輸入的字串固定時,產生的 UUID 也會是固定的。

Version 3 是使用 MD5 hash of the name and namespace

Version 5 是使用 SHA-1 hash of the name and namespace,當 hash 結果長度太長時,就直接 truncated

Version 4

完全是亂數產生的,有超過 5.3 x 10^36 個 UUIDs,這是最常被使用的版本

Java Code

import java.util.Random;
import java.util.UUID;

public class UUIDTest {
    public static void main(String... args) {
        v1();
        v3();
        v4();
    }

    private static void uuid_field(UUID uuid1) {
        int version = uuid1.version();
        int variant = uuid1.variant();
        int clockSequence = -1;
        long node = -1;
        long timestamp = -1;
        try {
            clockSequence = uuid1.clockSequence();
            node = uuid1.node();
            timestamp = uuid1.timestamp();
        } catch(UnsupportedOperationException e) {

        }
        System.out.println("uuid="+uuid1+", version="+version+", variant="+variant+", clockSequence="+clockSequence+", node="+node+", timestamp="+timestamp);
    }

    private static void v4() {
        UUID uuid1 = UUID.randomUUID();
        UUID uuid2 = UUID.randomUUID();
        UUID uuid3 = UUID.randomUUID();

        System.out.println("UUID v4");
        uuid_field(uuid1);
        uuid_field(uuid2);
        uuid_field(uuid3);
        System.out.println("");
    }

    private static void v3() {
        String s1 = "Test String";
        String s3 = "Test String3";
        UUID uuid1 = UUID.nameUUIDFromBytes(s1.getBytes());
        UUID uuid2 = UUID.nameUUIDFromBytes(s1.getBytes());
        UUID uuid3 = UUID.nameUUIDFromBytes(s3.getBytes());

        System.out.println("UUID v3");
        uuid_field(uuid1);
        uuid_field(uuid2);
        uuid_field(uuid3);
        System.out.println("");
    }
    // UUID.java 裡面的 v3 實作
//    public static UUID nameUUIDFromBytes(byte[] name) {
//        MessageDigest md;
//        try {
//            md = MessageDigest.getInstance("MD5");
//        } catch (NoSuchAlgorithmException nsae) {
//            throw new InternalError("MD5 not supported", nsae);
//        }
//        byte[] md5Bytes = md.digest(name);
//        md5Bytes[6]  &= 0x0f;  /* clear version        */
//        md5Bytes[6]  |= 0x30;  /* set to version 3     */
//        md5Bytes[8]  &= 0x3f;  /* clear variant        */
//        md5Bytes[8]  |= 0x80;  /* set to IETF variant  */
//        return new UUID(md5Bytes);
//    }

    private static void v1() {
        UUID uuid1 = generateUUID_V1();
        UUID uuid2 = generateUUID_V1();
        UUID uuid3 = generateUUID_V1();

        System.out.println("UUID v1");
        uuid_field(uuid1);
        uuid_field(uuid2);
        uuid_field(uuid3);
        System.out.println("");
    }

    public static UUID generateUUID_V1() {
        long most64SigBits = get64MostSignificantBitsForVersion1();
        long least64SigBits = get64LeastSignificantBitsForVersion1();
        return new UUID(most64SigBits, least64SigBits);
    }

    private static long get64LeastSignificantBitsForVersion1() {
        Random random = new Random();
        long random63BitLong = random.nextLong() & 0x3FFFFFFFFFFFFFFFL;
        long variant3BitFlag = 0x8000000000000000L;
        return random63BitLong | variant3BitFlag;
    }

    private static long get64MostSignificantBitsForVersion1() {
        final long currentTimeMillis = System.currentTimeMillis();
        final long time_low = (currentTimeMillis & 0x0000_0000_FFFF_FFFFL) << 32;
        final long time_mid = ((currentTimeMillis >> 32) & 0xFFFF) << 16;
        final long version = 1 << 12;
        final long time_hi = ((currentTimeMillis >> 48) & 0x0FFF);
        return time_low | time_mid | version | time_hi;
    }
}

執行結果

UUID v1
uuid=98a956f7-0188-1000-88fc-6650c08fc392, version=1, variant=2, clockSequence=2300, node=112497014064018, timestamp=1686188414711
uuid=98a956fb-0188-1000-b9c6-84e9d3083144, version=1, variant=2, clockSequence=14790, node=146139802775876, timestamp=1686188414715
uuid=98a956fb-0188-1000-9bdc-540d86968ed3, version=1, variant=2, clockSequence=7132, node=92417069321939, timestamp=1686188414715

UUID v3
uuid=bd08ba3c-982e-3ad7-a860-2536fb8e1184, version=3, variant=2, clockSequence=-1, node=-1, timestamp=-1
uuid=bd08ba3c-982e-3ad7-a860-2536fb8e1184, version=3, variant=2, clockSequence=-1, node=-1, timestamp=-1
uuid=c1a59283-7a55-3e83-b9ed-864b5367685b, version=3, variant=2, clockSequence=-1, node=-1, timestamp=-1

UUID v4
uuid=2de4d27e-9abc-4b9d-83bd-b721ef6b2882, version=4, variant=2, clockSequence=-1, node=-1, timestamp=-1
uuid=e82fca87-acd0-4000-8a4f-f4d7001d56a4, version=4, variant=2, clockSequence=-1, node=-1, timestamp=-1
uuid=fb3a6890-57e3-4a13-a238-ef6f3de63fbe, version=4, variant=2, clockSequence=-1, node=-1, timestamp=-1

References

通用唯一辨識碼 - 維基百科,自由的百科全書

# 閒談軟體架構:UUID

# 閒談軟體架構:UUID 之三部曲

Guide to UUID in Java | Baeldung

2023/6/26

Java Stream API

Collection 是程式中存放於內部記憶體的資料結構,而 Stream 是一種資料流 pipeline,可以依照需要使用這些內部記憶體的資料。

Stream 的 operation 有兩類:intermediate 或是 terminal,intermediate opertaion 會回傳 stream,故能夠以 chain 的方式依序串接多個 intermediate operation,針對資料進行運算。terminal operation 會於計算後,回傳某個類別的結果。

Stream operation 有循序跟平行化處理兩種,平行化的 operations 能夠在多個 thread 同時運算,能充分運用多核心處理器,加速運算的過程。

Stream 的資料來源是 java.util.Collection,例如 List, Set。Map 無法直接支援,但能分別對 keys, values 或 entries 建立 Stream。

建立 Stream

從 data collections 產生 Stream 的方法

    private static void createStream() {
        // Stream.of
        System.out.println("Stream.of");
        Stream<Integer> stream = Stream.of(1,2,3,4,5,6,7,8,9);
        stream.forEach(p -> System.out.print(p + " "));
        System.out.println("");

        // Stream.of(array)
        System.out.println("Stream.of(array)");
        Stream<Integer> stream2 = Stream.of( new Integer[]{1,2,3,4,5,6,7,8,9} );
        stream2.forEach(p -> System.out.print(p + " "));
        System.out.println("");

        // List.stream()
        System.out.println("List.stream()");
        List<Integer> list = new ArrayList<Integer>();
        for(int i = 1; i< 10; i++){
            list.add(i);
        }
        Stream<Integer> stream3 = list.stream();
        stream3.forEach(p -> System.out.print(p + " "));
        System.out.println("");

        // Stream.generate() or Stream.iterate()
        // 透過 generator function 產生 Stream elements
        // 以 limit() 限制 element 個數
        System.out.println("Stream.generate()");
        Stream<Integer> randomNumbers = Stream
                .generate(() -> (new Random()).nextInt(100));
        randomNumbers.limit(10).forEach(s -> System.out.print(s + " "));
        System.out.println("");

        //  Stream of String chars or tokens
        System.out.println("Stream of String chars");
        IntStream stream5 = "12345_abcdefg".chars();
        stream5.forEach(p -> System.out.print(p + " "));
        System.out.println("");
        System.out.println("Stream of tokens");
        Stream<String> stream6 = Stream.of("A$B$C".split("\\$"));
        stream6.forEach(p -> System.out.print(p + " "));
        System.out.println("");
    }

Stream Collector

透過 Collector 可將 Stream 裡面的 elements 再轉換為 Collection

    private static void streamCollector() {
        // List 轉換為 Stream
        List<Integer> list = new ArrayList<Integer>();
        for(int i = 1; i< 10; i++){
            list.add(i);
        }
        Stream<Integer> stream = list.stream();

        // 用 collect 再將 Stream 轉換為 List
        System.out.println("collect");
        List<Integer> evenNumbersList = stream.filter(i -> i%2 == 0)
                .collect(Collectors.toList());
        System.out.print(evenNumbersList);
        System.out.println("");

        // 用 collect 再將 Stream 轉換為 Array
        System.out.println("toArray");
        Stream<Integer> stream2 = list.stream();
        Integer[] evenNumbersArr = stream2.filter(i -> i%2 == 0).toArray(Integer[]::new);
        for (Integer i : evenNumbersArr) {
            System.out.print(i + " ");
        }
        System.out.println("");
    }

Operations

Intermediate Operations

method description
filter() 條件過濾
map() 對每個 elements 執行某個運算
flatMap() 將多個Stream連接成一個Stream
distinct() 刪除相同的 elements
sorted() 排序
peek() 用在debug
limit() 限制回傳的 elements 數量
skip() 略過前面幾個 elements

java sample

    private static void intermediateOperation() {
        List<String> names = new ArrayList<>();
        names.add("Allen");
        names.add("George");
        names.add("Anderson");
        names.add("Danny");

        // filter 條件過濾
        System.out.println("filter");
        names.stream().filter((s) -> s.startsWith("A"))
                .forEach(n -> System.out.print(n + " "));
        System.out.println("");
        System.out.println("");

        // map  對每個 elements 執行某個運算
        System.out.println("map");
        names.stream().filter((s) -> s.startsWith("A"))
                .map(String::toUpperCase)
                .forEach(n -> System.out.print(n + " "));
        System.out.println("");
        System.out.println("");

        // flatMap  將多個Stream連接成一個Stream
        List<String> names2 = new ArrayList<>();
        names2.add("Jim");
        System.out.println("flatMap");
        List<String> flapMapStream = Stream.of(names, names2).flatMap(u -> u.stream()).collect(Collectors.toList());
        System.out.println(flapMapStream);
        System.out.println("");
        System.out.println("");

        // distinct
        // 刪除相同的 elements
        List<String> names3 = new ArrayList<>();
        names3.add("Jim");
        names3.add("Jimmy");
        names3.add("Jim");
        System.out.println("distinct");
        names3.stream()
                .distinct()
                .forEach(n -> System.out.print(n + " "));
        System.out.println("");
        System.out.println("");

        //sorted  排序
        System.out.println("sorted");
        names.stream()
                .sorted()
                .forEach(n -> System.out.print(n + " "));
        System.out.println("");
        System.out.println("");

        // peek 用在debug
        System.out.println("peek");
        List res = names.stream().filter((s) -> s.startsWith("A"))
                .peek(e -> System.out.println("filter res: " + e))
                .map(String::toUpperCase)
                .peek(e -> System.out.println("map res: " + e))
                .collect(Collectors.toList());
        System.out.println(res);
        System.out.println("");
        System.out.println("");

        // limit
        // 限制回傳的 elements 數量
        System.out.println("limit");
        List res2 = names.stream().filter((s) -> s.startsWith("A"))
                .map(String::toUpperCase)
                .limit(1)
                .collect(Collectors.toList());
        System.out.println(res2);
        System.out.println("");
        System.out.println("");

        // skip
        // 略過前面幾個 elements
        System.out.println("skip");
        List res3 = names.stream().filter((s) -> s.startsWith("A"))
                .map(String::toUpperCase)
                .skip(1)
                .collect(Collectors.toList());
        System.out.println(res3);
        System.out.println("");
        System.out.println("");
    }

Terminal Operations

method description
forEach 對 steam 所有 elements 進行運算
forEachOrdered 會依照原本 stream 元素的順序輸出,平行化 stream 後,順序也會固定
toArray 轉換為 Array
reduce 把一個Stream的所有元素按照聚合函數聚合成一個結果
collect 透過 Collector 可將 Stream 裡面的 elements 再轉換為 Collection
min 找最小元素
max 找最大元素
count 元素數量
anyMatch 檢查是否有任何一個元素,滿足給定的條件
allMatch 檢查是否有所有的元素,都滿足給定的條件
nonMatch 檢查是否沒有任何一個元素,滿足給定的條件
findFirst 回傳 stream 的第一個元素
findAny 回傳 stream 的任一個元素
    private static void terminalOperation() {
        List<String> names = new ArrayList<>();
        names.add("Allen");
        names.add("George");
        names.add("Anderson");
        names.add("Danny");

        // forEach
        System.out.println("forEach");
        names.stream().forEach(n -> System.out.print(n + " "));
        System.out.println("");
        System.out.println("");

        // forEachOrdered
        System.out.println("1. Sequential Stream");
        System.out.println("1.1 forEach 會依照原本的順序輸出");
        names.stream().forEach(n -> System.out.print(n + " "));
        System.out.println("");
        System.out.println("1.2 forEachOrdered 會依照原本的順序輸出");
        names.stream().forEachOrdered(n -> System.out.print(n + " "));
        System.out.println("");
        System.out.println("2. Parallel Stream");
        System.out.println("2.1 forEach 輸出順序不固定");
        names.stream().parallel().forEach(n -> System.out.print(n + " "));
        System.out.println("");
        System.out.println("2.2 forEachOrdered 會依照原本的順序輸出");
        names.stream().parallel().forEachOrdered(n -> System.out.print(n + " "));
        System.out.println("");
        System.out.println("");

        // toArray
        System.out.println("toArray");
        String[] nameArray = names.stream().toArray(String[]::new);
        for( String n: nameArray) {
            System.out.print(n+" ");
        }
        System.out.println("");
        System.out.println("");

        // reduce 傳入 BinaryOperator
        System.out.println("reduce");
        int sum = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).reduce(0, (s, n) -> s + n);
        int mul = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).reduce(1, (m, n) -> m * n);
        System.out.println("累加 sum="+sum);
        System.out.println("連乘 mul="+mul);
        System.out.println("");
        System.out.println("");

        // collect
        System.out.println("collect");
        List<String> filterNames = names.stream().filter((s) -> s.startsWith("A"))
                .collect(Collectors.toList());
        for( String n: filterNames) {
            System.out.print(n+" ");
        }
        System.out.println("");
        System.out.println("");

        // min, max
        System.out.println("min, max");
        Optional<String> minName = names.stream()
                .min(Comparator.comparing(String::valueOf));
        if( minName.isPresent() ) {
            System.out.println("min = "+minName.get());
        }
        Optional<String> maxName = names.stream()
                .max(Comparator.comparing(String::valueOf));
        if( maxName.isPresent() ) {
            System.out.println("max = "+maxName.get());
        }
        System.out.println("");
        System.out.println("");

        // count
        System.out.println("count");
        long count = names.stream().filter((s) -> s.startsWith("A"))
                .count();
        System.out.println("count="+count);
        System.out.println("");
        System.out.println("");

        // anyMatch allMatch noneMatch
        System.out.println("anyMatch allMatch noneMatch");
        boolean anyMatch = names.stream()
                .anyMatch(n -> Character.isUpperCase(n.charAt(0)));
        boolean allMatch = names.stream()
                .allMatch(n -> Character.isUpperCase(n.charAt(0)));
        boolean noneMatch = names.stream()
                .noneMatch(n -> Character.isUpperCase(n.charAt(0)));
        System.out.println("anyMatch="+anyMatch);
        System.out.println("allMatch="+allMatch);
        System.out.println("noneMatch="+noneMatch);
        System.out.println("");
        System.out.println("");

        // findFirst  findAny
        System.out.println("findFirst  findAny");
        Optional<String> findFirst = names.stream().filter((s) -> s.startsWith("A"))
                .findFirst();
        if( findFirst.isPresent() ) {
            System.out.println("findFirst = "+findFirst.get());
        }
        Optional<String> findAny = names.stream().filter((s) -> s.startsWith("A"))
                .findAny();
        if( findAny.isPresent() ) {
            System.out.println("findAny = "+findAny.get());
        }
        System.out.println("");
        System.out.println("");
    }

parallel

parallelStream() 及 stream().parallel() 可將 stream 平行化

    private static void parallel() {
        // List.stream()
        // List.parallelStream()
        System.out.println("List.stream()");
        List<Integer> list = new ArrayList<Integer>();
        for (int i = 1; i < 10; i++) {
            list.add(i);
        }
        Stream<Integer> stream3 = list.stream();
        stream3.forEach(p -> System.out.print(p + " "));
        System.out.println("");
        System.out.println("List.parallelStream()");
        Stream<Integer> stream4 = list.parallelStream();
        stream4.forEach(p -> System.out.print(p + " "));
        System.out.println("");

        // stream().parallel()
        System.out.println("stream().parallel()");
        Stream<Integer> stream5 = list.stream().parallel();
        stream5.forEach(p -> System.out.print(p + " "));
        System.out.println("");
    }

References

[JAVA] Java 8 Streams API

Java Stream API (with Examples) - HowToDoInJava

Understanding Java 8 Streams API - amitph

10 Examples of Stream API in Java 8 - count + filter + map + distinct + collect() Examples | Java67

Java 8 Stream | 菜鸟教程

Difference Between parallelStream() and stream().parallel() in Java | Baeldung

2023/6/19

Java CompletableFuture

CompletableFuture 是 Java 8 在 java.util.concurrent 中新增的非同步處理類別

該類別主要有兩種執行非同步工作的方法:runAsync, supplyAsync,差別是有沒有需要回傳結果,在沒有 executor 的狀況下,會使用預設的 thread pool

public static CompletableFuture<Void> runAsync(Runnable runnable)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static CompletableFuture<Void> runAsync(Runnable runnable,Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

java sample

    private static void test1() {
        try {
            CompletableFuture<String> hello = CompletableFuture.supplyAsync(() -> {
                System.out.println("supplyAsync START");
                System.out.println("say supplyAsync Hello World");
                return "supplyAsync Hello World";
            });
            System.out.println(hello.get());

            CompletableFuture<Void> world = CompletableFuture.runAsync(() -> {
                System.out.println("runAsync START");
                System.out.println("say runAsync Hello World");
                return ;
            });

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        // console 結果
//        supplyAsync START
//        say supplyAsync Hello World
//        supplyAsync Hello World
//        runAsync START
//        say runAsync Hello World

    }

用 Executors 產生自訂的 thread pool

ExecutorService threadPool = Executors.newFixedThreadPool(10);
CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> System.out.println("runAsync"), threadPool);
CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(() -> "supplyAsync", threadPool);

java sample


處理 exception

可在工作處理完成後,取得結果,在處理過程中,遇到 exception 時,可以攔截 exception 並執行特定的工作

相關的 method 是

CompletableFuture<T> whenComplete(BiConsumer<? super T, ? super Throwable> action)
CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action)
// catch exception
CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn)

// use your own thread pool
CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action, Executor executor)

sample code:

// 在工作處理完成後,取得結果,在處理過程中,遇到 exception 時,可以攔截 exception 並執行特定的工作
    private static void test3() {
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(() -> {
            String doSomething = "Hello World";
            return doSomething;
        }, threadPool);

        supplyAsync.whenCompleteAsync((result, ex) -> {
            System.out.println("---supplyAsync---");
            System.out.println("supplyAsync result: " + result);
            System.out.println("exception: " + ex);
        }, threadPool).exceptionally(ex -> {
            System.out.println("exceptionally: " + ex.getMessage());
            return ex.getMessage();
        }).join();

        CompletableFuture<String> supplyAsyncException = CompletableFuture.supplyAsync(() -> {
            throw new CompletionException(new Exception("throw exception"));
        }, threadPool);

        supplyAsyncException.whenCompleteAsync((result, ex) -> {
            System.out.println("---supplyAsyncException---");
            System.out.println("supplyAsyncException result: " + result);
            System.out.println("exception: " + ex);
        }, threadPool).exceptionally(ex -> {
            System.out.println("exceptionally: " + ex.getMessage());
            return ex.getMessage();
        }).join();
        // 執行結果:
//        ---supplyAsync---
//        supplyAsync result: Hello World
//        exception: null
//        ---supplyAsyncException---
//        supplyAsyncException result: null
//        exception: java.util.concurrent.CompletionException: java.lang.Exception: throw exception
//        exceptionally: java.lang.Exception: throw exception
        threadPool.shutdown();
    }

也可以改用 handle 處理 exception,差別是 handle 可以有回傳值

CompletableFuture<U> handle(BiFunction<? super T, Throwable, ? extends U> fn)
CompletableFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn)

// use your own thread pool
CompletableFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn, Executor executor)

sample code:

    private static void test4() {
        ExecutorService threadPool = Executors.newFixedThreadPool(2);
        CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(() -> {
            throw new CompletionException(new Exception("throw exception"));
        }, threadPool);

        String res = supplyAsync.handle((result, ex) -> (null != ex) ? ex.getMessage() : result).join();
        System.out.println("res: " + res);

        // 結果
//        res: java.lang.Exception: throw exception
        threadPool.shutdown();
    }

資料轉換

可使用 thenApply 進行資料轉換,還有 thenCompose,不需要回傳值的 thenAccept

CompletableFuture<U> thenApply(Function<? super T, ? extends U> fn)
CompletableFuture<U> thenApplyAsync(Function<? super T, ? extends U> fn)
// use your own thread pool
CompletableFuture<U> thenApplyAsync(Function<? super T, ? extends U> fn, Executor executor)
//-----------
CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn)
CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn)

CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn, Executor executor)
//-----------
CompletableFuture<Void> thenAccept(Consumer<? super T> action)
CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action)
// use your own thread pool
CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor)

"兩個"獨立 CompletableFuture 要整合結果

當有"兩個"獨立 CompletableFuture 要整合結果時,可使用

thenCombine 可將兩個獨立的 CompletableFuture 執行結果整合在一起

thenAcceptBoth 類似 thenCombine,但不需要回傳值

//-----------
CompletableFuture<V> thenCombine(CompletionStage<? extends U> other, BiFunction<? super T, ? super U, ? extends V> fn)
CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T, ? super U, ? extends V> fn)
// use your own thread pool
CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T, ? super U, ? extends V> fn, Executor executor)

///////
CompletableFuture<Void> thenAcceptBoth(CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action)
CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action)
CompletableFuture<Void> runAfterBoth(CompletionStage<?> other,  Runnable action)

// use your own thread pool
CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action, Executor executor)

java sample

    // 將 supplyAsync1 和 supplyAsync2 結果整合
    public static void test5() {
        ExecutorService threadPool = Executors.newFixedThreadPool(2);
        CompletableFuture<String> supplyAsync1 = CompletableFuture.supplyAsync(() -> "supplyAsync 1", threadPool);
        CompletableFuture<String> supplyAsync2 = CompletableFuture.supplyAsync(() -> "supplyAsync 2", threadPool);

        String ans = supplyAsync1.thenCombine(supplyAsync2, (result1, result2) -> result1 + ", " + result2).join();
        System.out.println("ans: " + ans);
        threadPool.shutdown();
        // ans: supplyAsync 1, supplyAsync 2

        //-----------
        // 不需要回傳值
        ExecutorService threadPool2 = Executors.newFixedThreadPool(10);
        CompletableFuture<String> supplyAsync21 = CompletableFuture.supplyAsync(() -> "supplyAsync 1", threadPool2);
        CompletableFuture<String> supplyAsync22 = CompletableFuture.supplyAsync(() -> "supplyAsync 2", threadPool2);

        supplyAsync21.thenAcceptBothAsync(supplyAsync22, (result1, result2) -> System.out.println(result1 + ", " + result2), threadPool2).join();
        threadPool2.shutdown();

        // supplyAsync 1, supplyAsync 2
    }

多個 CompletableFuture

如果超過兩個以上的 CompletableFuture,可使用 allOf 等待所有的 CompletableFuture 結果,串接一個 thenRun

    public static void test6() {
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        CompletableFuture<String> supplyAsync1 = CompletableFuture.supplyAsync(() -> "supplyAsync 1", threadPool);
        CompletableFuture<String> supplyAsync2 = CompletableFuture.supplyAsync(() -> "supplyAsync 2", threadPool);
        CompletableFuture<String> supplyAsync3 = CompletableFuture.supplyAsync(() -> "supplyAsync 3", threadPool);

        CompletableFuture.allOf(supplyAsync1, supplyAsync2, supplyAsync3).thenRun(() -> {
            try {
                StringBuffer ans = new StringBuffer();
                ans.append(supplyAsync1.get()).append(", ")
                        .append(supplyAsync2.get()).append(", ")
                        .append(supplyAsync3.get());
                System.out.println("ans: " + ans.toString());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }).join();

        threadPool.shutdown();
        // ans: supplyAsync 1, supplyAsync 2, supplyAsync 3

    }

只想要其中一個有完成就往下執行,可使用 applyToEither,沒有回傳值的 acceptEither,用在多個 CompletableFuture 的anyOf

CompletableFuture<U> applyToEither(CompletionStage<? extends T> other, Function<? super T,U> fn)
CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T,U> fn)

// use your own thread pool
CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T,U> fn, Executor executor)

///////

CompletableFuture<Void>  acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action)
CompletableFuture<Void>  acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action)

// use your own thread pool
CompletableFuture<Void>  acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action, Executor executor)


///////
CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)

References

Guide To CompletableFuture | Baeldung

Java 9 CompletableFuture API Improvements | Baeldung

JDK8 - CompletableFuture 非同步處理簡介

CompletableFuture

【Java 8 新特性】Java CompletableFuture thenApply()_.thenapply_猫巳的博客-CSDN博客

【JDK8】CompletableFuture 非同步處理

關於 Java CompletableFuture 的用法 | HengLin31

2023/6/5

HttpClient in JDK 11

JDK 8 以前的 HTTP Client 通常是使用第三方函式庫,最常使用的是 Apache HTTPComponents 及 OkHttp。在 JDK 9 以後,標準函式庫裡面也有 HTTP Client 可以使用。

程式包含三個部分:HttpRequest, HttpResponse 及 HttpClient

HttpRequst

  1. URI 就是 request 要發送的目標網址

  2. HTTP Method

    • GET()
    • POST(BodyPublisher body)
    • PUT(BodyPublisher body)
    • DELETE()
  3. HTTP Protcol Version

    可指定這個 http request 的版本,以往常見的是 1.1,目前是 2

  4. Headers

    設定 http request header

  5. Timeout

    設定等待 http response 的 timeout 時間

  6. Request Body

    如果是 POST, PUT, DELETE method,需要再增加 body content,對應的 content

Request Body

可使用以下這些 API 實作 body content

  1. StringProcessor

    從 String 產生 body,以 HttpRequest.BodyPublishers.ofString 產生

    HttpRequest request = HttpRequest.newBuilder()
     .uri(new URI("https://postman-echo.com/post"))
     .headers("Content-Type", "text/plain;charset=UTF-8")
     .POST(HttpRequest.BodyPublishers.ofString("Sample request body"))
     .build();
  2. IputStreamProcessor

    從 InputSteam 產生 body,以 HttpRequest.BodyPublishers.ofInputStream 產生

    byte[] sampleData = "Sample request body".getBytes();
    HttpRequest request = HttpRequest.newBuilder()
           .uri(new URI("https://postman-echo.com/post"))
           .headers("Content-Type", "text/plain;charset=UTF-8")
           .POST(HttpRequest.BodyPublishers
                   .ofInputStream(() -> new ByteArrayInputStream(sampleData)))
           .build();
  3. ByteArrayProcessor

    從 ByteArray 產生 Body,以 HttpRequest.BodyPublishers.ofByteArray 產生

    byte[] sampleData = "Sample request body".getBytes();
    HttpRequest request = HttpRequest.newBuilder()
           .uri(new URI("https://postman-echo.com/post"))
           .headers("Content-Type", "text/plain;charset=UTF-8")
           .POST(HttpRequest.BodyPublishers.ofByteArray(sampleData))
           .build();
  4. FileProcessor

    從某個路徑的檔案產生 body,以 HttpRequest.BodyPublishers.ofFile 產生

    HttpRequest request = HttpRequest.newBuilder()
           .uri(new URI("https://postman-echo.com/post"))
           .headers("Content-Type", "text/plain;charset=UTF-8")
           .POST(HttpRequest.BodyPublishers.ofFile(
                   Paths.get("sample.txt")))
           .build();
  5. noBody

    如果沒有 body content,可使用 HttpRequest.BodyPublishers.**noBody()

    HttpRequest request = HttpRequest.newBuilder()
     .uri(new URI("https://postman-echo.com/post"))
     .POST(HttpRequest.BodyPublishers.noBody())
     .build();

HttpClient

  • 透過 HttpClient.newBuilder() 或是 HttpClient.newHttpClient() 產生 instance

  • 透過 Handler 處理 response body

    BodyHandlers.ofByteArray
    BodyHandlers.ofString
    BodyHandlers.ofFile
    BodyHandlers.discarding
    BodyHandlers.replacing
    BodyHandlers.ofLines
    BodyHandlers.fromLineSubscriber
    
    // jdk 11 以前
    HttpResponse<String> response = client.send(request, HttpResponse.BodyHandler.asString());
    
    // 新的 jdk
    HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
  • Proxy

    可定義 proxy

    HttpResponse<String> response = HttpClient
      .newBuilder()
      .proxy(ProxySelector.getDefault())
      .build()
      .send(request, BodyHandlers.ofString());
  • Direct Policy

    如果 reponse 收到 3XX 的 redirect 結果,可設定 redirect policy 直接轉址

    HttpResponse<String> response = HttpClient.newBuilder()
      .followRedirects(HttpClient.Redirect.ALWAYS)
      .build()
      .send(request, BodyHandlers.ofString());
  • HTTP Authentication

    HttpResponse<String> response = HttpClient.newBuilder()
      .authenticator(new Authenticator() {
        @Override
        protected PasswordAuthentication getPasswordAuthentication() {
          return new PasswordAuthentication(
            "username", 
            "password".toCharArray());
        }
    }).build()
      .send(request, BodyHandlers.ofString());
  • Cookie

    // 透過 cookieHandler(CookieHandler cookieHandler)  定義 CookieHandler
    // 設定不接受 cookie
    HttpClient.newBuilder()
      .cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_NONE))
      .build();
    
    // 取得 CookieStore
    ((CookieManager) httpClient.cookieHandler().get()).getCookieStore()
  • SSL Context

    在 HttpClient 指定 SSL Context,忽略 ssl key 檢查

    private static SSLContext disabledSSLContext() throws KeyManagementException, NoSuchAlgorithmException {
            SSLContext sslContext = SSLContext.getInstance("TLS");
            // https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#sslcontext-algorithms
            sslContext.init(
                    null,
                new TrustManager[] {
                    new X509TrustManager() {
                        public X509Certificate[] getAcceptedIssuers() {
                            return null;
                        }
    
                        public void checkClientTrusted(X509Certificate[] certs, String authType) {
                        }
    
                        public void checkServerTrusted(X509Certificate[] certs, String authType) {
                        }
                    }
                },
                new SecureRandom()
            );
            return sslContext;
        }

Sync or Async 同步或是非同步呼叫

HttpClient 有同步或非同步的發送 request 的方式

  • send 同步

    程式會停在這邊,等待 response 或是 timeout,下一行,可直接取得 response body

    HttpResponse<String> response = HttpClient.newBuilder()
      .build()
      .send(request, BodyHandlers.ofString());
  • sendAsync 非同步 non-blocking

    CompletableFuture<HttpResponse<String>> response = HttpClient.newBuilder()
      .build()
      .sendAsync(request, HttpResponse.BodyHandlers.ofString());
  • 可指定 Executor 限制 threads

    預設是使用 java.util.concurrent.Executors.newCachedThreadPool()

    ExecutorService executorService = Executors.newFixedThreadPool(2);
    
    CompletableFuture<HttpResponse<String>> response1 = HttpClient.newBuilder()
      .executor(executorService)
      .build()
      .sendAsync(request, HttpResponse.BodyHandlers.ofString());
    
    CompletableFuture<HttpResponse<String>> response2 = HttpClient.newBuilder()
      .executor(executorService)
      .build()
      .sendAsync(request, HttpResponse.BodyHandlers.ofString());

HttpResponse

  • URI

    HttpResponse 也有一個 uri() method,可取得 uri,因為有時候會遇到 redirect uri 的回應,因此 response 的 uri,會取得 redirect 後的網址

  • Response Header

    取得 response header list

    HttpResponse<String> response = HttpClient.newHttpClient()
      .send(request, HttpResponse.BodyHandlers.ofString());
    HttpHeaders responseHeaders = response.headers();
  • Http Version

    server 是以哪一個 http version 回應的

    response.version();
  • Response Body

    String body = response.body();

完整 Java Code

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpHeaders;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Paths;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

public class HttpClientTest {

    public static void main(String[] args) throws Exception {

        // 建立HttpClient實例
        HttpClient httpClient = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_1_1) // http 1.1
                .connectTimeout(Duration.ofSeconds(5)) // timeout after 5 seconds
                .sslContext(disabledSSLContext()) // disable SSL verify
                .build();

        // 建立 HttpRequest請求
//        HttpRequest request = getMethod();
//        HttpRequest request = postNoBody();
        HttpRequest request = postStringBody();
//        HttpRequest request = postInputStreamBody();
//        HttpRequest request = postByteArrayBody();
//        HttpRequest request = postFileBody();

        // 錯誤的 URI 測試 Timeout
//        HttpRequest request = postStringBody_InvalidUri();

        // Sync 同步呼叫
//        sync(httpClient, request);
        // 非同步呼叫
        async(httpClient, request);

    }

    private static void async(HttpClient httpClient, HttpRequest request) throws InterruptedException, java.util.concurrent.ExecutionException, java.util.concurrent.TimeoutException {
        // 非同步
        CompletableFuture<HttpResponse<String>> response = httpClient
                .sendAsync(request, HttpResponse.BodyHandlers.ofString());
        String result = response
                .thenApply(HttpResponse::body)
                .exceptionally(t -> {
                    t.printStackTrace();
                    return "fallback";
                })
                .get(10, TimeUnit.SECONDS);
        System.out.println(result);
    }

    private static void sync(HttpClient httpClient, HttpRequest request) throws java.io.IOException, InterruptedException {
        // 發送請求並接收回應
        HttpResponse<String> response = httpClient.send(
                request, HttpResponse.BodyHandlers.ofString());

        HttpClient.Version version = response.version();
        System.out.println("---response version---");
        System.out.println(version);

        System.out.println("---response headers---");
        HttpHeaders responseHeaders = response.headers();
        Map<String, List<String>> responseHeadersMap = responseHeaders.map();
        for (String key : responseHeadersMap.keySet()) {
            System.out.println(key + ":" + responseHeadersMap.get(key));
        }

        // 取得回應主體內容
        String body = response.body();
        System.out.println("---response body---");
        System.out.println(body);
    }

    private static HttpRequest getMethod() {
        // 臺灣證券交易所0056個股日成交資訊API
        String url = "https://www.twse.com.tw/exchangeReport/STOCK_DAY?response=json&date=20230531&stockNo=0056";

        // 建立 HttpRequest請求  get method
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(url))
                .version(HttpClient.Version.HTTP_2)
                .header("cache-control", "no-cache")
                .GET()
                .build();
        return request;
    }

    private static HttpRequest postNoBody() throws URISyntaxException {
        HttpRequest request = HttpRequest.newBuilder()
                .uri(new URI("https://postman-echo.com/post"))
                .POST(HttpRequest.BodyPublishers.noBody())
                .build();
        return request;
    }

    private static HttpRequest postStringBody() throws URISyntaxException {
        HttpRequest request = HttpRequest.newBuilder()
                .uri(new URI("https://postman-echo.com/post"))
                .headers("Content-Type", "text/plain;charset=UTF-8")
                .POST(HttpRequest.BodyPublishers.ofString("Sample request body"))
                .build();
        return request;
    }

    private static HttpRequest postInputStreamBody() throws URISyntaxException {
        byte[] sampleData = "Sample request body".getBytes();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(new URI("https://postman-echo.com/post"))
                .headers("Content-Type", "text/plain;charset=UTF-8")
                .POST(HttpRequest.BodyPublishers
                        .ofInputStream(() -> new ByteArrayInputStream(sampleData)))
                .build();
        return request;
    }

    private static HttpRequest postByteArrayBody() throws URISyntaxException {
        byte[] sampleData = "Sample request body".getBytes();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(new URI("https://postman-echo.com/post"))
                .headers("Content-Type", "text/plain;charset=UTF-8")
                .POST(HttpRequest.BodyPublishers.ofByteArray(sampleData))
                .build();
        return request;
    }

    private static HttpRequest postFileBody() throws URISyntaxException, FileNotFoundException {
        HttpRequest request = HttpRequest.newBuilder()
                .uri(new URI("https://postman-echo.com/post"))
                .headers("Content-Type", "text/plain;charset=UTF-8")
                .POST(HttpRequest.BodyPublishers.ofFile(
                        Paths.get("sample.txt")))
                .build();
        return request;
    }

    private static HttpRequest postStringBody_InvalidUri() throws URISyntaxException {
        HttpRequest request = HttpRequest.newBuilder()
                .uri(new URI("https://test.com/post"))
                .headers("Content-Type", "text/plain;charset=UTF-8")
                .POST(HttpRequest.BodyPublishers.ofString("Sample request body"))
                .build();
        return request;
    }

    private static SSLContext disabledSSLContext() throws KeyManagementException, NoSuchAlgorithmException {
        SSLContext sslContext = SSLContext.getInstance("TLS");
        // https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#sslcontext-algorithms
        sslContext.init(
                null,
            new TrustManager[] {
                new X509TrustManager() {
                    public X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }

                    public void checkClientTrusted(X509Certificate[] certs, String authType) {
                    }

                    public void checkServerTrusted(X509Certificate[] certs, String authType) {
                    }
                }
            },
            new SecureRandom()
        );
        return sslContext;
    }

}

References

菜鳥工程師 肉豬: Java 11 HttpClient發送請求範例

Exploring the New HTTP Client in Java | Baeldung

2023/5/29

0-90法則 ninety-ninety rule

貝爾實驗室 Tom Cargill 提出 90-90 法則,後來因為 Jon Bentley 寫在 ACM 的 programming Pearls 專欄的 Rule of Credibility 文章中而流行。

The first 90 percent of the code accounts for the first 90 percent of the development time. The remaining 10 percent of the code accounts for the other 90 percent of the development time.

開發軟體時,前90%的代碼要花費90%的開發時間,剩餘的10%的代碼要再花費90%的開發時間。

90-90 法則跟侯世達定律 Hofstadter's Law 都是說明同一件事,一個專案,會花上比預期還要多的時間才能完成。

軟體業的本質,其實某個程度來說,不是高科技產業,而像是一種手工業,也就是軟體手工業,因為軟體是程式設計師,用一行一行的程式碼,將程式邏輯組合而成的一種工藝品。這種耗費智力的工藝品,不同於其他工業產品,生產效率跟品質很難被評估,也可以說軟體工程師像是一種智能的純手工工人,要把程式碼一行一行打出來,才能完成作品。

References

90-90法則_百度百科

90-90法則 - MBA智库百科

90-90法則:簡介,軟體項目管理,風險策劃,有關因素,_中文百科全書