How to compare objects by multiple fields

后端 未结 22 2558
暖寄归人
暖寄归人 2020-11-22 00:43

Assume you have some objects which have several fields they can be compared by:

public class Person {

    private String firstName;
    private String lastN         


        
22条回答
  •  醉话见心
    2020-11-22 01:17

    (from Ways to sort lists of objects in Java based on multiple fields)

    Working code in this gist

    Using Java 8 lambda's (added April 10, 2019)

    Java 8 solves this nicely by lambda's (though Guava and Apache Commons might still offer more flexibility):

    Collections.sort(reportList, Comparator.comparing(Report::getReportKey)
                .thenComparing(Report::getStudentNumber)
                .thenComparing(Report::getSchool));
    

    Thanks to @gaoagong's answer below.

    Messy and convoluted: Sorting by hand

    Collections.sort(pizzas, new Comparator() {  
        @Override  
        public int compare(Pizza p1, Pizza p2) {  
            int sizeCmp = p1.size.compareTo(p2.size);  
            if (sizeCmp != 0) {  
                return sizeCmp;  
            }  
            int nrOfToppingsCmp = p1.nrOfToppings.compareTo(p2.nrOfToppings);  
            if (nrOfToppingsCmp != 0) {  
                return nrOfToppingsCmp;  
            }  
            return p1.name.compareTo(p2.name);  
        }  
    });  
    

    This requires a lot of typing, maintenance and is error prone.

    The reflective way: Sorting with BeanComparator

    ComparatorChain chain = new ComparatorChain(Arrays.asList(
       new BeanComparator("size"), 
       new BeanComparator("nrOfToppings"), 
       new BeanComparator("name")));
    
    Collections.sort(pizzas, chain);  
    

    Obviously this is more concise, but even more error prone as you lose your direct reference to the fields by using Strings instead (no typesafety, auto-refactorings). Now if a field is renamed, the compiler won’t even report a problem. Moreover, because this solution uses reflection, the sorting is much slower.

    Getting there: Sorting with Google Guava’s ComparisonChain

    Collections.sort(pizzas, new Comparator() {  
        @Override  
        public int compare(Pizza p1, Pizza p2) {  
            return ComparisonChain.start().compare(p1.size, p2.size).compare(p1.nrOfToppings, p2.nrOfToppings).compare(p1.name, p2.name).result();  
            // or in case the fields can be null:  
            /* 
            return ComparisonChain.start() 
               .compare(p1.size, p2.size, Ordering.natural().nullsLast()) 
               .compare(p1.nrOfToppings, p2.nrOfToppings, Ordering.natural().nullsLast()) 
               .compare(p1.name, p2.name, Ordering.natural().nullsLast()) 
               .result(); 
            */  
        }  
    });  
    

    This is much better, but requires some boiler plate code for the most common use case: null-values should be valued less by default. For null-fields, you have to provide an extra directive to Guava what to do in that case. This is a flexible mechanism if you want to do something specific, but often you want the default case (ie. 1, a, b, z, null).

    Sorting with Apache Commons CompareToBuilder

    Collections.sort(pizzas, new Comparator() {  
        @Override  
        public int compare(Pizza p1, Pizza p2) {  
            return new CompareToBuilder().append(p1.size, p2.size).append(p1.nrOfToppings, p2.nrOfToppings).append(p1.name, p2.name).toComparison();  
        }  
    });  
    

    Like Guava’s ComparisonChain, this library class sorts easily on multiple fields, but also defines default behavior for null values (ie. 1, a, b, z, null). However, you can’t specify anything else either, unless you provide your own Comparator.

    Thus

    Ultimately it comes down to flavor and the need for flexibility (Guava’s ComparisonChain) vs. concise code (Apache’s CompareToBuilder).

    Bonus method

    I found a nice solution that combines multiple comparators in order of priority on CodeReview in a MultiComparator:

    class MultiComparator implements Comparator {
        private final List> comparators;
    
        public MultiComparator(List> comparators) {
            this.comparators = comparators;
        }
    
        public MultiComparator(Comparator... comparators) {
            this(Arrays.asList(comparators));
        }
    
        public int compare(T o1, T o2) {
            for (Comparator c : comparators) {
                int result = c.compare(o1, o2);
                if (result != 0) {
                    return result;
                }
            }
            return 0;
        }
    
        public static  void sort(List list, Comparator... comparators) {
            Collections.sort(list, new MultiComparator(comparators));
        }
    }
    

    Ofcourse Apache Commons Collections has a util for this already:

    ComparatorUtils.chainedComparator(comparatorCollection)

    Collections.sort(list, ComparatorUtils.chainedComparator(comparators));
    

提交回复
热议问题