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
  2. @FunctionalInterface
    public interface Consumer<T> {
        
        void accept(T t);
    
        default Consumer<T> andThen(Consumer<? super T> after) {
            Objects.requireNonNull(after);
            return (T t) -> { accept(t); after.accept(t); };
        }
    }
    上面是 Consumer 本身的程式碼,這個 interface 有兩個 method,僅有一個未實作的 method - accept。從名稱猜測,可以知道這是個消費者,即實作 Consumer interface 的類別要透過 accept 吃進資料並進行處理。至於 andThen 這個 method 並沒有強制要用,當有使用時,會在 accept 執行完後接著執行。
    @DisplayName("ConsumerTest")
    @SpringBootTest
    @Slf4j
    class ConsumerTest {
    
        @Test
        void accept() {
            Consumer<Integer> display = a -> log.debug(a.toString());
     
            display.accept(10);
     
            Consumer<List<Integer> > modify = list ->
            {
                for (int i = 0; i < list.size(); i++)
                    list.set(i, 2 * list.get(i));
            };
     
            Consumer<List<Integer> >
                dispList = list -> list.stream().forEach(a -> log.debug(a + " "));
     
            List<Integer> list = new ArrayList<Integer>();
            list.add(2);
            list.add(1);
            list.add(3);
     
            modify.accept(list);
     
            dispList.accept(list);
        }
    
        @Test
        void andThen() {
            Consumer<List<Integer> > modify = list ->
            {
                for (int i = 0; i < list.size(); i++)
                    list.set(i, 2 * list.get(i));
            };
     
            Consumer<List<Integer> >
                dispList = list -> list.stream().forEach(a -> log.debug(a + " "));
     
            List<Integer> list = new ArrayList<Integer>();
            list.add(2);
            list.add(1);
            list.add(3);
     
            modify.andThen(dispList).accept(list);
        }
    }
    
    
    上面單元測試程式,執行後第一個 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 將值輸出。

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

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

  5. Predicate
  6. @FunctionalInterface
    public interface Predicate<T> {
    
        boolean test(T t);
    
        default Predicate<T> and(Predicate<? super T> other) {
            Objects.requireNonNull(other);
            return (t) -> test(t) && other.test(t);
        }
    
        default Predicate<T> negate() {
            return (t) -> !test(t);
        }
    
        default Predicate<T> or(Predicate<? super T> other) {
            Objects.requireNonNull(other);
            return (t) -> test(t) || other.test(t);
        }
    
        static <T> Predicate<T> isEqual(Object targetRef) {
            return (null == targetRef)
                    ? Objects::isNull
                    : object -> targetRef.equals(object);
        }
    
        @SuppressWarnings("unchecked")
        static <T> Predicate<T> not(Predicate<? super T> target) {
            Objects.requireNonNull(target);
            return (Predicate<T>)target.negate();
        }
    }
    Predicate 的 test method 是用來測試邏輯運算是 true 或 false,其餘 method 用來和 test 搭配出更複雜的邏輯判斷。
    @DisplayName("PredicateTest")
    @SpringBootTest
    @Slf4j
    class PredicateTest {
    
        @Test
        void greaterThanFive() {
    	Predicate<Integer> predicate = (i) -> i > 5;
    
            Stream<Integer> stream = Stream.of(1, 23, 3, 4, 5, 56, 6, 6);
            List<Integer> list = stream.filter(predicate).collect(Collectors.toList());
            list.forEach(item -> log.debug(Integer.toString(item)));
        }
    
        @Test
        void betweenTenAndTwenty() {
    	Predicate<Integer> greaterThanTen = (i) -> i > 10;
    
            Predicate<Integer> lowerThanTwenty = (i) -> i < 20; 
            boolean result = greaterThanTen.and(lowerThanTwenty).test(15);
            log.debug("result = " + result);
     
            boolean result2 = greaterThanTen.and(lowerThanTwenty).negate().test(15);
            log.debug("result2 = " + result2);
        }
    }
    
    上面單元測試程式,執行後第一個 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 行來說,可以改成如下:
    IntPredicate greaterThanTen = (i) -> i > 10;
    
    這樣避免了轉型,速度會快些。
  7. Function
  8. @FunctionalInterface
    public interface Function<T, R> {
        R apply(T t);
    
        default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
            Objects.requireNonNull(before);
            return (V v) -> apply(before.apply(v));
        }
    
        default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
            Objects.requireNonNull(after);
            return (T t) -> after.apply(apply(t));
        }
    
        static <T> Function<T, T> identity() {
            return t -> t;
        }
    }
    
    Function 的 apply method 類似於 Stream 的 map,將 T 經運算後傳出 R。
    @DisplayName("FunctionTest")
    @SpringBootTest
    @Slf4j
    class FunctionTest {
        @Test
        void apply() {
    	Function<Integer, Double> half = a -> a / 2.0;
    	log.debug(half.apply(10).toString());
        }
    }
    
    上面程式執行後傳回 5.0,程式說明如下:
    • 第 7 行: 實傳一個 Function 類別,將傳入值除以 2 傳回。
    • 第 8 行: 傳入 10,傳回 5.0。
    上面程式也有轉型的問題,要避開這個問題,程式可以改成如下:
    IntToDoubleFunction half = a -> a / 2.0;
    log.debug(Double.toString(half.applyAsDouble(10)));
    

2022年7月6日 星期三

jasypt 加解密

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

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