背景
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;
});
}
大家还有什么改进和想法呢,欢迎评论交流~