項目 | 日期 | |
---|---|---|
* |
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
- @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); };
- }
- }
- @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);
- }
- }
- 第 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
- @FunctionalInterface
- public interface Supplier<T> {
- T get();
- }
- @DisplayName("SupplierTest")
- @SpringBootTest
- @Slf4j
- class SupplierTest {
- @Test
- void random() {
- Supplier<Double> randomValue = Math::random;
- log.debug(randomValue.get().toString());
- }
- }
- 第 8 行: 實做一個命名為 rendomValue 的實做類別,覆寫 get() method 為 Math:: random。
- 第 10 行: 從 randomValue 中取出值並輸出。
- Predicate
- @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();
- }
- }
- @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);
- }
- }
- 第 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。
- IntPredicate greaterThanTen = (i) -> i > 10;
- Function
- @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;
- }
- }
- @DisplayName("FunctionTest")
- @SpringBootTest
- @Slf4j
- class FunctionTest {
- @Test
- void apply() {
- Function<Integer, Double> half = a -> a / 2.0;
- log.debug(half.apply(10).toString());
- }
- }
- 第 7 行: 實傳一個 Function 類別,將傳入值除以 2 傳回。
- 第 8 行: 傳入 10,傳回 5.0。
- IntToDoubleFunction half = a -> a / 2.0;
- log.debug(Double.toString(half.applyAsDouble(10)));
上面是 Consumer 本身的程式碼,這個 interface 有兩個 method,僅有一個未實作的 method - accept。從名稱猜測,可以知道這是個消費者,即實作 Consumer interface 的類別要透過 accept 吃進資料並進行處理。至於 andThen 這個 method 並沒有強制要用,當有使用時,會在 accept 執行完後接著執行。
上面單元測試程式,執行後第一個 method (accept) 會輸出 10 4 2 6,第二個 method (andThen) 會輸出 4 2 6,程式說明如下:
(1) 第一個單元測試 - accept()
Supplier 是生產者,上面是 Supplier 的程式碼,只有一個 method - get(),讓外界可以透過這個 method 取得 Supplier 實做類別裡的值。
上面單元測試程式執行後,輸出 0.4077113851887808,這是個隨機值,程式說明如下:
Predicate 的 test method 是用來測試邏輯運算是 true 或 false,其餘 method 用來和 test 搭配出更複雜的邏輯判斷。
上面單元測試程式,執行後第一個 method (greaterThanFive) 會輸出 23 56 6 6,第二個 method (betweenTenAndTwenty) 會輸出 result = true、result2 = false,程式說明如下:
上面程式第 8、17、19 宣告的是 Integer 型別,實際傳入的是 int 型別,在 Java 中執行時,會先把 int 轉型成 Integer,在效能上會比較差, 為了避免這個問題,Java 另外提供了 IntPredicate,以第 17 行來說,可以改成如下:
這樣避免了轉型,速度會快些。
Function 的 apply method 類似於 Stream 的 map,將 T 經運算後傳出 R。
上面程式執行後傳回 5.0,程式說明如下:
2022年7月6日 星期三
jasypt 加解密
在 application.yml 中如果有資料庫連線設定,可能會如下:
上面密碼的部份是用 jasypt 加密,通常不會在設定檔直接寫明碼。那麼,怎麼初始化 jasypt 函式庫呢?
- 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
- build.gradle
- implementation 'com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.4'
- application.yml
- jasypt:
- encryptor:
- password: ${JASYPT_ENCRYPTOR_PASSWORD}
- algorithm: PBEWithMD5AndDES
- iv-generator-classname: org.jasypt.iv.NoIvGenerator
- @EnableEncryptableProperties
- @EnableEncryptableProperties
- @SpringBootApplication
- public class DemoSecurityApplication {
- public static void main(String[] args) {
- SpringApplication.run(DemoSecurityApplication.class, args);
- }
- }
加入如上 jar 檔。
${JASYPT_ENCRYPTOR_PASSWORD} 會從環境變數取得加解密的 key,要注意最後一行,在 jasypt-spring-boot-starter 2.x 版不需要這個設定,到 3.x 版才需要。
在 @SpringBootApplication 或 @Configuration 所在的 class 上加上 @EnableEncryptableProperties 啟用 jasypt。
訂閱:
文章 (Atom)