java equal switch_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 & JDK13 的 swtich 类似的东西,给我们平淡的编码生活,加点糖。

实现

对标 JDK12 的 switch

首先,我们定义一个 Switch 类,然后它接收一个泛型参数,就类似与 Java 的 switch 语句,需要先接收一个参数。

public class Switch {

/**

* 输入值

*/

private final T input;

private Switch(T input) {

this.input = input;

}

public static Switch on(T value) {

return new Switch<>(value);

}

}

通过静态方法 on(on(input) 可以理解为在 value 上做 Switch 操作),我们可以构造出一个 Switch 实例。然后,我们定义一个 Predicate,用来表示当前的条件:

public class Switch {

private Predicate condition;

public Switch is(T target) {

// 判断输入值是否和 target 相等

condition = Predicate.isEqual(target);

return this;

}

}

is 方法的作用就是将当前的条件 condition 定义为 判断 Switch 的输入值是否和传入的 target 相等。既然都引入条件了,自然我们可以让用户自己来定义条件:

public Switch is(T target) {

// 判断输入值是否和 target 相等

return when(Predicate.isEqual(target));

}

public Switch when(Predicate condition) {

// 用户自己设定条件

this.condition = Objects.requireNonNull(condition);

return this;

}

接着我们就可以来定义 switch 语句中的 case ... break 功能:

public Switch thenAccept(Consumer 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 标记,用来表示用户设定的多个条件是否存在某一个满足,如果有一个满足了,则该条件之后的链式方法都直接 短路处理。

public class Switch {

...

/**

* 是否已经存在过满足的条件

*/

private boolean met;

public Switch is(T target) {

return when(Predicate.isEqual(target));

}

public Switch when(Predicate condition) {

// 短路处理

if (met) { return this; }

this.condition = Objects.requireNonNull(condition);

return this;

}

public Switch thenAccept(Consumer 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 action) {

// 之前存在被满足的条件,直接返回

if (met) { return; }

Objects.requireNonNull(action);

action.accept(input);

}

OK,我们来写个小 demo 对比感受一下:

// 获得前端传递的某个参数

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,支持任何类型,比如:

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 {

/**

* 输入值

*/

final T input;

/**

* 当前的条件

*/

Predicate condition;

/**

* 是否已经存在某个条件被满足

*/

boolean met;

Switch(T input) {

this.input = input;

}

/**

* 在指定的值上使用 Switch,返回用于消费的 Switch 实例

*/

public static ConsumptionSwitch on(I input) {

return new ConsumptionSwitch<>(input);

}

/**

* 在指定的输入值上使用 Switch,返回用于求值的 Switch 实例

*/

public static EvaluationSwitch in(I input) {

return new EvaluationSwitch<>(input);

}

/**

* 判断输入是否和给定的目标相等

*/

protected Switch is(T target) {

return when(Predicate.isEqual(target));

}

/**

* 设定输入值需要满足的条件

*/

protected Switch when(Predicate condition) {

// 短路处理

if (met) { return this; }

this.condition = Objects.requireNonNull(condition);

return this;

}

......

}

用于消费的的 Switch:

/**

* 用于消费的 Switch

*

* @param 输入值的类型

*/

public static class ConsumptionSwitch extends Switch {

ConsumptionSwitch(V value) {

super(value);

}

@Override

public ConsumptionSwitch is(V target) {

super.is(target);

return this;

}

@Override

public ConsumptionSwitch when(Predicate condition) {

super.when(condition);

return this;

}

/**

* 满足某个条件时,对输入值进行消费操作

*/

public ConsumptionSwitch thenAccept(Consumer action) {

// 短路处理

if (met) { return this; }

requireNonNullArgAndCondition(action);

if (condition.test(input)) {

action.accept(input);

// 标记已经存在过满足的条件

met = true;

}

return this;

}

/**

* 不满足任一条件时,对输入值进行消费操作

*/

public void elseAccept(Consumer action) {

// 之前存在被满足的条件,直接返回

if (met) { return; }

Objects.requireNonNull(action);

action.accept(input);

}

}

改造完毕,现在我们可以来实现用于求值的 Switch。首先,定义一个泛化类型的返回值:

/**

* 用于求值的 Switch

*

* @param 输入值的类型

* @param 输出值的类型

*/

public static class EvaluationSwitch extends Switch {

/**

* 输出

*/

private O output;

EvaluationSwitch(I input) {

super(input);

}

@Override

public EvaluationSwitch is(I target) {

super.is(target);

return this;

}

@Override

public EvaluationSwitch when(Predicate condition) {

super.when(condition);

return this;

}

}

然后加入两个方法,用来满足条件时进行求值和不满足任一条件时求值:

/**

* 满足某个条件时,进行求值操作

*/

public EvaluationSwitch 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,而我们需要的是 EvaluationSwitch。没办法,通过一个方法转换一下吧:

/**

* 设定当前 EvaluationSwitch 的输出值的类型

*

* @param type 输出值的类型

* @param 指定的输出值类型

* @return 当前的 EvaluationSwitch 实例

*/

@SuppressWarnings("unchecked")

public EvaluationSwitch out(Class extends R> type) {

return (EvaluationSwitch) 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 thenApply(Function 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 mapper) {

Objects.requireNonNull(mapper);

return met ? output : mapper.apply(input);

}

/**

* 满足某个条件时,使用 Supplier 进行求值操作

*/

public EvaluationSwitch thenSupply(Supplier supplier) {

if (met) { return this; }

requireNonNullArgAndCondition(supplier);

if (condition.test(input)) {

output = supplier.get();

met = true;

}

return this;

}

/**

* 不满足任一条件时,使用 Supplier 进行求值操作

*/

public O elseSupply(Supplier 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 isIn(T... values) {

Objects.requireNonNull(values);

return when(e -> {

for (T value : values) {

if (Objects.equals(e, value)) {

return true;

}

}

return false;

});

}

大家还有什么改进和想法呢,欢迎评论交流~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值