web-dev-qa-db-ja.com

複数のフィールドを持つCollections.sort

3つのフィールド(すべて文字列タイプ)を持つ「レポート」オブジェクトのリストがあります。

ReportKey
StudentNumber
School

私はソートコードを持っています

Collections.sort(reportList, new Comparator<Report>() {

@Override
public int compare(final Report record1, final Report record2) {
      return (record1.getReportKey() + record1.getStudentNumber() + record1.getSchool())                      
        .compareTo(record2.getReportKey() + record2.getStudentNumber() + record2.getSchool());
      }

});

何らかの理由で、ソートされた順序がありません。フィールド間にスペースを入れることをお勧めしますが、なぜですか?

コードに何か問題がありますか?

66
Milli Szabo

コードに何か問題がありますか?

はい。比較する前に3つのフィールドを一緒に追加するのはなぜですか?

私はおそらくこのようなことをするでしょう:(フィールドがあなたがそれらをソートしたい順番にあると仮定して)

@Override public int compare(final Report record1, final Report record2) {
    int c;
    c = record1.getReportKey().compareTo(record2.getReportKey());
    if (c == 0)
       c = record1.getStudentNumber().compareTo(record2.getStudentNumber());
    if (c == 0)
       c = record1.getSchool().compareTo(record2.getSchool());
    return c;
}
113
Jason S

(from Java)のオブジェクトのリストをソートする方法

this Gist の作業コード

Java 8ラムダの使用(2019年4月10日追加)

Java 8はこれをラムダでうまく解決します(ただし、GuavaとApache Commonsはさらに柔軟性を提供します)。

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

@gaoagongの 以下の回答 に感謝します。

面倒で複雑な:手作業で並べ替える

Collections.sort(pizzas, new Comparator<Pizza>() {  
    @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);  
    }  
});  

これには多くの入力、メンテナンスが必要で、エラーが発生しやすくなります。

リフレクティブな方法:BeanComparatorによるソート

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

Collections.sort(pizzas, chain);  

明らかにこれはより簡潔ですが、代わりに文字列を使用してフィールドへの直接参照を失うため、さらにエラーが発生しやすくなります(型安全性、自動リファクタリングなし)。これで、フィールドの名前が変更されても、コンパイラは問題を報告しません。さらに、このソリューションはリフレクションを使用するため、ソートがはるかに遅くなります。

アクセス方法:Google GuavaのComparisonChainによる並べ替え

Collections.sort(pizzas, new Comparator<Pizza>() {  
    @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(); 
        */  
    }  
});  

これははるかに優れていますが、最も一般的な使用例ではボイラープレートコードがいくつか必要です。既定では、null値の値を小さくする必要があります。ヌルフィールドの場合、その場合の対処方法をグアバに追加する必要があります。これは、特定のことをしたい場合の柔軟なメカニズムですが、多くの場合、デフォルトのケース(1、a、b、z、null)が必要です。

Apache Commons CompareToBuilderによる並べ替え

Collections.sort(pizzas, new Comparator<Pizza>() {  
    @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();  
    }  
});  

GuavaのComparisonChainと同様に、このライブラリクラスは複数のフィールドで簡単に並べ替えられますが、null値(1、a、b、z、nullなど)のデフォルトの動作も定義します。ただし、独自のコンパレータを提供しない限り、他のものも指定できません。

副<文>この[前述の事実の]結果として、それ故に、従って、だから◆【同】consequently; therefore <文>このような方法で、このようにして、こんなふうに、上に述べたように◆【同】in this manner <文>そのような程度まで<文> AひいてはB◆【用法】A and thus B <文>例えば◆【同】for example; as an example

最終的には、フレーバーと柔軟性(GuavaのComparisonChain)と簡潔なコード(ApacheのCompareToBuilder)が必要になります。

ボーナス方法

優先順位 CodeReviewでMultiComparatorで複数のコンパレーターを組み合わせたNiceソリューションを見つけました。

class MultiComparator<T> implements Comparator<T> {
    private final List<Comparator<T>> comparators;

    public MultiComparator(List<Comparator<? super T>> comparators) {
        this.comparators = comparators;
    }

