JDK 12 和 JDK 13 已經發布了,伴隨着許多對 Java 語法的小改進,好比咱們很是熟悉的 switch
:前端
switch (type) { case "all": System.out.println("列出全部帖子"); break; case "auditing": System.out.println("列出審覈中的帖子"); break; case "accepted": System.out.println("列出審覈經過的帖子"); break; case "rejected": System.out.println("列出審覈不經過的帖子"); break; default: System.out.println("參數'type'錯誤,請檢查"); break; }
switch (type) { case "all" -> System.out.println("列出全部帖子"); case "auditing" -> System.out.println("列出審覈中的帖子"); case "accepted" -> System.out.println("列出審覈經過的帖子"); case "rejected" -> System.out.println("列出審覈不經過的帖子"); default -> System.out.println("參數'type'錯誤,請檢查"); }
String value = switch (i) { case 0 -> "zero" case 1 -> "one" case 2 -> "two" default -> "many" };
新特性很美好,可是現在在業界最流行的版本依然是 JDK8,因此想要在生產環境用上這麼舒服的 switch
,目前看來仍是遙遙無期。好在咱們還有 Lambda,正所謂 「本身動手,豐衣足食」,咱們來試試能不能本身作出一個和 JDK12 & JDK13
的 swtich
相似的東西,給咱們平淡的編碼生活,加點糖。java
首先,咱們定義一個 Switch
類,而後它接收一個泛型參數,就相似與 Java 的 switch
語句,須要先接收一個參數。git
public class Switch<T> { /** * 輸入值 */ private final T input; private Switch(T input) { this.input = input; } public static <T, R> Switch<T, R> on(T value) { return new Switch<>(value); } }
經過靜態方法 on
(on(input)
能夠理解爲在 value 上作 Switch
操做),咱們能夠構造出一個 Switch
實例。而後,咱們定義一個 Predicate
,用來表示當前的條件:github
public class Switch<T> { private Predicate<T> condition; public Switch<T> is(T target) { // 判斷輸入值是否和 target 相等 condition = Predicate.isEqual(target); return this; } }
is
方法的做用就是將當前的條件 condition 定義爲 判斷 Switch 的輸入值是否和傳入的 target 相等。既然都引入條件了,天然咱們可讓用戶本身來定義條件:json
public Switch<T, R> is(T target) { // 判斷輸入值是否和 target 相等 return when(Predicate.isEqual(target)); } public Switch<T, R> when(Predicate<T> condition) { // 用戶本身設定條件 this.condition = Objects.requireNonNull(condition); return this; }
接着咱們就能夠來定義 switch
語句中的 case ... break
功能:app
public Switch<T> thenAccept(Consumer<T> action) { requireNonNullArgAndCondition(action); if (condition.test(input)) { action.accept(input); } return this; } void requireNonNullCondition() { if (condition == null) { throw new IllegalStateException("A condition must be set first"); } } void requireNonNullArgAndCondition(Object arg) { Objects.requireNonNull(arg, "Null argument " + arg.getClass().getName()); requireNonNullCondition(); }
好像有點不對勁?對哦,switch
只能知足一個 case
,若是是咱們本身來設定各類條件,可能會存在多個條件都知足的狀況 —— 那就不是咱們預期的 switch
了。因此咱們能夠定義一個 boolean
標記,用來表示用戶設定的多個條件是否存在某一個知足,若是有一個知足了,則該條件以後的鏈式方法都直接 短路處理。ide
public class Switch<T> { ... /** * 是否已經存在過知足的條件 */ private boolean met; public Switch<T, R> is(T target) { return when(Predicate.isEqual(target)); } public Switch<T, R> when(Predicate<T> condition) { // 短路處理 if (met) { return this; } this.condition = Objects.requireNonNull(condition); return this; } public Switch<T, R> thenAccept(Consumer<T> action) { // 短路處理 if (met) { return this; } requireNonNullArgAndCondition(action); if (condition.test(input)) { action.accept(input); // 標記已經存在過知足的條件 met = true; } return this; } }
好像還少了點什麼?對哦,switch
還有個 default ... break
。那咱們定義一個 elseAccept
方法,當且僅當以前沒有任何一個條件被知足時,調用這個方法:函數
public void elseAccept(Consumer<T> action) { // 以前存在被知足的條件,直接返回 if (met) { return; } Objects.requireNonNull(action); action.accept(input); }
OK,咱們來寫個小 demo 對比感覺一下:ui
// 得到前端傳遞的某個參數 String type = getType(); // 傳統 switch switch (type) { case "all": System.out.println("列出全部帖子"); break; case "auditing": System.out.println("列出審覈中的帖子"); break; case "accepted": System.out.println("列出審覈經過的帖子"); break; case "rejected": System.out.println("列出審覈不經過的帖子"); break; default: System.out.println("參數'type'錯誤,請檢查"); break; } // 咱們的 Switch Switch.on(type) .is("all") .thenAccept(t -> System.out.println("列出全部帖子")) .is("auditing") .thenAccept(t -> System.out.println("列出審覈中的帖子")) .is("accepted") .thenAccept(t -> System.out.println("列出審覈經過的帖子")) .is("rejected") .thenAccept(t -> System.out.println("列出審覈不經過的帖子")) .elseAccept(t -> System.out.println("參數'type'錯誤,請檢查"));
雖然咱們的 Switch
看起來沒有 JDK12 的 switch
那樣直觀,可是比 JDK12 以前的 switch
語句更加簡潔了 —— 並且鏈式調用,配合 Lambda,寫起來更舒服了~ 更重要的是,咱們都知道 switch
語句支持的類型有限(整數、枚舉、字符,字符串),而咱們自定義的 Switch
,支持任何類型,好比:this
Object value = getValue(); Switch.on(value) .is(null) .thenAccept(v -> System.out.println("value is null")) .is(123) .thenAccept(v -> System.out.println("value is 123")) .is("abc") .thenAccept(v -> System.out.println("value is abc")) .is(Arrays.asList(1, 2, 3)) .thenAccept(v -> System.out.println("value is [1, 2, 3]")) .elseAccept(v -> System.out.println("Unknown value"));
並且咱們還支持自定義條件語句,因此很顯然,咱們的 Switch
能夠用來代替 if-else
語句:
Object value = getValue(); Switch.on(value) .is(null) .thenAccept(v -> System.out.println("value is null")) .when(Integer.class::isInstance) .thenAccept(v -> System.out.println("value is Integer")) .when(String.class::isInstance) .thenAccept(v -> System.out.println("value is String")) .when(Boolean.class::isInstance) .thenAccept(v -> System.out.println("value is Boolean")) .elseAccept(v -> System.out.println("Unknown type of value")); // 等價的 if-else if (value == null) { System.out.println("value is null"); } else if (value instanceof Integer) { System.out.println("value is Integer"); } else if (value instanceof String) { System.out.println("value is String"); } else if (value instanceof Boolean) { System.out.println("value is Boolean"); } else { System.out.println("Unknown type of value"); }
至於哪一個更好用和閱讀起來更舒服,就 「仁者見仁,智者見智」 了。
JDK13 中,賦予了 switch
語句求值的功能 —— 咱們也能夠很容易的改造咱們的 Switch
來支持這個功能。首先,咱們對 Switch
進行抽象,並定義 ConsumptionSwitch
做爲消費用的 Switch
(即上文中實現的 Switch
),定義 EvaluationSwitch
做爲用於求值的 Switch
。 抽象 Switch
:
public abstract class Switch<T> { /** * 輸入值 */ final T input; /** * 當前的條件 */ Predicate<T> condition; /** * 是否已經存在某個條件被知足 */ boolean met; Switch(T input) { this.input = input; } /** * 在指定的值上使用 Switch,返回用於消費的 Switch 實例 */ public static <I> ConsumptionSwitch<I> on(I input) { return new ConsumptionSwitch<>(input); } /** * 在指定的輸入值上使用 Switch,返回用於求值的 Switch 實例 */ public static <I, O> EvaluationSwitch<I, O> in(I input) { return new EvaluationSwitch<>(input); } /** * 判斷輸入是否和給定的目標相等 */ protected Switch<T> is(T target) { return when(Predicate.isEqual(target)); } /** * 設定輸入值須要知足的條件 */ protected Switch<T> when(Predicate<T> condition) { // 短路處理 if (met) { return this; } this.condition = Objects.requireNonNull(condition); return this; } ...... }
用於消費的的 Switch
:
/** * 用於消費的 Switch * * @param <T> 輸入值的類型 */ public static class ConsumptionSwitch<V> extends Switch<V> { ConsumptionSwitch(V value) { super(value); } @Override public ConsumptionSwitch<V> is(V target) { super.is(target); return this; } @Override public ConsumptionSwitch<V> when(Predicate<V> condition) { super.when(condition); return this; } /** * 知足某個條件時,對輸入值進行消費操做 */ public ConsumptionSwitch<V> thenAccept(Consumer<V> action) { // 短路處理 if (met) { return this; } requireNonNullArgAndCondition(action); if (condition.test(input)) { action.accept(input); // 標記已經存在過知足的條件 met = true; } return this; } /** * 不知足任一條件時,對輸入值進行消費操做 */ public void elseAccept(Consumer<V> action) { // 以前存在被知足的條件,直接返回 if (met) { return; } Objects.requireNonNull(action); action.accept(input); } }
改造完畢,如今咱們能夠來實現用於求值的 Switch
。首先,定義一個泛化類型的返回值:
/** * 用於求值的 Switch * * @param <I> 輸入值的類型 * @param <O> 輸出值的類型 */ public static class EvaluationSwitch<I, O> extends Switch<I> { /** * 輸出 */ private O output; EvaluationSwitch(I input) { super(input); } @Override public EvaluationSwitch<I, O> is(I target) { super.is(target); return this; } @Override public EvaluationSwitch<I, O> when(Predicate<I> condition) { super.when(condition); return this; } }
而後加入兩個方法,用來知足條件時進行求值和不知足任一條件時求值:
/** * 知足某個條件時,進行求值操做 */ public EvaluationSwitch<I, O> thenGet(O value) { if (met) { return this; } requireNonNullCondition(); // 知足條件 if (condition.test(input)) { output = value; // 標記已經產生輸出值 met = true; } return this; } /** * 不知足任一條件時,進行求值操做 */ public O elseGet(O value) { return met ? output : value; }
一樣,寫個 demo 看看效果:
int num = getNum(); // 輸入 num 進行求值 String result = Switch.in(num) .is(0).thenGet("zero") .is(1).thenGet("one") .is(2).thenGet("two") .elseGet("many"); System.out.println(result);
然而,編譯不過 —— 由於推導不出返回值的類型....... Switch.input(k)
返回的是 EvaluationSwitch<Integer, Object>
,而咱們須要的是 EvaluationSwitch<Integer, String>
。沒辦法,經過一個方法轉換一下吧:
/** * 設定當前 EvaluationSwitch 的輸出值的類型 * * @param type 輸出值的類型 * @param <R> 指定的輸出值類型 * @return 當前的 EvaluationSwitch 實例 */ @SuppressWarnings("unchecked") public <R> EvaluationSwitch<I, R> out(Class<? extends R> type) { return (EvaluationSwitch<I, R>) this; }
即明確咱們要返回的類型:
int num = getNum(); String result = Switch.in(num) .out(String.class) .is(0).thenGet("zero") .is(1).thenGet("one") .is(2).thenGet("two") .elseGet("many"); System.out.println(result);
對比下 JDK13:
int num = getNum(); String value = switch (num) { case 0 -> "zero" case 1 -> "one" case 2 -> "two" default -> "many" }; System.out.println(value);
除了要明確下返回值的類型,兩者功能一致;雖然沒有 JDK13 簡潔,可是咱們的 Switch
看起來也很是的直觀。並且咱們能夠引入函數來進一步加強咱們的 Switch
的求值功能:
/** * 知足某個條件時,使用 Function 進行求值操做,當前 Switch 實例的輸入值會做爲 Function 的輸入 */ public EvaluationSwitch<I, O> thenApply(Function<I, O> mapper) { if (met) { return this; } requireNonNullArgAndCondition(mapper); if (condition.test(input)) { output = mapper.apply(input); met = true; } return this; } /** * 不知足任一條件時,使用 Function 進行求值操做,當前 Switch 實例的輸入值會做爲 Function 的輸入 */ public O elseApply(Function<I, O> mapper) { Objects.requireNonNull(mapper); return met ? output : mapper.apply(input); } /** * 知足某個條件時,使用 Supplier 進行求值操做 */ public EvaluationSwitch<I, O> thenSupply(Supplier<O> supplier) { if (met) { return this; } requireNonNullArgAndCondition(supplier); if (condition.test(input)) { output = supplier.get(); met = true; } return this; } /** * 不知足任一條件時,使用 Supplier 進行求值操做 */ public O elseSupply(Supplier<O> supplier) { Objects.requireNonNull(supplier); return met ? output : supplier.get(); }
寫個 demo:
ScheduleTypeEnum scheduleType = getScheduleType(); // 使用 if-else LocalDateTime ptTime; if (scheduleType == BY_DAY) { ptTime = LocalDateTime.now().minusDays(1); } else if (scheduleType == BY_HOUR) { ptTime = LocalDateTime.now().minusHours(1); } else if (scheduleType == BY_MINUTE) { ptTime = LocalDateTime.now().minusMinutes(1); } else { ptTime = LocalDateTime.now().minusSeconds(1); } // 使用 Java8 switch LocalDateTime ptTime; switch (scheduleType) { case BY_DAY: ptTime = LocalDateTime.now().minusDays(1); break; case BY_HOUR: ptTime = LocalDateTime.now().minusHours(1); break; case BY_MINUTE: ptTime = LocalDateTime.now().minusMinutes(1); break; default: ptTime = LocalDateTime.now().minusMinutes(1); break; } // 使用本文的求值 Switch LocalDateTime ptTime = Switch.input(scheduleType) .output(LocalDateTime.class) .is(BY_DAY) .thenSupply(() -> LocalDateTime.now().minusDays(1)) .is(BY_HOUR) .thenSupply(() -> LocalDateTime.now().minusHours(1)) .is(BY_MINUTE) .thenSupply(() -> LocalDateTime.now().minusMinutes(1)) .elseSupply(() -> LocalDateTime.now().minusSeconds(1));
之因此這裏使用 thenSupply
而不是直接使用 thenGet
,是由於使用函數能夠 惰性求值。
最終的 Switch
代碼可見:Switch.java
is
用來判斷 輸入 是否和某個 特定值 相等,那若是須要判斷 輸入 是否在某個 一羣值 中呢?很簡單,一樣基於 when
方法:
/** * 判斷輸入是否存在給定的一羣值中 * * @param values 給定的一羣值 * @return 當前 Switch 實例 */ protected Switch<T> isIn(T... values) { Objects.requireNonNull(values); return when(e -> { for (T value : values) { if (Objects.equals(e, value)) { return true; } } return false; }); }
你們還有什麼改進和想法呢,歡迎評論交流~