diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/lang/mutable/Mutable.java b/hutool-core/src/main/java/org/dromara/hutool/core/lang/mutable/Mutable.java
index b6ef0d702..e67c8b357 100644
--- a/hutool-core/src/main/java/org/dromara/hutool/core/lang/mutable/Mutable.java
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/lang/mutable/Mutable.java
@@ -12,14 +12,110 @@
package org.dromara.hutool.core.lang.mutable;
+import org.dromara.hutool.core.lang.Opt;
+
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.UnaryOperator;
+
/**
- * 提供可变值类型接口
+ *
提供可变值类型接口
+ *
+ * 相较于{@link Opt}或{@link java.util.Optional},该所有实现类中的方法都不区分值是否为{@code null},
+ * 因此在使用前需要自行判断值是否为{@code null},
+ * 确保不会因为{@code null}值而抛出{@link NullPointerException}的情况。
*
* @param 值得类型
* @since 3.0.1
*/
public interface Mutable {
+ // ==================== factory methods ====================
+
+ /**
+ * 创建一个{@link MutableBool}对象
+ *
+ * @param value 值
+ * @return {@link MutableBool}
+ */
+ static MutableBool of(final boolean value) {
+ return new MutableBool(value);
+ }
+
+ /**
+ * 创建一个{@link MutableByte}对象
+ *
+ * @param value 值
+ * @return {@link MutableByte}
+ */
+ static MutableByte of(final byte value) {
+ return new MutableByte(value);
+ }
+
+ /**
+ * 创建一个{@link MutableFloat}对象
+ *
+ * @param value 值
+ * @return {@link MutableFloat}
+ */
+ static MutableFloat of(final float value) {
+ return new MutableFloat(value);
+ }
+
+ /**
+ * 创建一个{@link MutableInt}对象
+ *
+ * @param value 值
+ * @return {@link MutableInt}
+ */
+ static MutableInt of(final int value) {
+ return new MutableInt(value);
+ }
+
+ /**
+ * 创建一个{@link MutableLong}对象
+ *
+ * @param value 值
+ * @return {@link MutableLong}
+ */
+ static MutableLong of(final long value) {
+ return new MutableLong(value);
+ }
+
+ /**
+ * 创建一个{@link MutableDouble}对象
+ *
+ * @param value 值
+ * @return {@link MutableDouble}
+ */
+ static MutableDouble of(final double value) {
+ return new MutableDouble(value);
+ }
+
+ /**
+ * 创建一个{@link MutableShort}对象
+ *
+ * @param value 值
+ * @return {@link MutableShort}
+ */
+ static MutableShort of(final short value) {
+ return new MutableShort(value);
+ }
+
+ /**
+ * 创建一个{@link MutableObj}对象
+ *
+ * @param value 值
+ * @return {@link MutableObj}
+ */
+ static MutableObj of(final T value) {
+ return new MutableObj<>(value);
+ }
+
+ // ==================== base methods ====================
+
/**
* 获得原始值
* @return 原始值
@@ -32,4 +128,57 @@ public interface Mutable {
*/
void set(T value);
+ /**
+ * 根据操作修改值
+ *
+ * @param operator 操作
+ * @return 值
+ */
+ default Mutable map(final UnaryOperator operator) {
+ set(operator.apply(get()));
+ return this;
+ }
+
+ /**
+ * 检查并操作值
+ *
+ * @param consumer 操作
+ * @return 当前对象
+ */
+ default Mutable peek(final Consumer consumer) {
+ consumer.accept(get());
+ return this;
+ }
+
+ /**
+ * 检查值是否满足条件
+ *
+ * @param predicate 条件
+ * @return 是否满足条件
+ */
+ default boolean test(final Predicate predicate) {
+ return predicate.test(get());
+ }
+
+ /**
+ * 获取值,并将值转换为{@link Opt}
+ *
+ * @return {@link Opt}
+ */
+ default Opt toOpt() {
+ return to(Opt::ofNullable);
+ }
+
+ /**
+ * 获取值,并将值转换为指定类型。
+ * 注意,值为null时,转换函数依然会被调用。
+ *
+ * @param function 转换函数
+ * @param 转换后的类型
+ * @return 转换后的值
+ */
+ default R to(final Function function) {
+ Objects.requireNonNull(function);
+ return function.apply(get());
+ }
}
diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/lang/mutable/MutableObj.java b/hutool-core/src/main/java/org/dromara/hutool/core/lang/mutable/MutableObj.java
index 2a87e8a02..d6e871447 100644
--- a/hutool-core/src/main/java/org/dromara/hutool/core/lang/mutable/MutableObj.java
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/lang/mutable/MutableObj.java
@@ -15,6 +15,7 @@ package org.dromara.hutool.core.lang.mutable;
import org.dromara.hutool.core.util.ObjUtil;
import java.io.Serializable;
+import java.util.Objects;
/**
* 可变{@code Object}
@@ -82,7 +83,7 @@ public class MutableObj implements Mutable, Serializable{
@Override
public int hashCode() {
- return value == null ? 0 : value.hashCode();
+ return Objects.hashCode(value);
}
// -----------------------------------------------------------------------
diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/BaseMutableTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/BaseMutableTest.java
new file mode 100644
index 000000000..8826a5c04
--- /dev/null
+++ b/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/BaseMutableTest.java
@@ -0,0 +1,129 @@
+package org.dromara.hutool.core.lang.mutable;
+
+import org.dromara.hutool.core.text.StrValidator;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Objects;
+
+/**
+ * base test for {@link Mutable} implementations.
+ *
+ * @author huangchengxing
+ */
+abstract class BaseMutableTest> {
+
+ /**
+ * 创建一个值,且反复调用应该返回完全相同的值
+ *
+ * @return 值
+ */
+ abstract V getValue1();
+
+ /**
+ * 创建一个值,与{@link #getValue1()}不同,且反复调用应该返回完全相同的值
+ *
+ * @return 值
+ */
+ abstract V getValue2();
+
+ /**
+ * 创建一个{@link Mutable}
+ *
+ * @param value 值
+ * @return 值
+ */
+ abstract M getMutable(V value);
+
+ @Test
+ void testOf() {
+ Assertions.assertInstanceOf(MutableBool.class, Mutable.of(true));
+ Assertions.assertInstanceOf(MutableByte.class, Mutable.of((byte) 1));
+ Assertions.assertInstanceOf(MutableDouble.class, Mutable.of(1.0D));
+ Assertions.assertInstanceOf(MutableFloat.class, Mutable.of(1.0F));
+ Assertions.assertInstanceOf(MutableInt.class, Mutable.of(1));
+ Assertions.assertInstanceOf(MutableLong.class, Mutable.of(1L));
+ Assertions.assertInstanceOf(MutableObj.class, Mutable.of(new Object()));
+ Assertions.assertInstanceOf(MutableShort.class, Mutable.of((short) 1));
+ }
+
+ @Test
+ void testGet() {
+ Mutable mutableObj = getMutable(getValue1());
+ V value = mutableObj.get();
+ Assertions.assertEquals(getValue1(), value);
+ }
+
+ @Test
+ void testSet() {
+ Mutable mutableObj = getMutable(getValue2());
+ mutableObj.set(getValue2());
+ V value = mutableObj.get();
+ Assertions.assertEquals(getValue2(), value);
+ }
+
+ @Test
+ void testTo() {
+ Mutable mutableObj = getMutable(getValue1());
+ String value = mutableObj.to(String::valueOf);
+ Assertions.assertEquals(String.valueOf(getValue1()), value);
+ }
+
+ @Test
+ void testToOpt() {
+ Mutable mutableObj = getMutable(getValue1());
+ V value = mutableObj.toOpt().get();
+ Assertions.assertEquals(getValue1(), value);
+ }
+
+ @Test
+ void testMap() {
+ Mutable mutableObj = getMutable(getValue1());
+ V value = mutableObj.map(v -> getValue2()).get();
+ Assertions.assertEquals(getValue2(), value);
+ }
+
+ @Test
+ void testTest() {
+ Mutable mutableObj = getMutable(getValue1());
+ Assertions.assertTrue(mutableObj.test(Objects::nonNull));
+ }
+
+ @Test
+ void testPeek() {
+ Mutable m1 = getMutable(getValue1());
+ Mutable m2 = getMutable(getValue2());
+ m1.peek(m2::set);
+ Assertions.assertEquals(getValue1(), m2.get());
+ }
+
+ @Test
+ void testHashCode() {
+ V value = getValue1();
+ Mutable mutableObj = new MutableObj<>(value);
+ Assertions.assertEquals(value.hashCode(), mutableObj.hashCode());
+ mutableObj.set(null);
+ Assertions.assertEquals(0, mutableObj.hashCode());
+ }
+
+ @Test
+ void testEquals() {
+ V value = getValue1();
+ Mutable mutableObj = new MutableObj<>(value);
+ Assertions.assertNotEquals(value, mutableObj);
+ Assertions.assertEquals(mutableObj, mutableObj);
+ Assertions.assertEquals(mutableObj, new MutableObj<>(value));
+ Assertions.assertNotEquals(mutableObj, new MutableObj<>(null));
+ Assertions.assertNotEquals(mutableObj, null);
+ Assertions.assertNotEquals(mutableObj, value);
+ }
+
+ @Test
+ void testToString() {
+ V value = getValue1();
+ Mutable mutableObj = new MutableObj<>(value);
+ Assertions.assertEquals(value.toString(), mutableObj.toString());
+ mutableObj.set(null);
+ Assertions.assertEquals(StrValidator.NULL, mutableObj.toString());
+ }
+}
diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableBoolTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableBoolTest.java
new file mode 100644
index 000000000..879a23c28
--- /dev/null
+++ b/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableBoolTest.java
@@ -0,0 +1,40 @@
+package org.dromara.hutool.core.lang.mutable;
+
+/**
+ * test for {@link MutableBool}
+ *
+ * @author huangchengxing
+ */
+public class MutableBoolTest extends BaseMutableTest {
+
+ /**
+ * 创建一个值
+ *
+ * @return 值
+ */
+ @Override
+ Boolean getValue1() {
+ return Boolean.TRUE;
+ }
+
+ /**
+ * 创建一个值
+ *
+ * @return 值
+ */
+ @Override
+ Boolean getValue2() {
+ return Boolean.FALSE;
+ }
+
+ /**
+ * 创建一个{@link Mutable}
+ *
+ * @param value 值
+ * @return 值
+ */
+ @Override
+ MutableBool getMutable(Boolean value) {
+ return new MutableBool(value);
+ }
+}
diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableByteTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableByteTest.java
new file mode 100644
index 000000000..2e30f5d72
--- /dev/null
+++ b/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableByteTest.java
@@ -0,0 +1,38 @@
+package org.dromara.hutool.core.lang.mutable;
+
+/**
+ * @author huangchengxing
+ */
+public class MutableByteTest extends BaseMutableTest {
+
+ /**
+ * 创建一个值,且反复调用应该返回完全相同的值
+ *
+ * @return 值
+ */
+ @Override
+ Number getValue1() {
+ return Byte.MAX_VALUE;
+ }
+
+ /**
+ * 创建一个值,与{@link #getValue1()}不同,且反复调用应该返回完全相同的值
+ *
+ * @return 值
+ */
+ @Override
+ Number getValue2() {
+ return Byte.MIN_VALUE;
+ }
+
+ /**
+ * 创建一个{@link Mutable}
+ *
+ * @param value 值
+ * @return 值
+ */
+ @Override
+ MutableByte getMutable(Number value) {
+ return new MutableByte(value);
+ }
+}
diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableDoubleTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableDoubleTest.java
new file mode 100644
index 000000000..2491dad6d
--- /dev/null
+++ b/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableDoubleTest.java
@@ -0,0 +1,38 @@
+package org.dromara.hutool.core.lang.mutable;
+
+/**
+ * @author huangchengxing
+ */
+public class MutableDoubleTest extends BaseMutableTest {
+
+ /**
+ * 创建一个值,且反复调用应该返回完全相同的值
+ *
+ * @return 值
+ */
+ @Override
+ Number getValue1() {
+ return Double.MAX_VALUE;
+ }
+
+ /**
+ * 创建一个值,与{@link #getValue1()}不同,且反复调用应该返回完全相同的值
+ *
+ * @return 值
+ */
+ @Override
+ Number getValue2() {
+ return Double.MAX_VALUE;
+ }
+
+ /**
+ * 创建一个{@link Mutable}
+ *
+ * @param value 值
+ * @return 值
+ */
+ @Override
+ MutableDouble getMutable(Number value) {
+ return new MutableDouble(value);
+ }
+}
diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableEntryTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableEntryTest.java
new file mode 100644
index 000000000..14ae5e848
--- /dev/null
+++ b/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableEntryTest.java
@@ -0,0 +1,45 @@
+package org.dromara.hutool.core.lang.mutable;
+
+import org.dromara.hutool.core.map.MapUtil;
+
+import java.util.Map;
+
+/**
+ * @author huangchengxing
+ */
+public class MutableEntryTest extends BaseMutableTest, MutableEntry> {
+
+ private static final Map.Entry ENTRY1 = MapUtil.entry("key1", "value1");
+ private static final Map.Entry ENTRY2 = MapUtil.entry("key2", "value2");
+
+ /**
+ * 创建一个值,且反复调用应该返回完全相同的值
+ *
+ * @return 值
+ */
+ @Override
+ Map.Entry getValue1() {
+ return ENTRY1;
+ }
+
+ /**
+ * 创建一个值,与{@link #getValue1()}不同,且反复调用应该返回完全相同的值
+ *
+ * @return 值
+ */
+ @Override
+ Map.Entry getValue2() {
+ return ENTRY2;
+ }
+
+ /**
+ * 创建一个{@link Mutable}
+ *
+ * @param value 值
+ * @return 值
+ */
+ @Override
+ MutableEntry getMutable(Map.Entry value) {
+ return new MutableEntry<>(value.getKey(), value.getValue());
+ }
+}
diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableFloatTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableFloatTest.java
new file mode 100644
index 000000000..6f43a8538
--- /dev/null
+++ b/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableFloatTest.java
@@ -0,0 +1,37 @@
+package org.dromara.hutool.core.lang.mutable;
+
+/**
+ * @author huangchengxing
+ */
+public class MutableFloatTest extends BaseMutableTest {
+ /**
+ * 创建一个值,且反复调用应该返回完全相同的值
+ *
+ * @return 值
+ */
+ @Override
+ Number getValue1() {
+ return Float.MAX_VALUE;
+ }
+
+ /**
+ * 创建一个值,与{@link #getValue1()}不同,且反复调用应该返回完全相同的值
+ *
+ * @return 值
+ */
+ @Override
+ Number getValue2() {
+ return Float.MIN_VALUE;
+ }
+
+ /**
+ * 创建一个{@link Mutable}
+ *
+ * @param value 值
+ * @return 值
+ */
+ @Override
+ MutableFloat getMutable(Number value) {
+ return new MutableFloat(value);
+ }
+}
diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableIntTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableIntTest.java
new file mode 100644
index 000000000..01ffe1750
--- /dev/null
+++ b/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableIntTest.java
@@ -0,0 +1,37 @@
+package org.dromara.hutool.core.lang.mutable;
+
+/**
+ * @author huangchengxing
+ */
+public class MutableIntTest extends BaseMutableTest {
+ /**
+ * 创建一个值,且反复调用应该返回完全相同的值
+ *
+ * @return 值
+ */
+ @Override
+ Number getValue1() {
+ return Integer.MAX_VALUE;
+ }
+
+ /**
+ * 创建一个值,与{@link #getValue1()}不同,且反复调用应该返回完全相同的值
+ *
+ * @return 值
+ */
+ @Override
+ Number getValue2() {
+ return Integer.MIN_VALUE;
+ }
+
+ /**
+ * 创建一个{@link Mutable}
+ *
+ * @param value 值
+ * @return 值
+ */
+ @Override
+ MutableInt getMutable(Number value) {
+ return new MutableInt(value);
+ }
+}
diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableLongTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableLongTest.java
new file mode 100644
index 000000000..3d46f281f
--- /dev/null
+++ b/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableLongTest.java
@@ -0,0 +1,37 @@
+package org.dromara.hutool.core.lang.mutable;
+
+/**
+ * @author huangchengxing
+ */
+public class MutableLongTest extends BaseMutableTest {
+ /**
+ * 创建一个值,且反复调用应该返回完全相同的值
+ *
+ * @return 值
+ */
+ @Override
+ Number getValue1() {
+ return Long.MAX_VALUE;
+ }
+
+ /**
+ * 创建一个值,与{@link #getValue1()}不同,且反复调用应该返回完全相同的值
+ *
+ * @return 值
+ */
+ @Override
+ Number getValue2() {
+ return Long.MIN_VALUE;
+ }
+
+ /**
+ * 创建一个{@link Mutable}
+ *
+ * @param value 值
+ * @return 值
+ */
+ @Override
+ MutableLong getMutable(Number value) {
+ return new MutableLong(value);
+ }
+}
diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableObjTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableObjTest.java
new file mode 100644
index 000000000..af71a2eee
--- /dev/null
+++ b/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableObjTest.java
@@ -0,0 +1,40 @@
+package org.dromara.hutool.core.lang.mutable;
+
+/**
+ * test for {@link MutableObj}
+ *
+ * @author huangchengxing
+ */
+class MutableObjTest extends BaseMutableTest> {
+
+ /**
+ * 创建一个值
+ *
+ * @return 值
+ */
+ @Override
+ String getValue1() {
+ return "test1";
+ }
+
+ /**
+ * 创建一个值
+ *
+ * @return 值
+ */
+ @Override
+ String getValue2() {
+ return "test2";
+ }
+
+ /**
+ * 创建一个{@link Mutable}
+ *
+ * @param value 值
+ * @return 值
+ */
+ @Override
+ MutableObj getMutable(String value) {
+ return new MutableObj<>(value);
+ }
+}
diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableShortTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableShortTest.java
new file mode 100644
index 000000000..a918098e8
--- /dev/null
+++ b/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableShortTest.java
@@ -0,0 +1,37 @@
+package org.dromara.hutool.core.lang.mutable;
+
+/**
+ * @author huangchengxing
+ */
+public class MutableShortTest extends BaseMutableTest {
+ /**
+ * 创建一个值,且反复调用应该返回完全相同的值
+ *
+ * @return 值
+ */
+ @Override
+ Number getValue1() {
+ return Short.MAX_VALUE;
+ }
+
+ /**
+ * 创建一个值,与{@link #getValue1()}不同,且反复调用应该返回完全相同的值
+ *
+ * @return 值
+ */
+ @Override
+ Number getValue2() {
+ return Short.MIN_VALUE;
+ }
+
+ /**
+ * 创建一个{@link Mutable}
+ *
+ * @param value 值
+ * @return 值
+ */
+ @Override
+ MutableShort getMutable(Number value) {
+ return new MutableShort(value);
+ }
+}
diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableTripleTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableTripleTest.java
new file mode 100644
index 000000000..457411c68
--- /dev/null
+++ b/hutool-core/src/test/java/org/dromara/hutool/core/lang/mutable/MutableTripleTest.java
@@ -0,0 +1,41 @@
+package org.dromara.hutool.core.lang.mutable;
+
+/**
+ * @author huangchengxing
+ */
+public class MutableTripleTest extends BaseMutableTest, MutableTriple> {
+
+ private static final MutableTriple VALUE1 = new MutableTriple<>("1", "2", "3");
+ private static final MutableTriple VALUE2 = new MutableTriple<>("4", "5", "6");
+
+ /**
+ * 创建一个值,且反复调用应该返回完全相同的值
+ *
+ * @return 值
+ */
+ @Override
+ MutableTriple getValue1() {
+ return VALUE1;
+ }
+
+ /**
+ * 创建一个值,与{@link #getValue1()}不同,且反复调用应该返回完全相同的值
+ *
+ * @return 值
+ */
+ @Override
+ MutableTriple getValue2() {
+ return VALUE2;
+ }
+
+ /**
+ * 创建一个{@link Mutable}
+ *
+ * @param value 值
+ * @return 值
+ */
+ @Override
+ MutableTriple getMutable(MutableTriple value) {
+ return new MutableTriple<>(value.getLeft(), value.getMiddle(), value.getRight());
+ }
+}