    public MultiComparator(Comparator<? super T>... comparators) {
        this(Arrays.asList(comparators));
    }

    public int compare(T o1, T o2) {
        for (Comparator<T> c : comparators) {
            int result = c.compare(o1, o2);
            if (result != 0) {
                return result;
            }
        }
        return 0;
    }

    public static <T> void sort(List<T> list, Comparator<? super T>... comparators) {
        Collections.sort(list, new MultiComparator<T>(comparators));
    }
}

もちろん、Apache Commons Collectionsにはこのためのユーティリティが既にあります。

ComparatorUtils.chainedComparator(comparatorCollection)

Collections.sort(list, ComparatorUtils.chainedComparator(comparators));
58
Benny Bottema

GuavaComparisonChainを使用してコンパレータを作成します。

public class ReportComparator implements Comparator<Report> {
  public int compare(Report r1, Report r2) {
    return ComparisonChain.start()
        .compare(r1.getReportKey(), r2.getReportKey())
        .compare(r1.getStudentNumber(), r2.getStudentNumber())
        .compare(r1.getSchool(), r2.getSchool())
        .result();
  }
}
44
ColinD

これは古い質問なので、Java 8に相当するものは表示されません。この特定のケースの例を次に示します。

import Java.util.ArrayList;
import Java.util.Collections;
import Java.util.Comparator;
import Java.util.List;

/**
 * Compares multiple parts of the Report object.
 */
public class SimpleJava8ComparatorClass {

    public static void main(String[] args) {
        List<Report> reportList = new ArrayList<>();
        reportList.add(new Report("reportKey2", "studentNumber2", "school1"));
        reportList.add(new Report("reportKey4", "studentNumber4", "school6"));
        reportList.add(new Report("reportKey1", "studentNumber1", "school1"));
        reportList.add(new Report("reportKey3", "studentNumber2", "school4"));
        reportList.add(new Report("reportKey2", "studentNumber2", "school3"));

        System.out.println("pre-sorting");
        System.out.println(reportList);
        System.out.println();

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

        System.out.println("post-sorting");
        System.out.println(reportList);
    }

    private static class Report {

        private String reportKey;
        private String studentNumber;
        private String school;

        public Report(String reportKey, String studentNumber, String school) {
            this.reportKey = reportKey;
            this.studentNumber = studentNumber;
            this.school = school;
        }

        public String getReportKey() {
            return reportKey;
        }

        public void setReportKey(String reportKey) {
            this.reportKey = reportKey;
        }

        public String getStudentNumber() {
            return studentNumber;
        }

        public void setStudentNumber(String studentNumber) {
            this.studentNumber = studentNumber;
        }

        public String getSchool() {
            return school;
        }

        public void setSchool(String school) {
            this.school = school;
        }

        @Override
        public String toString() {
            return "Report{" +
                   "reportKey='" + reportKey + '\'' +
                   ", studentNumber='" + studentNumber + '\'' +
                   ", school='" + school + '\'' +
                   '}';
        }
    }
}
17
gaoagong

レポートキー、学生番号、学校の順に並べ替える場合は、次のようにする必要があります。

public class ReportComparator implements Comparator<Report>
{
    public int compare(Report r1, Report r2)
    {
        int result = r1.getReportKey().compareTo(r2.getReportKey());
        if (result != 0)
        {
            return result;
        }
        result = r1.getStudentNumber().compareTo(r2.getStudentNumber());
        if (result != 0)
        {
            return result;
        }
        return r1.getSchool().compareTo(r2.getSchool());
    }
}

もちろん、値はどれもnullにできないことを前提としています。レポート、レポートキー、学生番号、または学校にnull値を許可する必要がある場合は、より複雑になります。

couldを使用して、スペースを使用して文字列連結バージョンを取得しますが、奇妙な場合、スペースなどを含む奇妙なデータがあると失敗します。上記のコードはlogical =必要なコード...最初にレポートキーで比較し、次にレポートキーが同じ場合にのみ学生番号で悩まします。

13
Jon Skeet

Java 8 Lambdaアプローチ:

List<Report> reportList = new ArrayList<Report>();
reportList.sort(Comparator.comparing(Report::getRecord1).thenComparing(Report::getRecord2));
6
Zia Ul Mustafa

