컬렉션여러 필드로 정렬하다
3개의 필드(All String type)가 있는 "Report" 개체 목록이 있습니다.
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());
}
});
어떤 이유에서인지, 저는 정렬된 순서를 가지고 있지 않습니다.어떤 이는 필드 사이에 공백을 두라고 조언했지만, 왜 그랬을까요?
코드에 무슨 문제라도 있나요?
(원래 여러 필드를 기준으로 Java에서 객체 목록을 정렬하는 방법)
Java 8 람다 사용(2019년 4월 10일 추가)
Java 8은 람다를 통해 이 문제를 잘 해결합니다(Guava와 Apache Commons는 여전히 유연성을 제공할 수 있습니다).
Collections.sort(reportList, Comparator.comparing(Report::getReportKey)
.thenComparing(Report::getStudentNumber)
.thenComparing(Report::getSchool));
아래 @gaoagong의 답변에 감사드립니다.
여기서의 장점 중 하나는 getter가 게으르게 평가된다는 것입니다(예: getSchool()이치노
난잡하고 복잡함:손으로 정렬
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);
}
});
이 작업에는 많은 입력과 유지보수가 필요하며 오류가 발생하기 쉽습니다.유일한 장점은 관련된 경우에만 getter가 호출된다는 것입니다.
반영적 방법: BeanComparator를 사용한 정렬
ComparatorChain chain = new ComparatorChain(Arrays.asList(
new BeanComparator("size"),
new BeanComparator("nrOfToppings"),
new BeanComparator("name")));
Collections.sort(pizzas, chain);
분명히 이것은 보다 간결하지만, 스트링을 대신 사용하여 필드에 대한 직접 참조를 잃기 때문에 오류가 발생하기 쉽습니다(형식 안전성 없음, 자동 리팩터링).필드의 이름이 바뀌어도 컴파일러는 문제를 보고하지 않습니다.또한 이 솔루션은 반사를 사용하기 때문에 정렬 속도가 훨씬 느립니다.
목표 달성: Google Guava의 비교 체인으로 정렬
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 값은 기본적으로 더 낮게 평가되어야 합니다.null 필드의 경우 이 경우 수행할 작업을 Guava에 추가 지시해야 합니다.이것은 특정 작업을 수행하는 경우 유연한 메커니즘이지만 대부분의 경우 기본 케이스(1, a, b, z, null)를 사용합니다.
그리고 아래 코멘트에 기재된 바와 같이 이들 getter는 각각의 비교에 대해 즉시 평가됩니다.
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의 Comparison Chain과 마찬가지로 이 라이브러리 클래스는 여러 필드에서 쉽게 정렬되지만 null 값(1, a, b, z, null)에 대한 기본 동작도 정의합니다.그러나 자체 Comparator를 제공하지 않는 한 다른 항목을 지정할 수도 없습니다.
아래 코멘트에서 설명한 바와 같이, 이들 getter는 각각의 비교에 대해 즉시 평가됩니다.
따라서
결국 맛과 유연성의 필요성(Guava의 Comparison Chain)과 간결한 코드(Appache의 Comparate To Builder)로 귀결됩니다.
보너스 방식
Code Review의 우선순위에 따라 여러 개의 대조군을 조합한 훌륭한 솔루션을 찾았습니다.MultiComparator:
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는 이미 이를 위한 유틸리티를 보유하고 있습니다.
Comparator Utils.chained Comparator(comparator Collection)
Collections.sort(list, ComparatorUtils.chainedComparator(comparators));
코드에 무슨 문제라도 있나요?
네. 비교하기 전에 왜 세 개의 필드를 합산하는 거죠?
다음과 같은 작업을 수행할 수 있습니다. (필드가 원하는 순서대로 정렬되어 있다고 가정합니다.)
@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;
}
구아바의 약으로 비교기를 만들고 싶다.ComparisonChain:
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();
}
}
이것은 오래된 질문이기 때문에 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 + '\'' +
'}';
}
}
}
리포트 키, 학생 번호, 학교 순으로 정렬하려면 다음과 같이 해야 합니다.
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 값을 허용해야 하는 경우 더 복잡해집니다.
공간을 사용하여 문자열 연결 버전을 사용할 수 있지만 공간 등이 포함된 홀수 데이터가 있으면 여전히 실패합니다.위의 코드는 당신이 원하는 논리 코드입니다.먼저 보고서 키로 비교한 후 보고서 키가 동일한 경우에만 학생 번호를 신경써야 합니다.
Java 8 Lambda 접근 방식을 사용할 것을 권장합니다.
List<Report> reportList = new ArrayList<Report>();
reportList.sort(Comparator.comparing(Report::getRecord1).thenComparing(Report::getRecord2));
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 + "]";
}
}
Student Number가 숫자일 경우 숫자가 아닌 영숫자로 정렬됩니다.기대하지 않다
"2" < "11"
다음과 같습니다.
"11" < "2"
먼저 ReportKey를 기준으로 정렬한 후 Student Number를 기준으로 정렬한 후 School을 기준으로 정렬하려면 각 문자열을 연결하는 대신 비교해야 합니다.각 ReportKey의 길이 등이 같도록 문자열을 공백으로 채우면 메서드가 동작할 수 있습니다.그러나 이 방법은 그다지 노력하지 않아도 됩니다.대신 비교 방법을 변경하여 ReportKeys를 비교하고 compareTo가 0을 반환하는 경우 StudentNumber, School 순으로 시도합니다.
사용하다ComparatorJDK1.8에서 도입된 메서드와의 인터페이스:comparing그리고.thenComparing, 또는 그 이상의 구체적인 방법:comparingXXX그리고.thenComparingXXX.
예를 들어, 먼저 ID, 나이 순으로 목록을 정렬하려면 다음 이름을 지정하십시오.
Comparator<Person> comparator = Comparator.comparingLong(Person::getId)
.thenComparingInt(Person::getAge)
.thenComparing(Person::getName);
personList.sort(comparator);
저도 같은 문제가 있어서 컨피규레이션파일을 사용한 알고리즘이 필요했습니다.이 방법에서는 컨피규레이션파일로 정의된 여러 필드를 사용할 수 있습니다(리스트<문자열>설정만으로 시뮬레이트).
public static void test() {
// Associate your configName with your Comparator
Map<String, Comparator<DocumentDto>> map = new HashMap<>();
map.put("id", new IdSort());
map.put("createUser", new DocumentUserSort());
map.put("documentType", new DocumentTypeSort());
/**
In your config.yml file, you'll have something like
sortlist:
- documentType
- createUser
- id
*/
List<String> config = new ArrayList<>();
config.add("documentType");
config.add("createUser");
config.add("id");
List<Comparator<DocumentDto>> sorts = new ArrayList<>();
for (String comparator : config) {
sorts.add(map.get(comparator));
}
// Begin creation of the list
DocumentDto d1 = new DocumentDto();
d1.setDocumentType(new DocumentTypeDto());
d1.getDocumentType().setCode("A");
d1.setId(1);
d1.setCreateUser("Djory");
DocumentDto d2 = new DocumentDto();
d2.setDocumentType(new DocumentTypeDto());
d2.getDocumentType().setCode("A");
d2.setId(2);
d2.setCreateUser("Alex");
DocumentDto d3 = new DocumentDto();
d3.setDocumentType(new DocumentTypeDto());
d3.getDocumentType().setCode("A");
d3.setId(3);
d3.setCreateUser("Djory");
DocumentDto d4 = new DocumentDto();
d4.setDocumentType(new DocumentTypeDto());
d4.getDocumentType().setCode("A");
d4.setId(4);
d4.setCreateUser("Alex");
DocumentDto d5 = new DocumentDto();
d5.setDocumentType(new DocumentTypeDto());
d5.getDocumentType().setCode("D");
d5.setId(5);
d5.setCreateUser("Djory");
DocumentDto d6 = new DocumentDto();
d6.setDocumentType(new DocumentTypeDto());
d6.getDocumentType().setCode("B");
d6.setId(6);
d6.setCreateUser("Alex");
DocumentDto d7 = new DocumentDto();
d7.setDocumentType(new DocumentTypeDto());
d7.getDocumentType().setCode("B");
d7.setId(7);
d7.setCreateUser("Alex");
List<DocumentDto> documents = new ArrayList<>();
documents.add(d1);
documents.add(d2);
documents.add(d3);
documents.add(d4);
documents.add(d5);
documents.add(d6);
documents.add(d7);
// End creation of the list
// The Sort
Stream<DocumentDto> docStream = documents.stream();
// we need to reverse this list in order to sort by documentType first because stream are pull-based, last sorted() will have the priority
Collections.reverse(sorts);
for(Comparator<DocumentDto> entitySort : sorts){
docStream = docStream.sorted(entitySort);
}
documents = docStream.collect(Collectors.toList());
// documents has been sorted has you configured
// in case of equality second sort will be used.
System.out.println(documents);
}
비교기 개체는 매우 단순합니다.
public class IdSort implements Comparator<DocumentDto> {
@Override
public int compare(DocumentDto o1, DocumentDto o2) {
return o1.getId().compareTo(o2.getId());
}
}
public class DocumentUserSort implements Comparator<DocumentDto> {
@Override
public int compare(DocumentDto o1, DocumentDto o2) {
return o1.getCreateUser().compareTo(o2.getCreateUser());
}
}
public class DocumentTypeSort implements Comparator<DocumentDto> {
@Override
public int compare(DocumentDto o1, DocumentDto o2) {
return o1.getDocumentType().getCode().compareTo(o2.getDocumentType().getCode());
}
}
결론: 이 방법은 효율적이지 않지만 파일 구성을 사용하여 일반 정렬을 만들 수 있습니다.
다음으로 오브젝트 내의 2개의 필드(String과 int)를 비교한 완전한 예를 나타냅니다.또한 Collator를 사용하여 정렬합니다.
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;
}
}
}
출력:
코스트렐 1039737
310831원
비용 310832
비용: 1570019
위의 많은 답변에는 실제로 작동하지 않는 단일 비교기 방법과 비교한 필드가 있습니다.각 분야별로 다른 대조군이 구현되어 있는 몇 가지 답변이 있지만, 이 예가 훨씬 더 명확하고 이해하기 쉽기 때문에 이 글을 올린다.
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()));
}
}
산출량
내림차순 학생 리스트는 [학생 [생년=2018, bornMonth=2018, bornDay=2018, bornMonth=12, bornDay=2018], [생년=2018, 12, bornDay=11, BornDay], 학생 [생년=2018, BornDay]입니다.ay=http], student [2017년, bornMonth=6, bornDay=http], student [2017년, bornMonth=4, bornDay=http], student [born=2017년, bornMonth=2, bornDay=ht], student[2016년, born], born]
im my case List of Lists (개산 시험에서의):
List<T>.steam
.map(Class1.StaticInnerClass1::Field1)
.flatMap(x -> x.getField11ListStaticInnerClass2OfField1.stream())
.max(Comparator.comparing(Class1.StaticInnerClass2::Field21,Collections.reverseOrder())
.thenCompare(Class1.StaticInnerClass2::Field22));
언급URL : https://stackoverflow.com/questions/4258700/collections-sort-with-multiple-fields
'source' 카테고리의 다른 글
| 해싱 사전? (0) | 2022.10.22 |
|---|---|
| Java에서 정적 메서드를 덮어쓰고 오버로드할 수 있습니까? (0) | 2022.10.22 |
| 어떻게 개발 LAMP 서버에서 여러 버전의 PHP 5.x를 실행할 수 있습니까? (0) | 2022.10.22 |
| virtualenv와 pyenv의 관계는 무엇입니까? (0) | 2022.10.22 |
| JavaScript에서 문자열을 변수 이름으로 변환 (0) | 2022.10.22 |