From cff7c1cedddf04d5249d21e9b0440ffc3bf7abe8 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 7 Oct 2021 22:36:41 +0800 Subject: [PATCH] add Opt --- CHANGELOG.md | 3 +- .../cn/hutool/core/bean/OptionalBean.java | 83 ++-- .../main/java/cn/hutool/core/lang/Opt.java | 417 ++++++++++++++++++ .../OptTest.java} | 54 ++- 4 files changed, 498 insertions(+), 59 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/lang/Opt.java rename hutool-core/src/test/java/cn/hutool/core/{bean/OptionalBeanTest.java => lang/OptTest.java} (55%) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8e9e689c..527991a41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.7.14 (2021-09-30) +# 5.7.14 (2021-10-07) ### 🐣新特性 * 【extra 】 修复HttpCookie设置cookies的方法,不符合RFC6265规范问题(issue#I4B70D@Gitee) @@ -18,6 +18,7 @@ * 【core 】 优化Base64.isBase64方法:减少一次多余的判断(pr#1860@Github) * 【core 】 优化Base64.isBase64方法:减少一次多余的判断(pr#1860@Github) * 【cache 】 优化FIFOCache未设置过期策略时,无需遍历判断过期对象(pr#425@Gitee) +* 【core 】 增加Opt类(pr#426@Gitee) ### 🐞Bug修复 * 【http 】 修复HttpCookie设置cookies的方法,不符合RFC6265规范问题(pr#418@Gitee) diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/OptionalBean.java b/hutool-core/src/main/java/cn/hutool/core/bean/OptionalBean.java index 97949a8b1..f2863ecd3 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/OptionalBean.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/OptionalBean.java @@ -24,11 +24,11 @@ */ package cn.hutool.core.bean; +import cn.hutool.core.lang.func.VoidFunc0; import cn.hutool.core.util.StrUtil; import java.util.NoSuchElementException; import java.util.Objects; -import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -36,14 +36,17 @@ import java.util.function.Supplier; import java.util.stream.Stream; /** - * 复制jdk16中的Optional,以及自己进行了一点调整和新增,比jdk8中的Optional多了几个实用的函数 + * 复制jdk16中的Optional,以及自己进行了一点调整和新增,比jdk8中的Optional多了几个实用的函数
+ * 详细见:https://gitee.com/dromara/hutool/pulls/426 * * @author VampireAchao * @see java.util.Optional + * @deprecated 请使用 {@link cn.hutool.core.lang.Opt} */ -public final class OptionalBean { +@Deprecated +public class OptionalBean { /** - * 一个空的{@code Opt} + * 一个空的{@code OptionalBean} */ private static final OptionalBean EMPTY = new OptionalBean<>(null); @@ -53,7 +56,7 @@ public final class OptionalBean { private final T value; /** - * {@code Opt}的构造函数 + * {@code OptionalBean}的构造函数 * * @param value 包裹里的元素 */ @@ -62,7 +65,7 @@ public final class OptionalBean { } /** - * 返回一个空的{@code Opt} + * 返回一个空的{@code OptionalBean} */ public static OptionalBean empty() { @SuppressWarnings("unchecked") @@ -71,11 +74,11 @@ public final class OptionalBean { } /** - * 返回一个包裹里元素不可能为空的{@code Opt} + * 返回一个包裹里元素不可能为空的{@code OptionalBean} * * @param value 包裹里的元素 * @param 包裹里元素的类型 - * @return 一个包裹里元素不可能为空的 {@code Opt} + * @return 一个包裹里元素不可能为空的 {@code OptionalBean} * @throws NullPointerException 如果传入的元素为空,抛出 {@code NPE} */ public static OptionalBean of(T value) { @@ -83,11 +86,11 @@ public final class OptionalBean { } /** - * 返回一个包裹里元素可能为空的{@code Opt} + * 返回一个包裹里元素可能为空的{@code OptionalBean} * * @param value 传入需要包裹的元素 * @param 包裹里元素的类型 - * @return 一个包裹里元素可能为空的 {@code Opt} + * @return 一个包裹里元素可能为空的 {@code OptionalBean} */ public static OptionalBean ofNullable(T value) { return value == null ? empty() @@ -95,18 +98,18 @@ public final class OptionalBean { } /** - * 返回一个包裹里元素可能为空的{@code Opt},额外判断了空字符串的情况 + * 返回一个包裹里元素可能为空的{@code OptionalBean},额外判断了空字符串的情况 * * @param value 传入需要包裹的元素 * @param 包裹里元素的类型 - * @return 一个包裹里元素可能为空,或者为空字符串的 {@code Opt} + * @return 一个包裹里元素可能为空,或者为空字符串的 {@code OptionalBean} */ public static OptionalBean ofBlankAble(T value) { return StrUtil.isBlankIfStr(value) ? empty() : new OptionalBean<>(value); } /** - * 返回包裹里的元素,取不到则为{@code null},注意!!!此处和{@link Optional#get()}不同的一点是本方法并不会抛出{@code NoSuchElementException} + * 返回包裹里的元素,取不到则为{@code null},注意!!!此处和{@link java.util.Optional#get()}不同的一点是本方法并不会抛出{@code NoSuchElementException} * 如果元素为空,则返回{@code null},如果需要一个绝对不能为{@code null}的值,则使用{@link #orElseThrow()} * * @return 包裹里的元素,有可能为{@code null} @@ -131,7 +134,7 @@ public final class OptionalBean { * 判断包裹里元素的值是否不存在,不存在为 {@code true},否则为{@code false} * * @return 包裹里元素的值不存在 则为 {@code true},否则为{@code false} - * @since 11 这是jdk11{@link Optional}中的新函数 + * @since 11 这是jdk11{@link java.util.Optional}中的新函数 */ public boolean isEmpty() { return value == null; @@ -144,7 +147,7 @@ public final class OptionalBean { * @throws NullPointerException 如果包裹里的值存在,但你传入的操作为{@code null}时抛出 * @apiNote 例如如果值存在就打印结果 *
{@code
-	 * Opt.ofNullable("Hello Hutool!").ifPresent(System.out::println);
+	 * Opt.ofNullable("Hello Hutool!").ifPresent(Console::log);
 	 * }