Java8の複数のフィールドを使用したソート

package com.Java8.chapter1;

import Java.util.Arrays;
import Java.util.Comparator;
import Java.util.List;
import static Java.util.Comparator.*;



 public class Example1 {

    public static void main(String[] args) {
        List<Employee> empList = getEmpList();


        // Before Java 8 
        empList.sort(new Comparator<Employee>() {

            @Override
            public int compare(Employee o1, Employee o2) {
                int res = o1.getDesignation().compareTo(o2.getDesignation());
                if (res == 0) {
                    return o1.getSalary() > o2.getSalary() ? 1 : o1.getSalary() < o2.getSalary() ? -1 : 0;
                } else {
                    return res;
                }

            }
        });
        for (Employee emp : empList) {
            System.out.println(emp);
        }
        System.out.println("---------------------------------------------------------------------------");

        // In Java 8

        empList.sort(comparing(Employee::getDesignation).thenComparing(Employee::getSalary));
        empList.stream().forEach(System.out::println);

    }
    private static List<Employee> getEmpList() {
        return Arrays.asList(new Employee("Lakshman A", "Consultent", 450000),
                new Employee("Chaitra S", "Developer", 250000), new Employee("Manoj PVN", "Developer", 250000),
                new Employee("Ramesh R", "Developer", 280000), new Employee("Suresh S", "Developer", 270000),
                new Employee("Jaishree", "Opearations HR", 350000));
    }
}

class Employee {
    private String fullName;
    private String designation;
    private double salary;

    public Employee(String fullName, String designation, double salary) {
        super();
        this.fullName = fullName;
        this.designation = designation;
        this.salary = salary;
    }

    public String getFullName() {
        return fullName;
    }

    public String getDesignation() {
        return designation;
    }

    public double getSalary() {
        return salary;
    }

    @Override
    public String toString() {
        return "Employee [fullName=" + fullName + ", designation=" + designation + ", salary=" + salary + "]";
    }

}
5
Lakshman Miani

JDK1.8で導入されたメソッドでComparatorインターフェイスを使用します:comparingおよびthenComparing、またはより具体的なメソッド:comparingXXXおよびthenComparingXXX

たとえば、最初にIDで、次に年齢で、次に名前で人のリストをソートする場合:

            Comparator<Person> comparator = Comparator.comparingLong(Person::getId)
                    .thenComparingInt(Person::getAge)
                    .thenComparing(Person::getName);
            personList.sort(comparator);
3
puppylpg

ReportKey、Student Number、Schoolの順に並べ替える場合は、連結するのではなく、各文字列を比較する必要があります。各ReportKeyが同じ長さになるように文字列にスペースを埋め込むとメソッドが機能する場合がありますが、実際に努力する価値はありません。代わりに、compareToが0を返し、StudentNumberを試し、次にSchoolを試す場合、単にCompareメソッドを変更してReportKeysを比較します。

3
jzd

StudentNumberが数値の場合、数値ではなく英数字でソートされます。期待しないでください

"2" < "11"

そうなる:

"11" < "2"
3
FrVaBe

上記の多くの回答には、実際に機能していない単一のコンパレーター方式で比較されたフィールドがあります。いくつかの答えがありますが、各フィールドに異なるコンパレータが実装されているため、この例をより明確で理解しやすいと信じているため、これを投稿しています。

class Student{
    Integer bornYear;
    Integer bornMonth;
    Integer bornDay;
    public Student(int bornYear, int bornMonth, int bornDay) {

        this.bornYear = bornYear;
        this.bornMonth = bornMonth;
        this.bornDay = bornDay;
    }
    public Student(int bornYear, int bornMonth) {

        this.bornYear = bornYear;
        this.bornMonth = bornMonth;

    }
    public Student(int bornYear) {

        this.bornYear = bornYear;

    }
    public Integer getBornYear() {
        return bornYear;
    }
    public void setBornYear(int bornYear) {
        this.bornYear = bornYear;
    }
    public Integer getBornMonth() {
        return bornMonth;
    }
    public void setBornMonth(int bornMonth) {
        this.bornMonth = bornMonth;
    }
    public Integer getBornDay() {
        return bornDay;
    }
    public void setBornDay(int bornDay) {
        this.bornDay = bornDay;
    }
    @Override
    public String toString() {
        return "Student [bornYear=" + bornYear + ", bornMonth=" + bornMonth + ", bornDay=" + bornDay + "]";
    }


}
class TestClass
{       

