Java8(7):自制多糖 switch

背景

JDK 12 和 JDK 13 已經發布了,伴隨着許多對 Java 語法的小改進,好比咱們很是熟悉的 switch前端

JDK12 以前

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;
}

JDK12

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'錯誤,請檢查");
}

JDK13

String value = switch (i) {
    case  0 -> "zero"
    case  1 -> "one"
    case  2 -> "two"
    default -> "many"
};

新特性很美好,可是現在在業界最流行的版本依然是 JDK8,因此想要在生產環境用上這麼舒服的 switch,目前看來仍是遙遙無期。好在咱們還有 Lambda,正所謂 「本身動手,豐衣足食」,咱們來試試能不能本身作出一個和 JDK12 & JDK13swtich 相似的東西,給咱們平淡的編碼生活,加點糖。java

實現

對標 JDK12 的 switch

首先,咱們定義一個 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);
    }
}

經過靜態方法 onon(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

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

擴展

isIn 操做

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;
    });
}

你們還有什麼改進和想法呢,歡迎評論交流~

相關文章
相關標籤/搜索