com.feilong.core.util.comparator.PropertyComparator Maven / Gradle / Ivy
Show all versions of feilong Show documentation
/*
* Copyright (C) 2008 feilong
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.feilong.core.util.comparator;
import static com.feilong.core.lang.ClassUtil.isInstance;
import java.io.Serializable;
import java.util.Comparator;
import java.util.TreeMap;
import java.util.TreeSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.feilong.core.Validate;
import com.feilong.core.bean.ConvertUtil;
import com.feilong.core.bean.PropertyUtil;
import com.feilong.lib.collection4.comparators.ReverseComparator;
import com.feilong.lib.lang3.ObjectUtils;
/**
* 属性比较器,自动获取 T
中的属性名字是 {@link #propertyName}的值,进行比较,不用每个需要排序的字段创建 {@link Comparator}类.
*
* 关于 {@link #propertyName}:
*
*
*
* {@link #propertyName} 取出来的值,必须实现 {@link Comparable}接口,比如 {@link Integer}, {@link String}等类型
*
*
*
* 顺序:
*
*
*
* 该类默认是正序的形式,如果需要反序,请再使用 {@link ReverseComparator}进行包装
*
*
*
* @author feilong
* @param
* the generic type
* @see "org.springframework.beans.support.PropertyComparator"
* @see com.feilong.lib.beanutils.BeanComparator
* @see "org.apache.commons.collections4.ComparatorUtils"
* @see "org.apache.commons.collections4.comparators.BooleanComparator"
* @see "org.apache.commons.collections4.comparators.ReverseComparator"
* @see "org.apache.commons.collections4.comparators.ComparableComparator"
* @see java-
* lang-illegalargumentexception-comparison-method-violates-its-general-contr
* @see Java error: Comparison
* method violates its general contract
* @see Java中的TreeMap、Comparable、Comparator
* @since 1.2.0
*/
@SuppressWarnings("squid:S1192") //String literals should not be duplicated
public class PropertyComparator implements Comparator,Serializable{
/** The Constant serialVersionUID. */
private static final long serialVersionUID = -3159374167882773300L;
/** The Constant LOGGER. */
private static final Logger LOGGER = LoggerFactory.getLogger(PropertyComparator.class);
//----------------------------------------------------------------------------------------------------------
/**
* 指定bean对象排序属性名字(默认正序).
*
*
* 泛型T对象指定的属性名称,Possibly indexed and/or nested name of the property to be modified,参见
* propertyName,
* 该属性的value 必须实现 {@link Comparable}接口.
*
*/
private final String propertyName;
/** 提取 propertyName
的属性值之后,支持使用自定义的比较器来比较值,如果是null,那么使用默认规则. */
@SuppressWarnings("rawtypes")
private Comparator comparator;
/** 反射提取出来的值,需要类型转成到的类型. */
@SuppressWarnings("rawtypes")
private Class propertyValueConvertToClass;
//----------------------------------------------------------------------------------------------------------
/**
* 只有指定参数名字 propertyName
的构造函数.
*
* 排序规则:
*
*
* - 空元素排在后面
* - 提取bean对象指定属性名字的值,比较两个值,按照自然顺序排序,指定属性的属性值越小对应的对象排在前面,反之排在后面;如果对应的属性值是null,排在最前面
*
* -
* 如果两个值相等,那么比较对象本身
*
*
* - 如果对象实现了 {@link Comparable} 接口, 那么直接比较
* - 如果对象没有实现 {@link Comparable} 接口, 那么比较两个对象的hashCode
*
*
*
*
*
*
*
* 示例:
*
*
*
*
*
* public void testPropertyComparatorWithTreeSet(){
* UserSameHashCode userSameHashCode_1_name1 = new UserSameHashCode(1, "name1");
* UserSameHashCode userSameHashCode_5_name5 = new UserSameHashCode(5, "name5_5");
* UserSameHashCode userSameHashCode_5_name6 = new UserSameHashCode(5, "name5_6");
* UserSameHashCode userSameHashCode_5_name1 = new UserSameHashCode(5, "name5_1");
* UserSameHashCode userSameHashCode_5_name2 = new UserSameHashCode(5, "name5_2");
*
* UserSameHashCode nullPropertyValue = new UserSameHashCode(null, "name2");
*
* UserSameHashCode nullObject = null;
* //------
*
* //null 相同 留一个
*
* Set{@code } set = new TreeSet{@code <>}(new PropertyComparator{@code <>(}"id"));
* set.add(userSameHashCode_5_name5);
* set.add(userSameHashCode_5_name6);
*
* set.add(nullPropertyValue);
* set.add(nullObject);
*
* set.add(userSameHashCode_1_name1);
* set.add(userSameHashCode_5_name1);
* set.add(userSameHashCode_5_name2);
*
* set.add(nullObject);
* set.add(nullPropertyValue);
*
* //----------------------------------------------------------
*
* assertEquals(4, set.size());
* assertThat(set, allOf(contains(
* nullPropertyValue, //如果对应的属性值是null,排在后面
* userSameHashCode_1_name1, //指定属性的属性值越小对应的对象排在前面,反之排在后面
*
* //如果两个值相等,那么比较对象本身
* //如果对象没有实现 {@link Comparable} 接口, 那么比较两个对象的hashCode
* userSameHashCode_5_name5,
*
* nullObject)//空元素排在后面
* ));
* }
*
*
*
*
* @param propertyName
* 指定bean对象排序属性名字(默认正序).
*
*
* 泛型T对象指定的属性名称,Possibly indexed and/or nested name of the property to be modified,参见
* propertyName,
* 该属性的value 必须实现 {@link Comparable}接口.
*
*
* 如果 propertyName
是null,抛出 {@link NullPointerException}
* 如果 propertyName
是blank,抛出 {@link IllegalArgumentException}
*/
public PropertyComparator(String propertyName){
Validate.notBlank(propertyName, "propertyName can't be blank!");
this.propertyName = propertyName;
LOGGER.trace("propertyName:[{}]", propertyName);
}
/**
* The Constructor.
*
* @param propertyName
* 指定bean对象排序属性名字(默认正序).
*
*
* 泛型T对象指定的属性名称,Possibly indexed and/or nested name of the property to be modified,参见
* propertyName,
* 该属性的value 必须实现 {@link Comparable}接口.
*
*
* 如果 propertyName
是null,抛出 {@link NullPointerException}
* 如果 propertyName
是blank,抛出 {@link IllegalArgumentException}
* @param comparator
* 提取 propertyName
的属性值之后,支持使用自定义的比较器来比较值,如果是null,那么使用默认规则
* @since 1.5.4
*/
@SuppressWarnings("rawtypes")
public PropertyComparator(String propertyName, Comparator comparator){
Validate.notBlank(propertyName, "propertyName can't be blank!");
this.propertyName = propertyName;
this.comparator = comparator;
LOGGER.trace("propertyName:[{}],comparator:[{}]", propertyName, comparator);
}
/**
* The Constructor.
*
* 使用场景:
*
* 诸如需要排序的对象指定属性类型是数字,但是申明类型的时候由于种种原因是字符串,
* 此时需要排序,如果不转成Integer比较的话,字符串比较13 将会在 3数字的前面
*
*
* 示例:
*
*
*
* 我们现在有这样的数据,其中属性 totalNo是字符串类型的
*
*
*
* CourseEntity courseEntity1 = new CourseEntity();
* courseEntity1.setTotalNo("3");
*
* CourseEntity courseEntity2 = new CourseEntity();
* courseEntity2.setTotalNo("13");
*
* List{@code } courseList = new ArrayList{@code <>}();
* courseList.add(courseEntity1);
* courseList.add(courseEntity2);
*
*
* 如果 我们只是使用 propertyName进行排序的话:
*
*
* Collections.sort(courseList, new PropertyComparator{@code }("totalNo"));
* LOGGER.debug(JsonUtil.format(courseList));
*
*
* 那么返回:
*
*
* [{
* "totalNo": "13",
* "name": ""
* },{
* "totalNo": "3",
* "name": ""
* }]
*
*
* 如果我们使用 propertyName+ propertyValueConvertToClass进行排序的话:
*
*
* Collections.sort(courseList, new PropertyComparator{@code }("totalNo", Integer.class));
* LOGGER.debug(JsonUtil.format(courseList));
*
*
* 返回:
*
*
* [{
* "totalNo": "3",
* "name": ""
* },{
* "totalNo": "13",
* "name": ""
* }]
*
*
*
*
* @param propertyName
* 指定bean对象排序属性名字(默认正序).
*
*
* 泛型T对象指定的属性名称,Possibly indexed and/or nested name of the property to be modified,参见
* propertyName,
* 该属性的value 必须实现 {@link Comparable}接口.
*
*
* 如果 propertyName
是null,抛出 {@link NullPointerException}
* 如果 propertyName
是blank,抛出 {@link IllegalArgumentException}
* @param propertyValueConvertToClass
* 反射提取出来的值,需要类型转成到的类型
* @since 1.5.0
*/
@SuppressWarnings("rawtypes")
public PropertyComparator(String propertyName, Class propertyValueConvertToClass){
Validate.notBlank(propertyName, "propertyName can't be blank!");
this.propertyName = propertyName;
this.propertyValueConvertToClass = propertyValueConvertToClass;
LOGGER.trace("propertyName:[{}],propertyValueConvertToClass:[{}]", propertyName, propertyValueConvertToClass);
}
/**
* 带propertyName
和 propertyValueConvertToClass
以及 comparator
的构造函数.
*
* 使用场景:
*
* 诸如需要排序的对象指定属性类型是数字,但是申明类型的时候由于种种原因是字符串,
* 此时需要排序,如果不转成Integer比较的话,字符串比较13 将会在 3数字的前面
*
*
* 示例:
*
*
*
* 我们现在有这样的数据,其中属性 totalNo是字符串类型的
*
*
*
* CourseEntity courseEntity1 = new CourseEntity();
* courseEntity1.setTotalNo("3");
*
* CourseEntity courseEntity2 = new CourseEntity();
* courseEntity2.setTotalNo("13");
*
* List{@code } courseList = new ArrayList{@code <>}();
* courseList.add(courseEntity1);
* courseList.add(courseEntity2);
*
*
* 如果 我们只是使用 propertyName进行排序的话:
*
*
* Collections.sort(courseList, new PropertyComparator{@code }("totalNo"));
* LOGGER.debug(JsonUtil.format(courseList));
*
*
* 那么返回:
*
*
* [{
* "totalNo": "13",
* "name": ""
* },{
* "totalNo": "3",
* "name": ""
* }]
*
*
* 如果我们使用 propertyName+ propertyValueConvertToClass进行排序的话:
*
*
* Collections.sort(courseList, new PropertyComparator{@code }("totalNo", Integer.class,ComparatorUtils.NATURAL_COMPARATOR));
* LOGGER.debug(JsonUtil.format(courseList));
*
*
* 返回:
*
*
* [{
* "totalNo": "3",
* "name": ""
* },{
* "totalNo": "13",
* "name": ""
* }]
*
*
*
*
* @param propertyName
* 指定bean对象排序属性名字(默认正序).
*
*
* 泛型T对象指定的属性名称,Possibly indexed and/or nested name of the property to be modified,参见
* propertyName,
* 该属性的value 必须实现 {@link Comparable}接口.
*
*
* 如果 propertyName
是null,抛出 {@link NullPointerException}
* 如果 propertyName
是blank,抛出 {@link IllegalArgumentException}
*
* @param propertyValueConvertToClass
* 反射提取出来的值,需要类型转成到的类型
* @param comparator
* 提取 propertyName
的属性值之后,支持使用自定义的比较器来比较值,如果是null,那么使用默认规则
* @since 1.5.4
*/
@SuppressWarnings("rawtypes")
public PropertyComparator(String propertyName, Class propertyValueConvertToClass, Comparator comparator){
Validate.notBlank(propertyName, "propertyName can't be blank!");
this.propertyName = propertyName;
this.propertyValueConvertToClass = propertyValueConvertToClass;
this.comparator = comparator;
LOGGER.trace("propertyName:[{}]", propertyName);
}
//----------------------------------------------------------------------------------------------------------
/*
* (non-Javadoc)
*
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*
* @see org.apache.commons.lang3.ObjectUtils#compare(Comparable, Comparable)
*
* @see org.apache.commons.lang3.ObjectUtils#compare(Comparable, Comparable, boolean)
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public int compare(T t1,T t2){
if (t1 == t2){
return 0;
}else if (null == t1){//null 排在后面
return 1;
}else if (null == t2){
return -1;
}
//---------------------------------------------------------------------
Comparable propertyValue1 = PropertyUtil.getProperty(t1, propertyName);
Comparable propertyValue2 = PropertyUtil.getProperty(t2, propertyName);
//如果值需要类型转换
if (null != propertyValueConvertToClass){
propertyValue1 = ConvertUtil.convert(propertyValue1, propertyValueConvertToClass);
propertyValue2 = ConvertUtil.convert(propertyValue2, propertyValueConvertToClass);
}
return null == comparator ? compare(t1, t2, propertyValue1, propertyValue2) : comparator.compare(propertyValue1, propertyValue2);
}
//---------------------------------------------------------------
/**
* 先比较 propertyValue1以及propertyValue2,再比较 t1/t2 .
*
*
* 由于我们是提取 property的特殊性, 如果只判断值的话, 那么 {@link TreeSet} / {@link TreeMap} 过滤掉同sort字段但是对象不相同的情况
*
*
* @param t1
* the t1
* @param t2
* the t2
* @param propertyValue1
* the property value1
* @param propertyValue2
* the property value2
* @return the int
* @see com.feilong.lib.collection4.comparators.ComparableComparator
* @since 1.5.4
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private int compare(T t1,T t2,Comparable propertyValue1,Comparable propertyValue2){
//NullPointException if propertyValue1.compareTo(propertyValue2)
// if true null is considered greater than a non-null value or if false null is considered less than a Non-null value
boolean nullPropertyValueGreater = false;
int compareTo = ObjectUtils.compare(propertyValue1, propertyValue2, nullPropertyValueGreater);
if (0 != compareTo){
String pattern = "propertyName:[{}],propertyValue1:[{}],propertyValue2:[{}],result:[{}]";
LOGGER.trace(pattern, propertyName, propertyValue1, propertyValue2, compareTo);
return compareTo;
}
//比较值相等的情况
return compareWithSameValue(t1, t2);
}
//---------------------------------------------------------------
/**
* 处理值相等的情况.
*
*
* 如果用于 {@link TreeSet}/{@link TreeMap},那么建议 bean 对象需要实现 {@link Comparable} 接口
*
* 避免过滤掉同sort字段但是对象不相同的情况
*
*
* 说明:
*
*
* - 如果对象实现了 {@link Comparable} 接口, 那么直接比较
* - 如果对象没有实现 {@link Comparable} 接口, 那么比较两个对象的hashCode
*
*
*
* @param t1
* the t 1
* @param t2
* the t 2
* @return 如果对象实现了 {@link Comparable} 接口, 那么直接强转比较
* 如果对象没有实现 {@link Comparable} 接口, 那么比较两个对象的hashCode
* @see PropertyComparator hash判断两个对象是否相等是否太草率?
* @see SortUtil.sortMapByValueDesc(Map) 会报异常
* @since 1.10.3
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private int compareWithSameValue(T t1,T t2){
//如果对象实现了 Comparable 接口, 那么直接强转比较
return isInstance(t1, Comparable.class) ? ObjectUtils.compare((Comparable) t1, (Comparable) t2)
: ObjectUtils.compare(t1.hashCode(), t2.hashCode());
}
}