    // Comparator problem in Java for sorting objects based on multiple fields 
    public static void main(String[] args)
    {
        int N,c;// Number of threads

        Student s1=new Student(2018,12);
        Student s2=new Student(2018,12);
        Student s3=new Student(2018,11);
        Student s4=new Student(2017,6);
        Student s5=new Student(2017,4);
        Student s6=new Student(2016,8);
        Student s7=new Student(2018);
        Student s8=new Student(2017,8);
        Student s9=new Student(2017,2);
        Student s10=new Student(2017,9);

        List<Student> studentList=new ArrayList<>();
        studentList.add(s1);
        studentList.add(s2);
        studentList.add(s3);
        studentList.add(s4);
        studentList.add(s5);
        studentList.add(s6);
        studentList.add(s7);
        studentList.add(s8);
        studentList.add(s9);
        studentList.add(s10);

        Comparator<Student> byMonth=new Comparator<Student>() {
            @Override
            public int compare(Student st1,Student st2) {
                if(st1.getBornMonth()!=null && st2.getBornMonth()!=null) {
                    return st2.getBornMonth()-st1.getBornMonth();
                }
                else if(st1.getBornMonth()!=null) {
                    return 1;
                }
                else {
                    return -1;
                }
        }};

        Collections.sort(studentList, new Comparator<Student>() {
            @Override
            public int compare(Student st1,Student st2) {
                return st2.getBornYear()-st1.getBornYear();
        }}.thenComparing(byMonth));

        System.out.println("The sorted students list in descending is"+Arrays.deepToString(studentList.toArray()));



    }

}

[〜#〜] output [〜#〜]

降順で並べ替えられた生徒のリストは、[生徒[bornYear = 2018、bornMonth = null、bornDay = null]、生徒[bornYear = 2018、bornMonth = 12、bornDay = null]、生徒[bornYear = 2018、bornMonth = 12、bornDay = null]、Student [bornYear = 2018、bornMonth = 11、bornDay = null]、Student [bornYear = 2017、bornMonth = 9、bornDay = null]、Student [bornYear = 2017、bornMonth = 8、bornDay = null]、Student [ bornYear = 2017、bornMonth = 6、bornDay = null]、Student [bornYear = 2017、bornMonth = 4、bornDay = null]、Student [bornYear = 2017、bornMonth = 2、bornDay = null]、Student [bornYear = 2016、bornMonth = 8、bornDay = null]]

0
Naseer Mohammad

オブジェクトの2つのフィールド(1つのストリングと1つのint)を比較する完全な例を次に示します。これもCollat​​orを使用してソートします。

public class Test {

    public static void main(String[] args) {

        Collator myCollator;
        myCollator = Collator.getInstance(Locale.US);

        List<Item> items = new ArrayList<Item>();

        items.add(new Item("costrels", 1039737, ""));
        items.add(new Item("Costs", 1570019, ""));
        items.add(new Item("costs", 310831, ""));
        items.add(new Item("costs", 310832, ""));

        Collections.sort(items, new Comparator<Item>() {
            @Override
            public int compare(final Item record1, final Item record2) {
                int c;
                //c = record1.item1.compareTo(record2.item1); //optional comparison without Collator                
                c = myCollator.compare(record1.item1, record2.item1);
                if (c == 0) 
                {
                    return record1.item2 < record2.item2 ? -1
                            :  record1.item2 > record2.item2 ? 1
                            : 0;
                }
                return c;
            }
        });     

        for (Item item : items)
        {
            System.out.println(item.item1);
            System.out.println(item.item2);
        }       

    }

    public static class Item
    {
        public String item1;
        public int item2;
        public String item3;

        public Item(String item1, int item2, String item3)
        {
            this.item1 = item1;
            this.item2 = item2;
            this.item3 = item3;
        }       
    }

}

出力:

costrels 1039737

コスト310831

コスト310832

コスト1570019

0
live-love