*/ public void ifPresent(Consumer action) { @@ -155,32 +158,32 @@ public final class OptionalBean { /** * 如果包裹里的值存在,就执行传入的值存在时的操作({@link Consumer#accept}) - * 否则执行传入的值不存在时的操作({@link Runnable}中的{@link Runnable#run}) + * 否则执行传入的值不存在时的操作({@link VoidFunc0}中的{@link VoidFunc0#call()}) * * @param action 包裹里的值存在时的操作 * @param emptyAction 包裹里的值不存在时的操作 * @throws NullPointerException 如果包裹里的值存在时,执行的操作为 {@code null}, 或者包裹里的值不存在时的操作为 {@code null},则抛出{@code NPE} - * @apiNote 例如值存在就打印对应的值,不存在则用{@code System.err.println}打印另一句字符串 + * @apiNote 例如值存在就打印对应的值,不存在则用{@code Console.error}打印另一句字符串 *
{@code
-	 * Opt.ofNullable("Hello Hutool!").ifPresentOrElse(System.out::println, () -> System.err.println("Ops!Something is wrong!"));
+	 * Opt.ofNullable("Hello Hutool!").ifPresentOrElse(Console::log, () -> Console.error("Ops!Something is wrong!"));
 	 * }
- * @since 9 这是jdk9{@link Optional}中的新函数 + * @since 9 这是jdk9{@link java.util.Optional}中的新函数 */ - public void ifPresentOrElse(Consumer action, Runnable emptyAction) { + public void ifPresentOrElse(Consumer action, VoidFunc0 emptyAction) { if (value != null) { action.accept(value); } else { - emptyAction.run(); + emptyAction.callWithRuntimeException(); } } /** * 判断包裹里的值存在并且与给定的条件是否满足 ({@link Predicate#test}执行结果是否为true) * 如果满足条件则返回本身 - * 不满足条件或者元素本身为空时返回一个返回一个空的{@code Opt} + * 不满足条件或者元素本身为空时返回一个返回一个空的{@code OptionalBean} * * @param predicate 给定的条件 - * @return 如果满足条件则返回本身, 不满足条件或者元素本身为空时返回一个返回一个空的{@code Opt} + * @return 如果满足条件则返回本身, 不满足条件或者元素本身为空时返回一个返回一个空的{@code OptionalBean} * @throws NullPointerException 如果给定的条件为 {@code null},抛出{@code NPE} */ public OptionalBean filter(Predicate predicate) { @@ -193,13 +196,13 @@ public final class OptionalBean { } /** - * 如果包裹里的值存在,就执行传入的操作({@link Function#apply})并返回一个包裹了该操作返回值的{@code Opt} - * 如果不存在,返回一个空的{@code Opt} + * 如果包裹里的值存在,就执行传入的操作({@link Function#apply})并返回一个包裹了该操作返回值的{@code OptionalBean} + * 如果不存在,返回一个空的{@code OptionalBean} * * @param mapper 值存在时执行的操作 * @param 操作返回值的类型 - * @return 如果包裹里的值存在,就执行传入的操作({@link Function#apply})并返回一个包裹了该操作返回值的{@code Opt}, - * 如果不存在,返回一个空的{@code Opt} + * @return 如果包裹里的值存在,就执行传入的操作({@link Function#apply})并返回一个包裹了该操作返回值的{@code OptionalBean}, + * 如果不存在,返回一个空的{@code OptionalBean} * @throws NullPointerException 如果给定的操作为 {@code null},抛出 {@code NPE} */ public OptionalBean map(Function mapper) { @@ -213,13 +216,13 @@ public final class OptionalBean { /** * 如果包裹里的值存在,就执行传入的操作({@link Function#apply})并返回该操作返回值 - * 如果不存在,返回一个空的{@code Opt} - * 和 {@link OptionalBean#map}的区别为 传入的操作返回值必须为 {@link OptionalBean} + * 如果不存在,返回一个空的{@code OptionalBean} + * 和 {@link OptionalBean#map}的区别为 传入的操作返回值必须为 OptionalBean * * @param mapper 值存在时执行的操作 * @param 操作返回值的类型 * @return 如果包裹里的值存在,就执行传入的操作({@link Function#apply})并返回该操作返回值 - * 如果不存在,返回一个空的{@code Opt} + * 如果不存在,返回一个空的{@code OptionalBean} * @throws NullPointerException 如果给定的操作为 {@code null}或者给定的操作执行结果为 {@code null},抛出 {@code NPE} */ public OptionalBean flatMap(Function> mapper) { @@ -235,7 +238,7 @@ public final class OptionalBean { /** * 如果包裹里元素的值存在,就执行对应的操作,并返回本身 - * 如果不存在,返回一个空的{@code Opt} + * 如果不存在,返回一个空的{@code OptionalBean} * * @param action 值存在时执行的操作 * @throws NullPointerException 如果值存在,并且传入的操作为 {@code null} @@ -252,11 +255,11 @@ public final class OptionalBean { } /** - * 如果包裹里元素的值存在,就返回本身,如果不存在,则使用传入的操作执行后获得的 {@code Opt} + * 如果包裹里元素的值存在,就返回本身,如果不存在,则使用传入的操作执行后获得的 {@code OptionalBean} * - * @return 如果包裹里元素的值存在,就返回本身,如果不存在,则使用传入的函数执行后获得的 {@code Opt} + * @return 如果包裹里元素的值存在,就返回本身,如果不存在,则使用传入的函数执行后获得的 {@code OptionalBean} * @throws NullPointerException 如果传入的操作为空,或者传入的操作执行后返回值为空,则抛出 {@code NPE} - * @since 9 这是jdk9{@link Optional}中的新函数 + * @since 9 这是jdk9{@link java.util.Optional}中的新函数 */ public OptionalBean or(Supplier> supplier) { Objects.requireNonNull(supplier); @@ -274,12 +277,12 @@ public final class OptionalBean { * 否则返回一个空元素的 {@link Stream} * * @return 返回一个包含该元素的 {@link Stream}或空的 {@link Stream} - * @apiNote 该方法能将 {@link OptionalBean} 中的元素传递给 {@link Stream} + * @apiNote 该方法能将 OptionalBean 中的元素传递给 {@link Stream} *
{@code
 	 *     Stream> os = ..
 	 *     Stream s = os.flatMap(Opt::stream)
 	 * }
- * @since 9 这是jdk9{@link Optional}中的新函数 + * @since 9 这是jdk9{@link java.util.Optional}中的新函数 */ public Stream stream() { if (isEmpty()) { @@ -315,7 +318,7 @@ public final class OptionalBean { * * @return 返回一个不为 {@code null} 的包裹里的值 * @throws NoSuchElementException 如果包裹里的值不存在则抛出该异常 - * @since 10 这是jdk10{@link Optional}中的新函数 + * @since 10 这是jdk10{@link java.util.Optional}中的新函数 */ public T orElseThrow() { if (value == null) { @@ -366,16 +369,16 @@ public final class OptionalBean { } /** - * 判断传入参数是否与 {@code Opt}相等 + * 判断传入参数是否与 {@code OptionalBean}相等 * 在以下情况下返回true *
    - *
  • 它也是一个 {@code Opt} 并且 + *
  • 它也是一个 {@code OptionalBean} 并且 *
  • 它们包裹住的元素都为空 或者 *
  • 它们包裹住的元素之间相互 {@code equals()} *
* * @param obj 一个要用来判断是否相等的参数 - * @return 如果传入的参数也是一个 {@code Opt}并且它们包裹住的元素都为空 + * @return 如果传入的参数也是一个 {@code OptionalBean}并且它们包裹住的元素都为空 * 或者它们包裹住的元素之间相互 {@code equals()} 就返回{@code true} * 否则返回 {@code false} */ diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java b/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java new file mode 100644 index 000000000..2b4fdb081 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java @@ -0,0 +1,417 @@ +/* + * Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package cn.hutool.core.lang; + +import cn.hutool.core.lang.func.VoidFunc0; +import cn.hutool.core.util.StrUtil; + +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Stream; + +/** + * 复制jdk16中的Optional,以及自己进行了一点调整和新增,比jdk8中的Optional多了几个实用的函数
+ * 详细见:https://gitee.com/dromara/hutool/pulls/426 + * + * @author VampireAchao + * @see java.util.Optional + */ +public class Opt { + /** + * 一个空的{@code Opt} + */ + private static final Opt EMPTY = new Opt<>(null); + + /** + * 包裹里实际的元素 + */ + private final T value; + + /** + * {@code Opt}的构造函数 + * + * @param value 包裹里的元素 + */ + private Opt(T value) { + this.value = value; + } + + /** + * 返回一个空的{@code Opt} + */ + public static Opt empty() { + @SuppressWarnings("unchecked") + Opt t = (Opt) EMPTY; + return t; + } + + /** + * 返回一个包裹里元素不可能为空的{@code Opt} + * + * @param value 包裹里的元素 + * @param 包裹里元素的类型 + * @return 一个包裹里元素不可能为空的 {@code Opt} + * @throws NullPointerException 如果传入的元素为空,抛出 {@code NPE} + */ + public static Opt of(T value) { + return new Opt<>(Objects.requireNonNull(value)); + } + + /** + * 返回一个包裹里元素可能为空的{@code Opt} + * + * @param value 传入需要包裹的元素 + * @param 包裹里元素的类型 + * @return 一个包裹里元素可能为空的 {@code Opt} + */ + public static Opt ofNullable(T value) { + return value == null ? empty() + : new Opt<>(value); + } + + /** + * 返回一个包裹里元素可能为空的{@code Opt},额外判断了空字符串的情况 + * + * @param value 传入需要包裹的元素 + * @param 包裹里元素的类型 + * @return 一个包裹里元素可能为空,或者为空字符串的 {@code Opt} + */ + public static Opt ofBlankAble(T value) { + return StrUtil.isBlankIfStr(value) ? empty() : new Opt<>(value); + } + + /** + * 返回包裹里的元素,取不到则为{@code null},注意!!!此处和{@link java.util.Optional#get()}不同的一点是本方法并不会抛出{@code NoSuchElementException} + * 如果元素为空,则返回{@code null},如果需要一个绝对不能为{@code null}的值,则使用{@link #orElseThrow()} + * + * @return 包裹里的元素,有可能为{@code null} + * @apiNote 如果需要一个绝对不能为 {@code null}的值,则使用{@link #orElseThrow()} + * 做此处修改的原因是,有时候我们确实需要返回一个null给前端,并且这样的时候并不少见 + * 而使用 {@code .orElse(null)}需要写整整12个字符,用{@code .get()}就只需要6个啦 + */ + public T get() { + return value; + } + + /** + * 判断包裹里元素的值是否存在,存在为 {@code true},否则为{@code false} + * + * @return 包裹里元素的值存在为 {@code true},否则为{@code false} + */ + public boolean isPresent() { + return value != null; + } + + /** + * 判断包裹里元素的值是否不存在,不存在为 {@code true},否则为{@code false} + * + * @return 包裹里元素的值不存在 则为 {@code true},否则为{@code false} + * @since 11 这是jdk11{@link java.util.Optional}中的新函数 + */ + public boolean isEmpty() { + return value == null; + } + + /** + * 如果包裹里的值存在,就执行传入的操作({@link Consumer#accept}) + * + * @param action 你想要执行的操作 + * @throws NullPointerException 如果包裹里的值存在,但你传入的操作为{@code null}时抛出 + * @apiNote 例如如果值存在就打印结果 + *
{@code
+	 * Opt.ofNullable("Hello Hutool!").ifPresent(Console::log);
+	 * }
+ */ + public void ifPresent(Consumer action) { + if (value != null) { + action.accept(value); + } + } + + /** + * 如果包裹里的值存在,就执行传入的值存在时的操作({@link Consumer#accept}) + * 否则执行传入的值不存在时的操作({@link VoidFunc0}中的{@link VoidFunc0#call()}) + * + * @param action 包裹里的值存在时的操作 + * @param emptyAction 包裹里的值不存在时的操作 + * @throws NullPointerException 如果包裹里的值存在时,执行的操作为 {@code null}, 或者包裹里的值不存在时的操作为 {@code null},则抛出{@code NPE} + * @apiNote 例如值存在就打印对应的值,不存在则用{@code Console.error}打印另一句字符串 + *
{@code
+	 * Opt.ofNullable("Hello Hutool!").ifPresentOrElse(Console::log, () -> Console.error("Ops!Something is wrong!"));
+	 * }
+ * @since 9 这是jdk9{@link java.util.Optional}中的新函数 + */ + public void ifPresentOrElse(Consumer action, VoidFunc0 emptyAction) { + if (value != null) { + action.accept(value); + } else { + emptyAction.callWithRuntimeException(); + } + } + + /** + * 判断包裹里的值存在并且与给定的条件是否满足 ({@link Predicate#test}执行结果是否为true) + * 如果满足条件则返回本身 + * 不满足条件或者元素本身为空时返回一个返回一个空的{@code Opt} + * + * @param predicate 给定的条件 + * @return 如果满足条件则返回本身, 不满足条件或者元素本身为空时返回一个返回一个空的{@code Opt} + * @throws NullPointerException 如果给定的条件为 {@code null},抛出{@code NPE} + */ + public Opt filter(Predicate predicate) { + Objects.requireNonNull(predicate); + if (isEmpty()) { + return this; + } else { + return predicate.test(value) ? this : empty(); + } + } + + /** + * 如果包裹里的值存在,就执行传入的操作({@link Function#apply})并返回一个包裹了该操作返回值的{@code Opt} + * 如果不存在,返回一个空的{@code Opt} + * + * @param mapper 值存在时执行的操作 + * @param 操作返回值的类型 + * @return 如果包裹里的值存在,就执行传入的操作({@link Function#apply})并返回一个包裹了该操作返回值的{@code Opt}, + * 如果不存在,返回一个空的{@code Opt} + * @throws NullPointerException 如果给定的操作为 {@code null},抛出 {@code NPE} + */ + public Opt map(Function mapper) { + Objects.requireNonNull(mapper); + if (isEmpty()) { + return empty(); + } else { + return Opt.ofNullable(mapper.apply(value)); + } + } + + /** + * 如果包裹里的值存在,就执行传入的操作({@link Function#apply})并返回该操作返回值 + * 如果不存在,返回一个空的{@code Opt} + * 和 {@link Opt#map}的区别为 传入的操作返回值必须为 Opt + * + * @param mapper 值存在时执行的操作 + * @param 操作返回值的类型 + * @return 如果包裹里的值存在,就执行传入的操作({@link Function#apply})并返回该操作返回值 + * 如果不存在,返回一个空的{@code Opt} + * @throws NullPointerException 如果给定的操作为 {@code null}或者给定的操作执行结果为 {@code null},抛出 {@code NPE} + */ + public Opt flatMap(Function> mapper) { + Objects.requireNonNull(mapper); + if (isEmpty()) { + return empty(); + } else { + @SuppressWarnings("unchecked") + Opt r = (Opt) mapper.apply(value); + return Objects.requireNonNull(r); + } + } + + /** + * 如果包裹里元素的值存在,就执行对应的操作,并返回本身 + * 如果不存在,返回一个空的{@code Opt} + * + * @param action 值存在时执行的操作 + * @throws NullPointerException 如果值存在,并且传入的操作为 {@code null} + * @apiNote 属于 {@link #ifPresent}的链式拓展 + * @author VampireAchao + */ + public Opt peek(Consumer action) { + Objects.requireNonNull(action); + if (isEmpty()) { + return Opt.empty(); + } + action.accept(value); + return this; + } + + /** + * 如果包裹里元素的值存在,就返回本身,如果不存在,则使用传入的操作执行后获得的 {@code Opt} + * + * @return 如果包裹里元素的值存在,就返回本身,如果不存在,则使用传入的函数执行后获得的 {@code Opt} + * @throws NullPointerException 如果传入的操作为空,或者传入的操作执行后返回值为空,则抛出 {@code NPE} + * @since 9 这是jdk9{@link java.util.Optional}中的新函数 + */ + public Opt or(Supplier> supplier) { + Objects.requireNonNull(supplier); + if (isPresent()) { + return this; + } else { + @SuppressWarnings("unchecked") + Opt r = (Opt) supplier.get(); + return Objects.requireNonNull(r); + } + } + + /** + * 如果包裹里元素的值存在,就返回一个包含该元素的 {@link Stream}, + * 否则返回一个空元素的 {@link Stream} + * + * @return 返回一个包含该元素的 {@link Stream}或空的 {@link Stream} + * @apiNote 该方法能将 Opt 中的元素传递给 {@link Stream} + *
{@code
+	 *     Stream> os = ..
+	 *     Stream s = os.flatMap(Opt::stream)
+	 * }
+ * @since 9 这是jdk9{@link java.util.Optional}中的新函数 + */ + public Stream stream() { + if (isEmpty()) { + return Stream.empty(); + } else { + return Stream.of(value); + } + } + + /** + * 如果包裹里元素的值存在,则返回该值,否则返回传入的{@code other} + * + * @param other 元素为空时返回的值,有可能为 {@code null}. + * @return 如果包裹里元素的值存在,则返回该值,否则返回传入的{@code other} + */ + public T orElse(T other) { + return value != null ? value : other; + } + + /** + * 如果包裹里元素的值存在,则返回该值,否则返回传入的操作执行后的返回值 + * + * @param supplier 值不存在时需要执行的操作,返回一个类型与 包裹里元素类型 相同的元素 + * @return 如果包裹里元素的值存在,则返回该值,否则返回传入的操作执行后的返回值 + * @throws NullPointerException 如果之不存在,并且传入的操作为空,则抛出 {@code NPE} + */ + public T orElseGet(Supplier supplier) { + return value != null ? value : supplier.get(); + } + + /** + * 如果包裹里的值存在,则返回该值,否则抛出 {@code NoSuchElementException} + * + * @return 返回一个不为 {@code null} 的包裹里的值 + * @throws NoSuchElementException 如果包裹里的值不存在则抛出该异常 + * @since 10 这是jdk10{@link java.util.Optional}中的新函数 + */ + public T orElseThrow() { + if (value == null) { + throw new NoSuchElementException("No value present"); + } + return value; + } + + /** + * 如果包裹里的值存在,则返回该值,否则执行传入的操作,获取异常类型的返回值并抛出 + * + * @param 异常类型 + * @param exceptionSupplier 值不存在时执行的操作,返回值继承 {@link Throwable} + * @return 包裹里不能为空的值 + * @throws X 如果值不存在 + * @throws NullPointerException 如果值不存在并且 传入的操作为 {@code null}或者操作执行后的返回值为{@code null} + * @apiNote 往往是一个包含无参构造器的异常 例如传入{@code IllegalStateException::new} + */ + public T orElseThrow(Supplier exceptionSupplier) throws X { + if (value != null) { + return value; + } else { + throw exceptionSupplier.get(); + } + } + + /** + * 如果包裹里的值存在,则返回该值,否则执行传入的操作,获取异常类型的返回值并抛出 + * + * @param 异常类型 + * @param exceptionFunction 值不存在时执行的操作,返回值继承 {@link Throwable} + * @param message 作为传入操作执行时的参数,一般作为异常自定义提示语 + * @return 包裹里不能为空的值 + * @throws X 如果值不存在 + * @throws NullPointerException 如果值不存在并且 传入的操作为 {@code null}或者操作执行后的返回值为{@code null} + * @apiNote 往往是一个包含 自定义消息 构造器的异常 例如 + *
{@code
+	 * 		Opt.ofNullable(null).orElseThrow(IllegalStateException::new, "Ops!Something is wrong!");
+	 * }
+ * @author VampireAchao + */ + public T orElseThrow(Function exceptionFunction, String message) throws X { + if (value != null) { + return value; + } else { + throw exceptionFunction.apply(message); + } + } + + /** + * 判断传入参数是否与 {@code Opt}相等 + * 在以下情况下返回true + *
    + *
  • 它也是一个 {@code Opt} 并且 + *
  • 它们包裹住的元素都为空 或者 + *
  • 它们包裹住的元素之间相互 {@code equals()} + *
+ * + * @param obj 一个要用来判断是否相等的参数 + * @return 如果传入的参数也是一个 {@code Opt}并且它们包裹住的元素都为空 + * 或者它们包裹住的元素之间相互 {@code equals()} 就返回{@code true} + * 否则返回 {@code false} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (!(obj instanceof Opt)) { + return false; + } + + Opt other = (Opt) obj; + return Objects.equals(value, other.value); + } + + /** + * 如果包裹内元素为空,则返回0,否则返回元素的 {@code hashcode} + * + * @return 如果包裹内元素为空,则返回0,否则返回元素的 {@code hashcode} + */ + @Override + public int hashCode() { + return Objects.hashCode(value); + } + + /** + * 返回包裹内元素调用{@code toString()}的结果,不存在则返回{@code null} + * return 包裹内元素调用{@code toString()}的结果,不存在则返回{@code null} + */ + @Override + public String toString() { + return value != null + ? value.toString() + : null; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/bean/OptionalBeanTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java similarity index 55% rename from hutool-core/src/test/java/cn/hutool/core/bean/OptionalBeanTest.java rename to hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java index 367aba5e2..9c52e98fe 100644 --- a/hutool-core/src/test/java/cn/hutool/core/bean/OptionalBeanTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java @@ -1,22 +1,26 @@ -package cn.hutool.core.bean; +package cn.hutool.core.lang; +import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; +import java.util.NoSuchElementException; + /** - * {@link OptionalBean}的单元测试 + * {@link Opt}的单元测试 * * @author VampireAchao */ -public class OptionalBeanTest { +public class OptTest { @Test public void ofBlankAbleTest() { // ofBlankAble相对于ofNullable考虑了字符串为空串的情况 - String hutool = OptionalBean.ofBlankAble("").orElse("hutool"); + String hutool = Opt.ofBlankAble("").orElse("hutool"); Assert.assertEquals("hutool", hutool); } @@ -24,7 +28,7 @@ public class OptionalBeanTest { public void getTest() { // 和原版Optional有区别的是,get不会抛出NoSuchElementException // 如果想使用原版Optional中的get这样,获取一个一定不为空的值,则应该使用orElseThrow - Object opt = OptionalBean.ofNullable(null).get(); + Object opt = Opt.ofNullable(null).get(); Assert.assertNull(opt); } @@ -32,28 +36,30 @@ public class OptionalBeanTest { public void isEmptyTest() { // 这是jdk11 Optional中的新函数,直接照搬了过来 // 判断包裹内元素是否为空,注意并没有判断空字符串的情况 - boolean isEmpty = OptionalBean.empty().isEmpty(); + boolean isEmpty = Opt.empty().isEmpty(); Assert.assertTrue(isEmpty); } @Test + @Ignore public void ifPresentOrElseTest() { // 这是jdk9中的新函数,直接照搬了过来 // 存在就打印对应的值,不存在则用{@code System.err.println}打印另一句字符串 - OptionalBean.ofNullable("Hello Hutool!").ifPresentOrElse(System.out::println, () -> System.err.println("Ops!Something is wrong!")); - OptionalBean.empty().ifPresentOrElse(System.out::println, () -> System.err.println("Ops!Something is wrong!")); + Opt.ofNullable("Hello Hutool!").ifPresentOrElse(Console::log, () -> Console.error("Ops!Something is wrong!")); + + Opt.empty().ifPresentOrElse(Console::log, () -> Console.error("Ops!Something is wrong!")); } @Test public void peekTest() { User user = new User(); // 相当于ifPresent的链式调用 - OptionalBean.ofNullable("hutool").peek(user::setUsername).peek(user::setNickname); + Opt.ofNullable("hutool").peek(user::setUsername).peek(user::setNickname); Assert.assertEquals("hutool", user.getNickname()); Assert.assertEquals("hutool", user.getUsername()); // 注意,传入的lambda中,对包裹内的元素执行赋值操作并不会影响到原来的元素 - String name = OptionalBean.ofNullable("hutool").peek(username -> username = "123").peek(username -> username = "456").get(); + String name = Opt.ofNullable("hutool").peek(username -> username = "123").peek(username -> username = "456").get(); Assert.assertEquals("hutool", name); } @@ -61,30 +67,42 @@ public class OptionalBeanTest { public void orTest() { // 这是jdk9 Optional中的新函数,直接照搬了过来 // 给一个替代的Opt - String str = OptionalBean.ofNullable(null).or(() -> OptionalBean.ofNullable("Hello hutool!")).map(String::toUpperCase).orElseThrow(); + String str = Opt.ofNullable(null).or(() -> Opt.ofNullable("Hello hutool!")).map(String::toUpperCase).orElseThrow(); Assert.assertEquals("HELLO HUTOOL!", str); User user = User.builder().username("hutool").build(); - OptionalBean userOptionalBean = OptionalBean.of(user); + Opt userOpt = Opt.of(user); // 获取昵称,获取不到则获取用户名 - String name = userOptionalBean.map(User::getNickname).or(() -> userOptionalBean.map(User::getUsername)).get(); + String name = userOpt.map(User::getNickname).or(() -> userOpt.map(User::getUsername)).get(); Assert.assertEquals("hutool", name); } - @Test + @Test(expected = NoSuchElementException.class) public void orElseThrowTest() { // 获取一个不可能为空的值,否则抛出NoSuchElementException异常 - Object obj = OptionalBean.ofNullable(null).orElseThrow(); + Object obj = Opt.ofNullable(null).orElseThrow(); + Assert.assertNull(obj); + } + + @Test(expected = IllegalStateException.class) + public void orElseThrowTest2() { // 获取一个不可能为空的值,否则抛出自定义异常 - Object assignException = OptionalBean.ofNullable(null).orElseThrow(IllegalStateException::new); + Object assignException = Opt.ofNullable(null).orElseThrow(IllegalStateException::new); + Assert.assertNull(assignException); + } + + @Test(expected = IllegalStateException.class) + public void orElseThrowTest3() { // 获取一个不可能为空的值,否则抛出带自定义消息的自定义异常 - Object exceptionWithMessage = OptionalBean.empty().orElseThrow(IllegalStateException::new, "Ops!Something is wrong!"); + Object exceptionWithMessage = Opt.empty().orElseThrow(IllegalStateException::new, "Ops!Something is wrong!"); + Assert.assertNull(exceptionWithMessage); } @Data @Builder @NoArgsConstructor - private static class User { + @AllArgsConstructor + static class User { private String username; private String nickname; }