diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f5a9693d..b1fde4966 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### 🐣新特性 * 【core 】 增加UserPassAuthenticator * 【db 】 获取分组数据源时,移除公共属性项 +* 【core 】 增加StrJoiner ### 🐞Bug修复 * 【db 】 修复Oracle下别名错误造成的SQL语法啊错误(issue#I3VTQW@Gitee) diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index a995093d0..09be8aa08 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -1371,7 +1371,7 @@ public class CollUtil { * @since 5.0.6 */ public static Map fieldValueMap(Iterable iterable, String fieldName) { - return IterUtil.fieldValueMap(null == iterable ? null : iterable.iterator(), fieldName); + return IterUtil.fieldValueMap(IterUtil.getIter(iterable), fieldName); } /** @@ -1386,7 +1386,7 @@ public class CollUtil { * @since 5.0.6 */ public static Map fieldValueAsMap(Iterable iterable, String fieldNameForKey, String fieldNameForValue) { - return IterUtil.fieldValueAsMap(null == iterable ? null : iterable.iterator(), fieldNameForKey, fieldNameForValue); + return IterUtil.fieldValueAsMap(IterUtil.getIter(iterable), fieldNameForKey, fieldNameForValue); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java index 81fc390f7..85a2d92e6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java @@ -5,6 +5,7 @@ import cn.hutool.core.lang.Editor; import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.func.Func1; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.text.StrJoiner; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ReflectUtil; @@ -30,6 +31,18 @@ import java.util.function.Function; */ public class IterUtil { + /** + * 获取{@link Iterator} + * + * @param iterable {@link Iterable} + * @param 元素类型 + * @return 当iterable为null返回{@code null},否则返回对应的{@link Iterator} + * @since 5.7.2 + */ + public static Iterator getIter(Iterable iterable) { + return null == iterable ? null : iterable.iterator(); + } + /** * Iterable是否为空 * @@ -199,7 +212,7 @@ public class IterUtil { * @since 4.6.2 */ public static List fieldValueList(Iterable iterable, String fieldName) { - return fieldValueList(null == iterable ? null : iterable.iterator(), fieldName); + return fieldValueList(getIter(iterable), fieldName); } /** @@ -252,9 +265,9 @@ public class IterUtil { return join(iterator, conjunction, (item) -> { if (ArrayUtil.isArray(item)) { return ArrayUtil.join(ArrayUtil.wrap(item), conjunction, prefix, suffix); - } else if (item instanceof Iterable) { + } else if (item instanceof Iterable) { return CollUtil.join((Iterable) item, conjunction, prefix, suffix); - } else if (item instanceof Iterator) { + } else if (item instanceof Iterator) { return join((Iterator) item, conjunction, prefix, suffix); } else { return StrUtil.wrap(String.valueOf(item), prefix, suffix); @@ -278,20 +291,7 @@ public class IterUtil { return null; } - final StringBuilder sb = new StringBuilder(); - boolean isFirst = true; - T item; - while (iterator.hasNext()) { - if (isFirst) { - isFirst = false; - } else { - sb.append(conjunction); - } - - item = iterator.next(); - sb.append(func.apply(item)); - } - return sb.toString(); + return new StrJoiner(conjunction).append(iterator, func).toString(); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java new file mode 100644 index 000000000..fbeec12e4 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java @@ -0,0 +1,237 @@ +package cn.hutool.core.text; + +import cn.hutool.core.collection.ArrayIter; +import cn.hutool.core.collection.IterUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; + +import java.io.IOException; +import java.util.Iterator; +import java.util.function.Function; + +/** + * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * + * @author looly + * @since 5.7.2 + */ +public class StrJoiner implements Appendable { + + private Appendable appendable; + private CharSequence delimiter; + private CharSequence prefix; + private CharSequence suffix; + /** + * appendable中是否包含内容 + */ + private boolean hasContent; + + /** + * 构造 + * + * @param delimiter 分隔符,{@code null}表示无连接符,直接拼接 + */ + public StrJoiner(CharSequence delimiter) { + this(null, delimiter); + } + + /** + * 构造 + * + * @param appendable 字符串追加器,拼接的字符串都将加入到此,{@code null}使用默认{@link StringBuilder} + * @param delimiter 分隔符,{@code null}表示无连接符,直接拼接 + */ + public StrJoiner(Appendable appendable, CharSequence delimiter) { + this(appendable, delimiter, null, null); + } + + /** + * 构造 + * + * @param delimiter 分隔符,{@code null}表示无连接符,直接拼接 + * @param prefix 前缀 + * @param suffix 后缀 + */ + public StrJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix) { + this(null, delimiter, prefix, suffix); + } + + /** + * 构造 + * + * @param appendable 字符串追加器,拼接的字符串都将加入到此,{@code null}使用默认{@link StringBuilder} + * @param delimiter 分隔符,{@code null}表示无连接符,直接拼接 + * @param prefix 前缀 + * @param suffix 后缀 + */ + public StrJoiner(Appendable appendable, CharSequence delimiter, + CharSequence prefix, CharSequence suffix) { + if (null != appendable) { + this.appendable = appendable; + final String initStr = appendable.toString(); + if (StrUtil.isNotEmpty(initStr) && false == StrUtil.endWith(initStr, delimiter)) { + // 用户传入的Appendable中已经存在内容,且末尾不是分隔符 + this.hasContent = true; + } + } + + this.delimiter = delimiter; + this.prefix = prefix; + this.suffix = suffix; + } + + /** + * 设置分隔符 + * + * @param delimiter 分隔符 + * @return this + */ + public StrJoiner setDelimiter(CharSequence delimiter) { + this.delimiter = delimiter; + return this; + } + + /** + * 设置前缀 + * + * @param prefix 前缀 + * @return this + */ + public StrJoiner setPrefix(CharSequence prefix) { + this.prefix = prefix; + return this; + } + + /** + * 设置后缀 + * + * @param suffix 后缀 + * @return this + */ + public StrJoiner setSuffix(CharSequence suffix) { + this.suffix = suffix; + return this; + } + + /** + * 追加{@link Iterator}中的元素到拼接器中 + * + * @param 元素类型 + * @param iterable 元素列表 + * @return this + */ + public StrJoiner append(Iterable iterable) { + return append(IterUtil.getIter(iterable)); + } + + /** + * 追加{@link Iterator}中的元素到拼接器中 + * + * @param 元素类型 + * @param iterator 元素列表 + * @return this + */ + public StrJoiner append(Iterator iterator) { + return append(iterator, (t)->{ + if(ArrayUtil.isArray(t)){ + return new StrJoiner(this.delimiter).append((Iterator) new ArrayIter<>(t)).toString(); + } else if(t instanceof Iterator){ + return new StrJoiner(this.delimiter).append((Iterator)t).toString(); + } else if(t instanceof Iterable){ + return new StrJoiner(this.delimiter).append((Iterable)t).toString(); + } + return String.valueOf(t); + }); + } + + /** + * 追加{@link Iterator}中的元素到拼接器中 + * + * @param 元素类型 + * @param iterable 元素列表 + * @param toStrFunc 元素对象转换为字符串的函数 + * @return this + */ + public StrJoiner append(Iterable iterable, Function toStrFunc) { + return append(IterUtil.getIter(iterable), toStrFunc); + } + + /** + * 追加{@link Iterator}中的元素到拼接器中 + * + * @param 元素类型 + * @param iterator 元素列表 + * @param toStrFunc 元素对象转换为字符串的函数 + * @return this + */ + public StrJoiner append(Iterator iterator, Function toStrFunc) { + if (null != iterator) { + while (iterator.hasNext()) { + append(toStrFunc.apply(iterator.next())); + } + } + return this; + } + + @Override + public StrJoiner append(CharSequence csq) { + try { + prepare().append(csq); + } catch (IOException e) { + throw new IORuntimeException(e); + } + return this; + } + + @Override + public StrJoiner append(CharSequence csq, int start, int end) { + try { + prepare().append(csq, start, end); + } catch (IOException e) { + throw new IORuntimeException(e); + } + return this; + } + + @Override + public StrJoiner append(char c) { + try { + prepare().append(c); + } catch (IOException e) { + throw new IORuntimeException(e); + } + return this; + } + + @Override + public String toString() { + if (StrUtil.isNotEmpty(this.suffix)) { + try { + appendable.append(this.suffix); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + return appendable.toString(); + } + + /** + * 准备连接器,如果连接器非空,追加元素,否则初始化前缀 + * + * @return {@link Appendable} + * @throws IOException IO异常 + */ + private Appendable prepare() throws IOException { + if (hasContent) { + this.appendable.append(delimiter); + } else { + this.appendable = new StringBuilder(); + if (StrUtil.isNotEmpty(this.prefix)) { + this.appendable.append(this.prefix); + } + this.hasContent = true; + } + return this.appendable; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java index f01ac7601..a4814874e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java @@ -130,7 +130,9 @@ public class StrUtil extends CharSequenceUtil implements StrPool { * @param obj 对象 * @param charsetName 字符集 * @return 字符串 + * @deprecated 请使用 {@link #str(Object, Charset)} */ + @Deprecated public static String str(Object obj, String charsetName) { return str(obj, Charset.forName(charsetName)); } @@ -263,9 +265,10 @@ public class StrUtil extends CharSequenceUtil implements StrPool { * @param obj 对象 * @return 字符串 * @since 4.1.3 + * @see String#valueOf(Object) */ public static String toString(Object obj) { - return null == obj ? NULL : obj.toString(); + return String.valueOf(obj); } /** diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/IterUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/IterUtilTest.java index 613212049..1d4ef4f2e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/IterUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/IterUtilTest.java @@ -37,6 +37,13 @@ public class IterUtilTest { Assert.assertEquals("\"1\":\"2\":\"3\":\"4\"", join2); } + @Test + public void joinWithFuncTest() { + ArrayList list = CollUtil.newArrayList("1", "2", "3", "4"); + String join = IterUtil.join(list.iterator(), ":", String::valueOf); + Assert.assertEquals("1:2:3:4", join); + } + @Test public void testToListMap() { Map> expectedMap = new HashMap<>();