• List对象集合按照对象某一属性字段排序


    1. @Slf4j
    2. public class SortByField {
    3. static class Cmp implements Comparator {
    4. Method getMethod = null;
    5. Field fieldToGet = null;
    6. MethodHandle cmpMethodHandle;
    7. Cmp(MethodHandle cmpMethodHandle, Method getMethod) {
    8. this.getMethod = getMethod;
    9. this.cmpMethodHandle = cmpMethodHandle;
    10. }
    11. Cmp(MethodHandle cmpMethodHandle, Field fieldToGet) {
    12. this.cmpMethodHandle = cmpMethodHandle;
    13. this.fieldToGet = fieldToGet;
    14. }
    15. @Override
    16. public int compare(E o1, E o2) {
    17. if (o1 == null && o2 == null) {
    18. return 0;
    19. }
    20. if (o1 == null) {
    21. return -1;
    22. }
    23. if (o2 == null) {
    24. return 1;
    25. }
    26. try {
    27. Object o1GetMethod = getMethod.invoke(o1);
    28. Object o2GetMethod = getMethod.invoke(o2);
    29. if (o1GetMethod == null && o2GetMethod == null) {
    30. return 0;
    31. }
    32. if (o1GetMethod == null) {
    33. return -1;
    34. }
    35. if (o2GetMethod == null) {
    36. return 1;
    37. }
    38. log.info(o1GetMethod.toString().concat("-").concat(o2GetMethod.toString()));
    39. if (getMethod != null) {
    40. return (int) cmpMethodHandle.invokeExact((Comparable) o1GetMethod,
    41. o2GetMethod);
    42. }
    43. if (fieldToGet != null) {
    44. return (int) cmpMethodHandle.invokeExact((Comparable) fieldToGet.get(o1), fieldToGet.get(o2));
    45. }
    46. } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
    47. log.error("If sortByField() isn't modifier, it won't print errorStackTrace. Default return 0", e);
    48. } catch (Throwable e) {
    49. log.error("unknown error...", e);
    50. }
    51. return 0;
    52. }
    53. }
    54. /**
    55. * sort a list containing JavaBean according specific key( field ).
    56. * Mostly, sortByField take ~1.5 times as much as Traditional implementation when list.size() > 100K
    57. *
    58. * @param list: list to be sorted
    59. * @param fieldName: sort list according this field
    60. * @param order: asc(default) or desc
    61. */
    62. public static void sortByField(List list, String fieldName, String order) {
    63. if (list == null || list.size() < 2) { // no need to sort
    64. return;
    65. }
    66. if (fieldName == null || fieldName.trim().isEmpty())
    67. // won't sort if fieldName is null or ""
    68. return;
    69. // get actual class of generic E
    70. Class eClazz = null; // use reflect to get the actual class
    71. boolean isAllNull = true; // default all elements are null
    72. for (E e : list) {
    73. if (e != null) {
    74. isAllNull = false;
    75. eClazz = e.getClass();
    76. break;
    77. }
    78. }
    79. if (isAllNull)
    80. // no need to sort, because all elements are null
    81. return;
    82. // check fieldName in Class E
    83. Field keyField; // the Field as sort key
    84. try {
    85. keyField = eClazz.getDeclaredField(fieldName);
    86. } catch (NoSuchFieldException e1) {
    87. log.error(String.format("The List doesn't contain fieldName. That is %s has no Field %s.", eClazz, fieldName), e1);
    88. throw new Exception(String.format("列表不包含字段名称. That is %s has no Field %s.", eClazz, fieldName));
    89. } catch (SecurityException e1) {
    90. log.error("deny access to class or field.", e1);
    91. throw new Exception("拒绝访问类或字段");
    92. }
    93. // check field is either Comparable
    94. Class fieldClazz = keyField.getType();
    95. boolean isComparable = Comparable.class.isAssignableFrom(fieldClazz);
    96. if (!isComparable)
    97. return; // if the class of fieldName is not comparable, don't sort
    98. // try to use getter method to get field first. Because a little faster
    99. // than Field.get(Object)
    100. StringBuilder getterName; // adapt to JavaBean getter method
    101. if (fieldClazz.getSimpleName().equals("Boolean")) {
    102. getterName = new StringBuilder("is");
    103. } else {
    104. getterName = new StringBuilder("get");
    105. }
    106. // 将第一个字符更改为大写
    107. /*
    108. char[] cs = fieldName.toCharArray();
    109. if (cs[0] >= 'a' && cs[0] <= 'z')
    110. cs[0] -= 32;
    111. getterName.append(cs);
    112. */
    113. String capitalizeFieldName = StringUtils.capitalize(fieldName);
    114. getterName.append(capitalizeFieldName);
    115. Method getterMethod;
    116. try {
    117. getterMethod = eClazz.getDeclaredMethod(getterName.toString());
    118. } catch (NoSuchMethodException | SecurityException e1) {
    119. log.error("Field " + fieldName + " has no " + getterName + "() . ", e1);
    120. throw new Exception("Field " + fieldName + " has no " + getterName + "() . ");
    121. }
    122. Cmp cmp;
    123. MethodHandles.Lookup lookup = MethodHandles.lookup();
    124. MethodType type = MethodType.methodType(int.class, Object.class);
    125. MethodHandle mh;
    126. try {
    127. mh = lookup.findVirtual(Comparable.class, "compareTo", type);
    128. } catch (NoSuchMethodException | IllegalAccessException e1) {
    129. throw new Exception(String.format("未知的排序字段:%s", fieldName));
    130. }
    131. if (getterMethod != null) {
    132. getterMethod.setAccessible(true);
    133. cmp = new Cmp<>(mh, getterMethod);
    134. } else {
    135. // if cannot find getter method, use reflect to get specified
    136. // field
    137. keyField.setAccessible(true);
    138. cmp = new Cmp<>(mh, keyField);
    139. }
    140. if (order.equalsIgnoreCase("desc")) {
    141. list.sort(Collections.reverseOrder(cmp));
    142. return;
    143. }
    144. list.sort(cmp);
    145. }
    146. /**
    147. * sort a list containing JavaBean according specific key( field ) order by
    148. * ascend.
    149. *

    150. * Mostly, sortByField take ~1.5 times as much as Traditional implementation when list.size() > 100K
    151. *
    152. * @param list list to be sort
    153. * @param fieldName sort list according this field
    154. */
    155. public static void sortByField(List list, String fieldName) {
    156. sortByField(list, fieldName, "asc");
    157. }
    158. }
    159. // Collections.reverseOrder(Comparator cmp)源码:
    160. public static Comparator reverseOrder(Comparator cmp) {
    161. if (cmp == null)
    162. return reverseOrder();
    163. if (cmp instanceof ReverseComparator2)
    164. return ((ReverseComparator2)cmp).cmp;
    165. return new ReverseComparator2<>(cmp);
    166. }
    167. // ((ReverseComparator2)cmp).cmp compare方法
    168. public int compare(T t1, T t2) {
    169. return cmp.compare(t2, t1);
    170. }

    从Collections.reverseOrder(Comparator cmp)源码中可以看出,在执行Compartor比较器的重写方法compare(Obiect o1, Object o2)前,会根据排序方式(升序/降序),将o1和o2进行不同的赋值

    1. // 测试类
    2. @NoArgsConstructor
    3. @AllArgsConstructor
    4. @Data
    5. class strClass {
    6. String name;
    7. }
    8. @Test
    9. void sortTest() {
    10. List stringList = Lists.newArrayList(new strClass("aaa"),
    11. new strClass("bbb"));
    12. SortByField.sortByField(stringList, "name");
    13. System.out.println("升序结果:" + stringList.stream().map(strClass::getName).collect(Collectors.toList()));
    14. SortByField.sortByField(stringList, "name", "desc");
    15. System.out.println("降序结果:" + stringList.stream().map(strClass::getName).collect(Collectors.toList()));
    16. }

    测试结果:

    1. 16:53:44.911 [main] INFO xxx.SortByField - o1:bbb-o2:aaa
    2. 16:53:44.916 [main] INFO xxx.SortByField - Comparator override compare() return:1
    3. 升序结果:[aaa, bbb]
    4. 16:53:44.918 [main] INFO xxx.SortByField - o1:aaa-o2:bbb
    5. 16:53:44.918 [main] INFO xxx.SortByField - Comparator override compare() return:-1
    6. 降序结果:[bbb, aaa]

    排序时调用重写的compartor的compare(Obiect o1, Object o2)方法,在compare方法里比较o1和o2;

    从测试结果可以得出:

    当o1大于o2时,返回1,当o1小于o2时,返回-1,当o1等于o2时,返回0;

    当compare()返回值为-1时,则需要调整o1和o2的位置;

    注意:

    当对一个list{A,B}进行排序时:

    • 若为升序排序:在执行compare()方法时,会将A赋值给o2,将B赋值给o1;
    • 若为降序排序:在执行compare()方法时,会将A赋值给o1,将B赋值给o2;

    源码参考:https://github.com/Tony36051/sortByField/blob/master/src/main/java/SortByField.java

  • 相关阅读:
    【校招VIP】产品深入分析之电商运营
    ElasticSearch - 初步检索
    【计组】总线
    使用 Vue3 构建 Web Components
    LabVIEW和Arduino的巧妙结合(基础篇—1)
    spark-submit源码解析
    Java基础知识:逻辑与三元运算符(笔记)
    idea Gradle 控制台中文乱码
    图像梯度(opencv-c++)
    MySQL高级语句(一)
  • 原文地址:https://blog.csdn.net/sliencr/article/details/134458048