Google Code Prettify

2022年7月23日 星期六

Lambda / Stream / Function

項目日期
*
  Lambda / Stream / Function
1Java Lambda: getting started
2020/11/01
2Java Stream: getting started
2020/10/03
3Java Stream: Creation
2020/10/04
4Java Stream: operation
2020/11/02
5Java Stream: Collections
2020/11/07
6Consumer、Supplier、Predicate、Function
2022/07/23

Consumer、Supplier、Predicate、Function

 Java 要逐漸導入 Functional Programming,多了 Lambda、Stream 及現在要說明的 java.util.function 這個 package 下的四個 interface。

這四個 interface 都標注有 @FunctionalInterface,這表示這些 interface 只能剛剛好有一個未實作的 method! 要特別注意,Java 8 開始,interface 的 method 是可以實作非僅有宣告。

  1. Consumer
    1. @FunctionalInterface
    2. public interface Consumer<T> {
    3. void accept(T t);
    4.  
    5. default Consumer<T> andThen(Consumer<? super T> after) {
    6. Objects.requireNonNull(after);
    7. return (T t) -> { accept(t); after.accept(t); };
    8. }
    9. }
    上面是 Consumer 本身的程式碼,這個 interface 有兩個 method,僅有一個未實作的 method - accept。從名稱猜測,可以知道這是個消費者,即實作 Consumer interface 的類別要透過 accept 吃進資料並進行處理。至於 andThen 這個 method 並沒有強制要用,當有使用時,會在 accept 執行完後接著執行。
    1. @DisplayName("ConsumerTest")
    2. @SpringBootTest
    3. @Slf4j
    4. class ConsumerTest {
    5.  
    6. @Test
    7. void accept() {
    8. Consumer<Integer> display = a -> log.debug(a.toString());
    9. display.accept(10);
    10. Consumer<List<Integer> > modify = list ->
    11. {
    12. for (int i = 0; i < list.size(); i++)
    13. list.set(i, 2 * list.get(i));
    14. };
    15. Consumer<List<Integer> >
    16. dispList = list -> list.stream().forEach(a -> log.debug(a + " "));
    17. List<Integer> list = new ArrayList<Integer>();
    18. list.add(2);
    19. list.add(1);
    20. list.add(3);
    21. modify.accept(list);
    22. dispList.accept(list);
    23. }
    24.  
    25. @Test
    26. void andThen() {
    27. Consumer<List<Integer> > modify = list ->
    28. {
    29. for (int i = 0; i < list.size(); i++)
    30. list.set(i, 2 * list.get(i));
    31. };
    32. Consumer<List<Integer> >
    33. dispList = list -> list.stream().forEach(a -> log.debug(a + " "));
    34. List<Integer> list = new ArrayList<Integer>();
    35. list.add(2);
    36. list.add(1);
    37. list.add(3);
    38. modify.andThen(dispList).accept(list);
    39. }
    40. }
    41.  
    上面單元測試程式,執行後第一個 method (accept) 會輸出 10 4 2 6,第二個 method (andThen) 會輸出 4 2 6,程式說明如下:
    (1) 第一個單元測試 - accept()
    • 第 8 行: 實做一個命名為 display 的 Consumer 實做類別,實做出 accept() method,很簡單的將收到的值輸出。
    • 第 10 行: 將 10 傳入給 display 這個 Consumer 實做類別,程式會輸出 10。
    • 第 12~16 行: 實做一個命名為 modify 的 Consumer 實做類別,這個類別會接收一個 List<Integer>,並將 List 的每個值乘以 2。
    • 第 18 行: 實做一個命名為 dispList 實做類別,這個類別會接收一個 List<Integer>,並將 List 裡所有值輸出。
    • 第 21~24 行: 產生一個 3 個元素的 List,值分別為2、1、3。
    • 第 26 行: modify 將傳入的 List 裡的元素都乘以 2。
    • 第 28 行: dispList 將傳入的 List 裡的元素依序輸出為 4 2 6。
    (2) 第二個單元測試 - andThen()
    • 第 33~37 行: 實做一個命名為 modify 的 Consumer 實做類別,這個類別會接收一個 List<Integer>,並將 List 的每個值乘以 2。
    • 第 39 行: 實做一個命名為 dispList 實做類別,這個類別會接收一個 List<Integer>,並將 List 裡所有值輸出。
    • 第 42~45 行: 產生一個 3 個元素的 List,值分別為2、1、3。
    • 第 47 行: 會先執行 accept 將值乘 2,接著執行 andThen 將值輸出。

  2. Supplier
    1. @FunctionalInterface
    2. public interface Supplier<T> {
    3. T get();
    4. }

    Supplier 是生產者,上面是 Supplier 的程式碼,只有一個 method - get(),讓外界可以透過這個 method 取得 Supplier 實做類別裡的值。
    1. @DisplayName("SupplierTest")
    2. @SpringBootTest
    3. @Slf4j
    4. class SupplierTest {
    5.  
    6. @Test
    7. void random() {
    8. Supplier<Double> randomValue = Math::random;
    9. log.debug(randomValue.get().toString());
    10. }
    11. }
    上面單元測試程式執行後,輸出 0.4077113851887808,這是個隨機值,程式說明如下:
    • 第 8 行: 實做一個命名為 rendomValue 的實做類別,覆寫 get() method 為 Math:: random。
    • 第 10 行: 從 randomValue 中取出值並輸出。

  3. Predicate
    1. @FunctionalInterface
    2. public interface Predicate<T> {
    3.  
    4. boolean test(T t);
    5.  
    6. default Predicate<T> and(Predicate<? super T> other) {
    7. Objects.requireNonNull(other);
    8. return (t) -> test(t) && other.test(t);
    9. }
    10.  
    11. default Predicate<T> negate() {
    12. return (t) -> !test(t);
    13. }
    14.  
    15. default Predicate<T> or(Predicate<? super T> other) {
    16. Objects.requireNonNull(other);
    17. return (t) -> test(t) || other.test(t);
    18. }
    19.  
    20. static <T> Predicate<T> isEqual(Object targetRef) {
    21. return (null == targetRef)
    22. ? Objects::isNull
    23. : object -> targetRef.equals(object);
    24. }
    25.  
    26. @SuppressWarnings("unchecked")
    27. static <T> Predicate<T> not(Predicate<? super T> target) {
    28. Objects.requireNonNull(target);
    29. return (Predicate<T>)target.negate();
    30. }
    31. }
    Predicate 的 test method 是用來測試邏輯運算是 true 或 false,其餘 method 用來和 test 搭配出更複雜的邏輯判斷。
    1. @DisplayName("PredicateTest")
    2. @SpringBootTest
    3. @Slf4j
    4. class PredicateTest {
    5.  
    6. @Test
    7. void greaterThanFive() {
    8. Predicate<Integer> predicate = (i) -> i > 5;
    9.  
    10. Stream<Integer> stream = Stream.of(1, 23, 3, 4, 5, 56, 6, 6);
    11. List<Integer> list = stream.filter(predicate).collect(Collectors.toList());
    12. list.forEach(item -> log.debug(Integer.toString(item)));
    13. }
    14.  
    15. @Test
    16. void betweenTenAndTwenty() {
    17. Predicate<Integer> greaterThanTen = (i) -> i > 10;
    18.  
    19. Predicate<Integer> lowerThanTwenty = (i) -> i < 20;
    20. boolean result = greaterThanTen.and(lowerThanTwenty).test(15);
    21. log.debug("result = " + result);
    22. boolean result2 = greaterThanTen.and(lowerThanTwenty).negate().test(15);
    23. log.debug("result2 = " + result2);
    24. }
    25. }
    上面單元測試程式,執行後第一個 method (greaterThanFive) 會輸出 23 56 6 6,第二個 method (betweenTenAndTwenty) 會輸出 result = true、result2 = false,程式說明如下:
    • 第 8 行: 實做一個 Predicate 類別,測試傳入值是否大於 5。
    • 第 10 行: 產生一個有 8 個數字的串流。
    • 第 11 行: 利用第 8 行實做的 Predicate 類別過瀘第 10 行的串流資料,過瀘出來的資料放入一個 List。
    • 第 12 行: 輸出 List 裡的值,可以看到只輸出大於 5 的元素。
    • 第 17、19 行: 實做兩個 Predicate 類別,用來篩選大於 10 及小於 20 的值。
    • 第 20 行: 用 greaterThanTen 搭配 Predicate 介面中的 and method 整合 lowerThanTwenty 篩選同時滿足大於 10 及小於 20 兩個條件的值。
    • 第 23 行: 將第 20 行的結果透過 Predicate 介面中提供的 negate method 將值反向,所以原本是 true,反向變 false。 
    上面程式第 8、17、19 宣告的是 Integer 型別,實際傳入的是 int 型別,在 Java 中執行時,會先把 int 轉型成 Integer,在效能上會比較差, 為了避免這個問題,Java 另外提供了 IntPredicate,以第 17 行來說,可以改成如下:
    1. IntPredicate greaterThanTen = (i) -> i > 10;
    這樣避免了轉型,速度會快些。
  4. Function
    1. @FunctionalInterface
    2. public interface Function<T, R> {
    3. R apply(T t);
    4.  
    5. default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
    6. Objects.requireNonNull(before);
    7. return (V v) -> apply(before.apply(v));
    8. }
    9.  
    10. default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    11. Objects.requireNonNull(after);
    12. return (T t) -> after.apply(apply(t));
    13. }
    14.  
    15. static <T> Function<T, T> identity() {
    16. return t -> t;
    17. }
    18. }
    Function 的 apply method 類似於 Stream 的 map,將 T 經運算後傳出 R。
    1. @DisplayName("FunctionTest")
    2. @SpringBootTest
    3. @Slf4j
    4. class FunctionTest {
    5.     @Test
    6.     void apply() {
    7. Function<Integer, Double> half = a -> a / 2.0;
    8. log.debug(half.apply(10).toString());
    9.     }
    10. }
    上面程式執行後傳回 5.0,程式說明如下:
    • 第 7 行: 實傳一個 Function 類別,將傳入值除以 2 傳回。
    • 第 8 行: 傳入 10,傳回 5.0。
    上面程式也有轉型的問題,要避開這個問題,程式可以改成如下:
    1. IntToDoubleFunction half = a -> a / 2.0;
    2. log.debug(Double.toString(half.applyAsDouble(10)));

