項目 | 日期 | |
---|---|---|
* |
Lambda / Stream / Function
| |
1 | Java Lambda: getting started | 2020/11/01
|
2 | Java Stream: getting started | 2020/10/03
|
3 | Java Stream: Creation | 2020/10/04
|
4 | Java Stream: operation | 2020/11/02 |
5 | Java Stream: Collections | 2020/11/07 |
6 | Consumer、Supplier、Predicate、Function | 2022/07/23 |
Google Code Prettify
2022年7月23日 星期六
Lambda / Stream / Function
Consumer、Supplier、Predicate、Function
Java 要逐漸導入 Functional Programming,多了 Lambda、Stream 及現在要說明的 java.util.function 這個 package 下的四個 interface。
這四個 interface 都標注有 @FunctionalInterface,這表示這些 interface 只能剛剛好有一個未實作的 method! 要特別注意,Java 8 開始,interface 的 method 是可以實作非僅有宣告。
- Consumer
- 第 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。
- 第 33~37 行: 實做一個命名為 modify 的 Consumer 實做類別,這個類別會接收一個 List<Integer>,並將 List 的每個值乘以 2。
- 第 39 行: 實做一個命名為 dispList 實做類別,這個類別會接收一個 List<Integer>,並將 List 裡所有值輸出。
- 第 42~45 行: 產生一個 3 個元素的 List,值分別為2、1、3。
- 第 47 行: 會先執行 accept 將值乘 2,接著執行 andThen 將值輸出。
- Supplier
- 第 8 行: 實做一個命名為 rendomValue 的實做類別,覆寫 get() method 為 Math:: random。
- 第 10 行: 從 randomValue 中取出值並輸出。
- Predicate
- 第 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。
- Function
- 第 7 行: 實傳一個 Function 類別,將傳入值除以 2 傳回。
- 第 8 行: 傳入 10,傳回 5.0。
@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()
@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,這是個隨機值,程式說明如下:
@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、17、19 宣告的是 Integer 型別,實際傳入的是 int 型別,在 Java 中執行時,會先把 int 轉型成 Integer,在效能上會比較差, 為了避免這個問題,Java 另外提供了 IntPredicate,以第 17 行來說,可以改成如下:
IntPredicate greaterThanTen = (i) -> i > 10;這樣避免了轉型,速度會快些。
@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,程式說明如下:
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 函式庫呢?
- build.gradle
- application.yml
- @EnableEncryptableProperties
implementation 'com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.4'加入如上 jar 檔。
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 版才需要。
@EnableEncryptableProperties
@SpringBootApplication
public class DemoSecurityApplication {
public static void main(String[] args) {
SpringApplication.run(DemoSecurityApplication.class, args);
}
}
在 @SpringBootApplication 或 @Configuration 所在的 class 上加上 @EnableEncryptableProperties 啟用 jasypt。
訂閱:
文章 (Atom)