diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/lang/Match.java b/hutool-core/src/main/java/org/dromara/hutool/core/lang/Match.java new file mode 100644 index 000000000..c336c9fd0 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/lang/Match.java @@ -0,0 +1,289 @@ +package org.dromara.hutool.core.lang; + + +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +/** + * Match类实现了一个类似于模式匹配的结构,允许根据给定的条件(matchValue)返回相应的结果(returnValue)。 + * 它提供了多种静态工厂方法来创建Match实例,包括空实例、基于值和基于Supplier的实例。 + * 该类支持map和flatMap操作,可以对matchValue和returnValue应用转换函数。 + * 还提供了条件匹配的方法,可以根据给定的谓词返回新的Match实例或保持当前状态。 + * 此外,Match类实现了处理缺失值的机制,提供了orElse、orElseGet和orElseThrow方法来处理返回值的缺失情况。 + *
+ * 
+ *      MatchV2.of(matchString, "def")
+ *          .caseNull("is null")
+ *          .caseValue("null", "null")
+ *          .caseValue(String::isEmpty, "is empty")
+ *          .caseValue(StringUtils::isBlank, ()->"is blank");
+ * 
+ * 
+ * + * @param 用于匹配的值类型. + * @param 用于返回的值类型. + * @author tanglt + * @version jdk 8 + */ +public class Match { + + + /** + * 判断的元素 + */ + private final T matchValue; + + /** + * 返回的元素 + */ + private final R returnValue; + + /** + * 是否命中条件 + */ + private final boolean hit; + + /** + * 默认返回的元素 + */ + private final R defValue; + + /** + * {@code Match}的构造函数 + * + */ + private Match(final T matchValue, final R defValue) { + this.matchValue = matchValue; + this.returnValue = null; + this.defValue = defValue; + this.hit = false; + } + /** + * {@code Match}的构造函数 + * + */ + private Match(final T matchValue, final R returnValue, final R defValue, boolean hit) { + this.matchValue = matchValue; + this.returnValue = returnValue; + this.defValue = defValue; + this.hit = hit; + } + + /** + * 传入匹配的值和默认返回值,包装一个Match + * + * @param matchValue 匹配的值 + * @param defValue 默认返回值 + * @return {@code Match} + */ + public static Match of(final T matchValue,final R defValue) { + return new Match<>(matchValue,defValue); + } + + + /** + * 传入匹配的值,无默认返回值,包装一个Match + * + * @param matchValue 匹配的值 + * @return {@code Match} + */ + public static Match of(final T matchValue) { + return new Match<>(matchValue,null); + } + + /** + * 可以拼接两个Match,如果第一个Match条件命中,则后一个Match无效。反之,后一个条件生效。 + * + * @param mapper 需要合并的第二个Match的BiFunction,第一个值是前一个Match的匹配值,第二个值是前一个Match的返回值 + * @param 新的匹配值类型 + * @return {@code Match} + */ + public Match flatMap(final BiFunction> mapper) { + Objects.requireNonNull(mapper); + Match mrMatch = mapper.apply(this.matchValue ,isPresent()?this.returnValue:this.defValue); + if (isPresent()) { + return new Match<>(mrMatch.matchValue,this.returnValue,mrMatch.defValue,true); + } else { + return mrMatch; + } + } + + /** + * 映射匹配值。 + * + * @param matchMapper 新的匹配值映射 + * @param 新的匹配值类型 + * @return {@code Match} + */ + public Match mapMatch(final Function matchMapper) { + Objects.requireNonNull(matchMapper); + return new Match<>(matchMapper.apply(this.matchValue),this.returnValue,this.defValue,this.hit); + } + + /** + * 映射返回值。如果返回值还没有,则映射默认返回值 + * + * @param returnMapper 新的返回值映射 + * @param 新的返回值类型 + * @return {@code Match} + */ + public Match map(final Function returnMapper) { + Objects.requireNonNull(returnMapper); + if(isPresent()){ + F returnValue = returnMapper.apply(this.returnValue); + return new Match<>(this.matchValue,returnValue,returnValue,this.hit); + }else { + F defValue = returnMapper.apply(this.defValue); + return new Match<>(this.matchValue,null,defValue,this.hit); + } + } + + /** + * 判断是否和匹配值相等,并指定返回的值 + * + * @param caseValue 判断的值 + * @param returnValue 指定返回的值 + * @return {@code Match} + */ + public Match caseValue(final T caseValue, final R returnValue) { + Objects.requireNonNull(caseValue); + if(isPresent()||!Objects.equals(this.matchValue,caseValue)){ + return this; + } + return new Match<>(this.matchValue,returnValue,this.defValue,true); + } + + /** + * 使用谓词判断是否和匹配值相等,并指定返回的值。可以在类似BigDecimal相等判断的时候使用 + * + * @param predicate 谓词条件 + * @param returnValue 指定返回的值 + * @return {@code Match} + */ + public Match caseValue(final Predicate predicate, final R returnValue) { + Objects.requireNonNull(predicate); + if(isPresent()||!predicate.test(this.matchValue)){ + return this; + } + return new Match<>(this.matchValue,returnValue,this.defValue,true); + } + + /** + * 使用谓词判断是否和匹配值相等,并指定返回的值的。使用Supplier可以在复杂计算返回值的情况下延迟计算,提高性能和规避空异常 + * + * @param predicate 谓词条件 + * @param supplier 指定返回值Supplier + * @return {@code Match} + */ + public Match caseValue(final Predicate predicate, final Supplier supplier) { + Objects.requireNonNull(predicate); + Objects.requireNonNull(supplier); + if(isPresent()||!predicate.test(this.matchValue)){ + return this; + } + return new Match<>(this.matchValue,supplier.get(),this.defValue,true); + } + + + /** + * 检查null的判断条件. + * + * @param returnValue 指定返回的值 + * @return {@code Match} + */ + public Match caseNull(final R returnValue) { + if(isPresent()||!Objects.isNull(this.matchValue)){ + return this; + } + return new Match<>(null,returnValue,this.defValue,true); + } + + /** + * 检查null的判断条件.使用Supplier返回,可以在复杂计算返回值的情况下延迟计算 + * + * @param supplier 指定返回值的Supplier + * @return {@code Match} + */ + public Match caseNull(final Supplier supplier) { + Objects.requireNonNull(supplier); + if(isPresent()||!Objects.isNull(this.matchValue)){ + return this; + } + return new Match<>(null,supplier.get(),this.defValue,true); + } + + /** + * 返回条件命中的值或者默认值 + * @return 条件命中的值或者默认值的类型 + */ + public R get() { + return isPresent() ? this.returnValue : this.defValue; + } + + /** + * 返回条件命中的值。如果没有命中,返回传入值 + * @param other 没有命中,返回的值 + * @return 条件命中的值或者默认值的类型 + */ + public R orElse(final R other) { + return isPresent() ? this.returnValue : other; + } + + + /** + * 返回条件命中的值。如果没有命中,返回Supplier的值,可以在复杂计算返回值的情况下延迟计算 + * @param supplier 没有命中,返回值的Supplier + * @return 条件命中的值或者默认值的类型 + */ + public R orElseGet(final Supplier supplier) { + return isPresent() ? this.returnValue : supplier.get(); + } + + + /** + * 返回条件命中的值。如果没有命中,返回Supplier 可以抛出异常 + * @param exceptionSupplier 没有命中,抛出异常 + * @return 条件命中的值或者抛出异常 + */ + public R orElseThrow(final Supplier exceptionSupplier) throws X{ + if (isPresent()) { + return this.returnValue; + } + throw exceptionSupplier.get(); + } + + /** + * 返回条件是否命中结果 + * + * @return boolean 条件是否命中结果 + */ + public boolean isPresent() { + return this.hit; + } + + + @Override + public boolean equals(Object o) { + return o == this || ( o instanceof Match && Objects.equals(o.toString(),this.toString())); + } + + @Override + public int hashCode() { + return Objects.hashCode(this.toString()); + } + + @Override + public String toString() { + return "matchValue(" + +this.matchValue+ + "),returnValue(" + +this.returnValue+ + "),hit(" + +this.hit+ + "),defValue(" + +this.defValue+")"; + } +} diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/lang/MatchTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/lang/MatchTest.java new file mode 100644 index 000000000..76754e299 --- /dev/null +++ b/hutool-core/src/test/java/org/dromara/hutool/core/lang/MatchTest.java @@ -0,0 +1,103 @@ +package org.dromara.hutool.core.lang; + + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.util.StringUtils; + +import java.util.NoSuchElementException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + + +/** + * {@link Match}的单元测试 + * + * @author tanglt + */ +public class MatchTest { + + @Test + public void ofNull() { + Match match = stringMatch(null); + assertEquals(match.get(),"is null"); + } + + @Test + public void ofNullString() { + Match match = stringMatch("null"); + assertEquals(match.get(),"null"); + } + + @Test + public void ofPredicate() { + Match match = stringMatch(""); + assertEquals(match.get(),"is empty"); + } + + @Test + public void ofPredicateAndSupplier() { + Match match = stringMatch(" "); + assertEquals(match.get(),"is blank"); + } + + @Test + public void ofDefault() { + Match match = stringMatch("a"); + assertEquals(match.get(),"def"); + } + + @Test + public void ofFlatMap() { + + + /** + * String a = "0"; + * if... + * + * else if(Objects.equals(Integer.valueOf(a),0)){ + * caseValue = "zero"; + * } + */ + Match match = stringMatch("0") + .flatMap((m, r) -> Match.of(Integer.valueOf(m), r)) + .caseValue(0, "zero"); + assertEquals(match.get(),"zero"); + } + + @Test + public void ofOrElse() { + Match match = stringMatch("a"); + assertEquals(match.orElse("def2"),"def2"); + } + + @Test + public void ofOrElseThrow() { + assertThrows(NoSuchElementException.class, + ()->stringMatch("a").orElseThrow(NoSuchElementException::new)); + } + + + + private Match stringMatch(String matchString){ + + /** + * String caseValue = "def"; + * if(Objects.isNull(matchString)){ + * caseValue = "null"; + * } else if(Objects.equals(matchString,"null")){ + * caseValue = "is null"; + * } else if(StringUtils.isBlank(matchString)){ + * caseValue = "is empty"; + * } else if(StringUtils.isBlank(matchString)){ + * caseValue = ()->"is blank"; + * } + */ + + return Match.of(matchString, "def") + .caseNull("is null") + .caseValue("null", "null") + .caseValue(String::isEmpty, "is empty") + .caseValue(StringUtils::isBlank, ()->"is blank"); + } +}