2022年7月6日 星期三

jasypt 加解密

 在 application.yml 中如果有資料庫連線設定,可能會如下:

  1. spring:
  2. datasource:
  3. url: jdbc:mysql://127.0.0.1:3306/AUTH
  4. username: root
  5. password: ENC(wwCYqJ+PMxrCAY0aPs8v91/oykkFVyux)
  6. driver-class-name: com.mysql.cj.jdbc.Driver
  7. jpa:
  8. database-platform: org.hibernate.dialect.MySQLDialect
上面密碼的部份是用 jasypt 加密,通常不會在設定檔直接寫明碼。那麼,怎麼初始化 jasypt 函式庫呢? 
  1. build.gradle
    1. implementation 'com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.4'
    加入如上 jar 檔。
  2. application.yml
    1. jasypt:
    2. encryptor:
    3. password: ${JASYPT_ENCRYPTOR_PASSWORD}
    4. algorithm: PBEWithMD5AndDES
    5. iv-generator-classname: org.jasypt.iv.NoIvGenerator
    ${JASYPT_ENCRYPTOR_PASSWORD} 會從環境變數取得加解密的 key,要注意最後一行,在 jasypt-spring-boot-starter 2.x 版不需要這個設定,到 3.x 版才需要。
  3. @EnableEncryptableProperties
    1. @EnableEncryptableProperties
    2. @SpringBootApplication
    3. public class DemoSecurityApplication {
    4.  
    5. public static void main(String[] args) {
    6. SpringApplication.run(DemoSecurityApplication.class, args);
    7. }
    8. }
    在 @SpringBootApplication 或 @Configuration 所在的 class 上加上 @EnableEncryptableProperties 啟用 jasypt。