From 9a21da9c783aa31758a69c92459c8a88ad7253ff Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 24 Aug 2021 23:13:10 +0800 Subject: [PATCH] add FuncComparator --- CHANGELOG.md | 3 +- .../cn/hutool/core/collection/ListUtil.java | 3 + .../core/comparator/BaseFieldComparator.java | 2 + .../hutool/core/comparator/CompareUtil.java | 2 +- .../core/comparator/FieldComparator.java | 41 +++++++--- .../core/comparator/FieldsComparator.java | 42 +++++----- .../core/comparator/FuncComparator.java | 63 +++++++++++++++ .../core/comparator/NullComparator.java | 78 +++++++++++++++++++ .../core/comparator/PropertyComparator.java | 44 +---------- .../hutool/core/collection/ListUtilTest.java | 27 +++++++ 10 files changed, 231 insertions(+), 74 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/comparator/FuncComparator.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/comparator/NullComparator.java diff --git a/CHANGELOG.md b/CHANGELOG.md index c0d1721b9..d29c8ff37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.7.10 (2021-08-22) +# 5.7.10 (2021-08-24) ### 🐣新特性 * 【core 】 增加NamingCase类 @@ -19,6 +19,7 @@ * 【poi 】 Excel07SaxReader支持数字类型sheet名称、支持sheetName:名称前缀(issue#I46OMA@Gitee) * 【extra 】 Mail增加build方法(issue#I46LGE@Gitee) * 【core 】 XmlUtil增加beanToXml重载,支持忽略null +* 【core 】 添加NullComparator、FuncComparator(issue#I471X7@Gitee) ### 🐞Bug修复 * 【core 】 修复MapUtil.sort比较器不一致返回原map的问题(issue#I46AQJ@Gitee) diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java index df516b931..780d83246 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java @@ -312,6 +312,9 @@ public class ListUtil { * @see Collections#sort(List, Comparator) */ public static List sort(List list, Comparator c) { + if(CollUtil.isEmpty(list)){ + return list; + } list.sort(c); return list; } diff --git a/hutool-core/src/main/java/cn/hutool/core/comparator/BaseFieldComparator.java b/hutool-core/src/main/java/cn/hutool/core/comparator/BaseFieldComparator.java index be4469dd4..0888aeaa4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/comparator/BaseFieldComparator.java +++ b/hutool-core/src/main/java/cn/hutool/core/comparator/BaseFieldComparator.java @@ -13,7 +13,9 @@ import java.util.Comparator; * * @param 被比较的Bean * @author jiangzeyin + * @deprecated 此类不再需要,使用FuncComparator代替更加灵活 */ +@Deprecated public abstract class BaseFieldComparator implements Comparator, Serializable { private static final long serialVersionUID = -3482464782340308755L; diff --git a/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java b/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java index 2dbf47e2d..4ff9948dd 100644 --- a/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java @@ -53,7 +53,7 @@ public class CompareUtil { * @param 被比较对象类型(必须实现Comparable接口) * @param c1 对象1,可以为{@code null} * @param c2 对象2,可以为{@code null} - * @param isNullGreater 当被比较对象为null时是否排在前面,true表示null大于任何对象,false反之 + * @param isNullGreater 当被比较对象为null时是否排在后面,true表示null大于任何对象,false反之 * @return 比较结果,如果c1 < c2,返回数小于0,c1==c2返回0,c1 > c2 大于0 * @see java.util.Comparator#compare(Object, Object) */ diff --git a/hutool-core/src/main/java/cn/hutool/core/comparator/FieldComparator.java b/hutool-core/src/main/java/cn/hutool/core/comparator/FieldComparator.java index e56955de6..b4a1dd19a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/comparator/FieldComparator.java +++ b/hutool-core/src/main/java/cn/hutool/core/comparator/FieldComparator.java @@ -1,6 +1,8 @@ package cn.hutool.core.comparator; +import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; import java.lang.reflect.Field; @@ -12,11 +14,9 @@ import java.lang.reflect.Field; * @param 被比较的Bean * @author Looly */ -public class FieldComparator extends BaseFieldComparator { +public class FieldComparator extends FuncComparator { private static final long serialVersionUID = 9157326766723846313L; - private final Field field; - /** * 构造 * @@ -24,10 +24,7 @@ public class FieldComparator extends BaseFieldComparator { * @param fieldName 字段名 */ public FieldComparator(Class beanClass, String fieldName) { - this.field = ClassUtil.getDeclaredField(beanClass, fieldName); - if (this.field == null) { - throw new IllegalArgumentException(StrUtil.format("Field [{}] not found in Class [{}]", fieldName, beanClass.getName())); - } + this(getNonNullField(beanClass, fieldName)); } /** @@ -36,11 +33,33 @@ public class FieldComparator extends BaseFieldComparator { * @param field 字段 */ public FieldComparator(Field field) { - this.field = field; + this(true, field); } - @Override - public int compare(T o1, T o2) { - return compareItem(o1, o2, this.field); + /** + * 构造 + * + * @param nullGreater 是否{@code null}在后 + * @param field 字段 + */ + public FieldComparator(boolean nullGreater, Field field) { + super(nullGreater, (bean) -> + (Comparable) ReflectUtil.getFieldValue(bean, + Assert.notNull(field, "Field must be not null!"))); + } + + /** + * 获取字段,附带检查字段不存在的问题。 + * + * @param beanClass Bean类 + * @param fieldName 字段名 + * @return 非null字段 + */ + private static Field getNonNullField(Class beanClass, String fieldName) { + final Field field = ClassUtil.getDeclaredField(beanClass, fieldName); + if (field == null) { + throw new IllegalArgumentException(StrUtil.format("Field [{}] not found in Class [{}]", fieldName, beanClass.getName())); + } + return field; } } diff --git a/hutool-core/src/main/java/cn/hutool/core/comparator/FieldsComparator.java b/hutool-core/src/main/java/cn/hutool/core/comparator/FieldsComparator.java index 6e21c6d1a..5c10eda78 100644 --- a/hutool-core/src/main/java/cn/hutool/core/comparator/FieldsComparator.java +++ b/hutool-core/src/main/java/cn/hutool/core/comparator/FieldsComparator.java @@ -1,7 +1,7 @@ package cn.hutool.core.comparator; +import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ClassUtil; -import cn.hutool.core.util.StrUtil; import java.lang.reflect.Field; @@ -12,11 +12,9 @@ import java.lang.reflect.Field; * @param 被比较的Bean * @author Looly */ -public class FieldsComparator extends BaseFieldComparator { +public class FieldsComparator extends NullComparator { private static final long serialVersionUID = 8649196282886500803L; - private final Field[] fields; - /** * 构造 * @@ -24,23 +22,29 @@ public class FieldsComparator extends BaseFieldComparator { * @param fieldNames 多个字段名 */ public FieldsComparator(Class beanClass, String... fieldNames) { - this.fields = new Field[fieldNames.length]; - for (int i = 0; i < fieldNames.length; i++) { - this.fields[i] = ClassUtil.getDeclaredField(beanClass, fieldNames[i]); - if (this.fields[i] == null) { - throw new IllegalArgumentException(StrUtil.format("Field [{}] not found in Class [{}]", fieldNames[i], beanClass.getName())); - } - } + this(true, beanClass, fieldNames); } - @Override - public int compare(T o1, T o2) { - for (Field field : fields) { - int compare = this.compareItem(o1, o2, field); - if (compare != 0) { - return compare; + /** + * 构造 + * + * @param nullGreater 是否{@code null}在后 + * @param beanClass Bean类 + * @param fieldNames 多个字段名 + */ + public FieldsComparator(boolean nullGreater, Class beanClass, String... fieldNames) { + super(nullGreater, (a, b) -> { + Field field; + for (String fieldName : fieldNames) { + field = ClassUtil.getDeclaredField(beanClass, fieldName); + Assert.notNull(field, "Field [{}] not found in Class [{}]", fieldName, beanClass.getName()); + final int compare = new FieldComparator<>(field).compare(a, b); + if (0 != compare) { + return compare; + } } - } - return 0; + return 0; + }); } + } diff --git a/hutool-core/src/main/java/cn/hutool/core/comparator/FuncComparator.java b/hutool-core/src/main/java/cn/hutool/core/comparator/FuncComparator.java new file mode 100644 index 000000000..78ea09840 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/comparator/FuncComparator.java @@ -0,0 +1,63 @@ +package cn.hutool.core.comparator; + +import cn.hutool.core.util.ObjectUtil; + +import java.util.function.Function; + +/** + * 指定函数排序器 + * + * @param 被比较的对象 + * @author looly + */ +public class FuncComparator extends NullComparator { + private static final long serialVersionUID = 1L; + + private final Function> func; + + /** + * 构造 + * + * @param nullGreater 是否{@code null}在后 + * @param func 比较项获取函数 + */ + public FuncComparator(boolean nullGreater, Function> func) { + super(nullGreater, null); + this.func = func; + } + + @Override + protected int doCompare(T a, T b) { + Comparable v1; + Comparable v2; + try { + v1 = func.apply(a); + v2 = func.apply(b); + } catch (Exception e) { + throw new ComparatorException(e); + } + + return compare(a, b, v1, v2); + } + + /** + * 对象及对应比较的值的综合比较
+ * 考虑到如果对象对应的比较值相同,如对象的字段值相同,则返回相同结果,此时在TreeMap等容器比较去重时会去重。
+ * 因此需要比较下对象本身以避免去重 + * + * @param o1 对象1 + * @param o2 对象2 + * @param v1 被比较的值1 + * @param v2 被比较的值2 + * @return 比较结果 + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + private int compare(T o1, T o2, Comparable v1, Comparable v2) { + int result = ObjectUtil.compare(v1, v2); + if (0 == result) { + //避免TreeSet / TreeMap 过滤掉排序字段相同但是对象不相同的情况 + result = CompareUtil.compare(o1, o2, this.nullGreater); + } + return result; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/comparator/NullComparator.java b/hutool-core/src/main/java/cn/hutool/core/comparator/NullComparator.java new file mode 100644 index 000000000..7f3544d27 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/comparator/NullComparator.java @@ -0,0 +1,78 @@ +package cn.hutool.core.comparator; + +import java.io.Serializable; +import java.util.Comparator; +import java.util.Objects; + +/** + * {@code null}友好的比较器包装,如果nullGreater,则{@code null} > non-null,否则反之。
+ * 如果二者皆为{@code null},则为相等,返回0。
+ * 如果二者都非{@code null},则使用传入的比较器排序。
+ * 传入比较器为{@code null},则看被比较的两个对象是否都实现了{@link Comparable}实现则调用{@link Comparable#compareTo(Object)}。 + * 如果两者至少一个未实现,则视为所有元素相等。 + * + * @param 被比较的对象 + * @author looly + * @since 5.7.10 + */ +public class NullComparator implements Comparator, Serializable { + private static final long serialVersionUID = 1L; + + protected final boolean nullGreater; + protected final Comparator comparator; + + /** + * 构造 + * @param nullGreater 是否{@code null}最大,排在最后 + * @param comparator 实际比较器 + */ + @SuppressWarnings("unchecked") + public NullComparator(boolean nullGreater, Comparator comparator) { + this.nullGreater = nullGreater; + this.comparator = (Comparator) comparator; + } + + @Override + public int compare(T a, T b) { + if (a == b) { + return 0; + }if (a == null) { + return nullGreater ? 1 : -1; + } else if (b == null) { + return nullGreater ? -1 : 1; + } else { + return doCompare(a, b); + } + } + + @Override + public Comparator thenComparing(Comparator other) { + Objects.requireNonNull(other); + return new NullComparator<>(nullGreater, comparator == null ? other : comparator.thenComparing(other)); + } + + @Override + public Comparator reversed() { + return new NullComparator<>((false == nullGreater), comparator == null ? null : comparator.reversed()); + } + + /** + * 不检查{@code null}的比较方法
+ * 用户可自行重写此方法自定义比较方式 + * + * @param a A值 + * @param b B值 + * @return 比较结果,-1:a小于b,0:相等,1:a大于b + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + protected int doCompare(T a, T b) { + if (null == comparator) { + if (a instanceof Comparable && b instanceof Comparable) { + return ((Comparable) a).compareTo(b); + } + return 0; + } + + return comparator.compare(a, b); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/comparator/PropertyComparator.java b/hutool-core/src/main/java/cn/hutool/core/comparator/PropertyComparator.java index 5df0af91c..c94ced29e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/comparator/PropertyComparator.java +++ b/hutool-core/src/main/java/cn/hutool/core/comparator/PropertyComparator.java @@ -1,10 +1,6 @@ package cn.hutool.core.comparator; import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.util.ObjectUtil; - -import java.io.Serializable; -import java.util.Comparator; /** * Bean属性排序器
@@ -14,12 +10,9 @@ import java.util.Comparator; * * @param 被比较的Bean */ -public class PropertyComparator implements Comparator, Serializable { +public class PropertyComparator extends FuncComparator { private static final long serialVersionUID = 9157326766723846313L; - private final String property; - private final boolean isNullGreater; - /** * 构造 * @@ -36,39 +29,6 @@ public class PropertyComparator implements Comparator, Serializable { * @param isNullGreater null值是否排在后(从小到大排序) */ public PropertyComparator(String property, boolean isNullGreater) { - this.property = property; - this.isNullGreater = isNullGreater; - } - - @Override - public int compare(T o1, T o2) { - if (o1 == o2) { - return 0; - } else if (null == o1) {// null 排在后面 - return isNullGreater ? 1 : -1; - } else if (null == o2) { - return isNullGreater ? -1 : 1; - } - - Comparable v1; - Comparable v2; - try { - v1 = BeanUtil.getProperty(o1, property); - v2 = BeanUtil.getProperty(o2, property); - } catch (Exception e) { - throw new ComparatorException(e); - } - - return compare(o1, o2, v1, v2); - } - - @SuppressWarnings({"rawtypes", "unchecked"}) - private int compare(T o1, T o2, Comparable fieldValue1, Comparable fieldValue2) { - int result = ObjectUtil.compare(fieldValue1, fieldValue2, isNullGreater); - if(0 == result) { - //避免TreeSet / TreeMap 过滤掉排序字段相同但是对象不相同的情况 - result = CompareUtil.compare(o1, o2, this.isNullGreater); - } - return result; + super(isNullGreater, (bean)-> BeanUtil.getProperty(bean, property)); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 25455b1ad..75a767e9b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -4,6 +4,8 @@ import cn.hutool.core.date.StopWatch; import cn.hutool.core.lang.Console; import cn.hutool.core.util.PageUtil; import cn.hutool.core.util.RandomUtil; +import lombok.AllArgsConstructor; +import lombok.Data; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; @@ -158,4 +160,29 @@ public class ListUtilTest { Assert.assertEquals(4, of.size()); Assert.assertEquals(1, sub.size()); } + + @Test + public void sortByPropertyTest(){ + @Data + @AllArgsConstructor + class TestBean{ + private int order; + private String name; + } + + final List beanList = ListUtil.toList( + new TestBean(2, "test2"), + new TestBean(1, "test1"), + new TestBean(5, "test5"), + new TestBean(4, "test4"), + new TestBean(3, "test3") + ); + + final List order = ListUtil.sortByProperty(beanList, "order"); + Assert.assertEquals("test1", order.get(0).getName()); + Assert.assertEquals("test2", order.get(1).getName()); + Assert.assertEquals("test3", order.get(2).getName()); + Assert.assertEquals("test4", order.get(3).getName()); + Assert.assertEquals("test5", order.get(4).getName()); + } }