I have a list of "Report" objects with three fields (All String type)-
ReportKey
StudentNumber
School
I have a sort code goes like-
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());
}
});
For some reason, I don't have the sorted order. One advised to put spaces in between fields, but why?
Do you see anything wrong with the code?
(originally from Ways to sort lists of objects in Java based on multiple fields)
Original 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.
Note that one advantage here is that the getters are evaluated lazily (eg. getSchool() is only evaluated if relevant).
Messy and convoluted: Sorting by hand
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);
}
});
This requires a lot of typing, maintenance and is error prone. The only advantage is that getters are only invoked when relevant.
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<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();
*/
}
});
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).
And as noted in the comments below, these getters are all evaluated immediately for each comparison.
Sorting with 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();
}
});
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.
Again, as noted in the comments below, these getters are all evaluated immediately for each comparison.
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<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));
}
}
Ofcourse Apache Commons Collections has a util for this already:
ComparatorUtils.chainedComparator(comparatorCollection)
Collections.sort(list, ComparatorUtils.chainedComparator(comparators));
Do you see anything wrong with the code?
Yes. Why are you adding the three fields together before you compare them?
I would probably do something like this: (assuming the fields are in the order you wish to sort them in)
#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;
}
I'd make a comparator using Guava's 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();
}
}
This is an old question so I don't see a Java 8 equivalent. Here is an example for this specific case.
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 + '\'' +
'}';
}
}
}
If you want to sort by report key, then student number, then school, you should do something like this:
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());
}
}
This assumes none of the values can be null, of course - it gets more complicated if you need to allow for null values for the report, report key, student number or school.
While you could get the string concatenation version to work using spaces, it would still fail in strange cases if you had odd data which itself included spaces etc. The above code is the logical code you want... compare by report key first, then only bother with the student number if the report keys are the same, etc.
I suggest to use Java 8 Lambda approach:
List<Report> reportList = new ArrayList<Report>();
reportList.sort(Comparator.comparing(Report::getRecord1).thenComparing(Report::getRecord2));
Sorting with multiple fields in 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 + "]";
}
}
If the StudentNumber is numeric it will not be sorted numeric but alphanumeric.
Do not expect
"2" < "11"
it will be:
"11" < "2"
Use Comparator interface with methods introduced in JDK1.8: comparing and thenComparing, or more concrete methods: comparingXXX and thenComparingXXX.
For example, if we wanna sort a list of persons by their id firstly, then age, then name:
Comparator<Person> comparator = Comparator.comparingLong(Person::getId)
.thenComparingInt(Person::getAge)
.thenComparing(Person::getName);
personList.sort(comparator);
If you want to sort based on ReportKey first then Student Number then School, you need to compare each String instead of concatenating them. Your method might work if you pad the strings with spaces so that each ReportKey is the same length and so on, but it is not really worth the effort. Instead just change the compare method to compare the ReportKeys, if compareTo returns 0 then try StudentNumber, then School.
I had the same issue and I needed an algorithm using a config file. In This way you can use multiple fields define by a configuration file (simulate just by a List<String) config)
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);
}
Comparator objects are really simple.
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());
}
}
Conclusion : this method isn't has efficient but you can create generic sort using a file configuration in this way.
Here is a full example comparing 2 fields in an object, one String and one int, also using Collator to sort.
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;
}
}
}
Output:
costrels
1039737
costs
310831
costs
310832
Costs
1570019
A lot of answers above have fields compared in single comparator method which is not actually working. There are some answers though with different comparators implemented for each field, I am posting this because this example would be much more clearer and simple to understand I am believing.
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
The sorted students list in descending is[Student [bornYear=2018, bornMonth=null, bornDay=null], Student [bornYear=2018, bornMonth=12, bornDay=null], Student [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]]
im my case List of Lists (in the approximation examle):
List<T>.steam
.map(Class1.StaticInnerClass1::Field1)
.flatMap(x -> x.getField11ListStaticInnerClass2OfField1.stream())
.max(Comparator.comparing(Class1.StaticInnerClass2::Field21,Collections.reverseOrder())
.thenCompare(Class1.StaticInnerClass2::Field22));
For my case, I had 3 fields (For example - int index, bool isArchive ,bool isClassPrivate)
and I summed their comparison result like this-
Collections.sort(getData(), (o1, o2) ->
Integer.compare(o1.getIndex(getContext()), o2.getIndex(getContext()))
+ Boolean.compare(o1.isArchive(), o2.isArchive())
+ Boolean.compare(o1.isClassPrivate(), o2.isClassPrivate()
));
Related
This question already has answers here:
Sort a Map<Key, Value> by values
(64 answers)
Closed 4 years ago.
I am actually able to sort my Map on the basis of both Key and value alone, I even tried to sort them like below:
I sorted the students on the basis of country , and if two students happen to have same states then sort by StudentID only among the matched country.
What i have tried so far:
final Map<Integer, String> studentMaster = new HashMap<>() {{
put(146, "Sweden");
put(148, "Sweden");
put(110, "Orebro");
put(6, "Malmo");
put(14, "Orebro");
put(26, "Malmo");
}
};
studentMaster.entrySet().stream()
.sorted((i1,i2)->i1.getValue().compareTo(i2.getValue()))
.sorted((j1,j2)->j1.getKey().compareTo(j2.getKey()))
.forEach(System.out::println);
The result that I am getting**( actual output )**
14=Orebro
26=Malmo
110=Orebro
146=Sweden
148=Sweden
Expected Output:
26=Malmo
14=Orebro
110=Orebro
146=Sweden
148=Sweden
Note: Your expected and actual outputs don't match up with the keys that you added to your Map.
The reason that your code doesn't work is because you're calling Stream#sorted twice with two separate Comparators, so the first call to Stream#sorted is useless in your case (as it's overridden by the second call).
I was able to achieve your expected output by passing a custom Comparator to Stream#sorted:
Map.Entry.<Integer, String>comparingByValue()
.thenComparing(Map.Entry.comparingByKey())
Output:
6=Malmo
26=Malmo
14=Orebro
110=Orebro
146=Sweden
148=Sweden
Sometime back i gave answer to How to sort the name along with age in java , Many similarities to your question apart from data-structure used for storage.
To traverse in each key and sort it and then again in value and then sort it is quite tedious and can get you hell a lot confused. just remember how you used to traverse in Map when you did not used to use Stream :
for (Map.Entry<String,String> entry : somemap.entrySet()){..Some Statements..};
studentMaster.entrySet().stream()
.sorted(Comparator.comparing((Map.Entry<Integer, String> m) -> m.getValue())
.thenComparing(Map.Entry::getKey)).forEach(System.out::println);
Output
6=Malmo
26=Malmo
14=Orebro
110=Orebro
146=Sweden
148=Sweden
The Comparator should look like this:
Comparator<Entry<Integer, String>> comparator = (o1, o2) -> {
int i = o1.getValue().compareTo(o2.getValue());
if (i == 0) {
return o1.getKey().compareTo(o2.getKey());
} else {
return i;
}
};
And then pass it to the Stream#sorted method: studentMaster.entrySet().stream().sorted(comparator).forEach(System.out::println);
Output:
6=Malmo
26=Malmo
14=Orebro
110=Orebro
146=Sweden
148=Sweden
2 ways:
Use TreeSet with Comparable pojo.
Use TreeSet with customized Comparator.
Code
Tmp.java
(Use TreeSet with Comparable pojo.)
import java.util.*;
public class Tmp {
static class StudentMaster implements Comparable<StudentMaster> {
private Integer id;
private String master;
public StudentMaster(Integer id, String master) {
this.id = id;
this.master = master;
}
#Override
public int compareTo(StudentMaster other) {
int masterFlag = master.compareTo(other.master);
return (masterFlag == 0) ? id.compareTo(other.id) : masterFlag;
}
#Override
public boolean equals(Object o) {
StudentMaster osm = (StudentMaster) o;
return id == osm.id && master.equals(osm.master);
}
#Override
public int hashCode() {
return Objects.hash(id, master);
}
public String toString() {
StringBuilder sb = new StringBuilder();
Formatter fm = new Formatter(sb);
fm.format("id = %d, master = %s\n", id, master);
fm.close();
return sb.toString();
}
}
public static void test() {
final Set<StudentMaster> smSet = new TreeSet<>();
smSet.add(new StudentMaster(146, "Sweden"));
smSet.add(new StudentMaster(148, "Sweden"));
smSet.add(new StudentMaster(110, "Orebro"));
smSet.add(new StudentMaster(6, "Malmo"));
smSet.add(new StudentMaster(14, "Orebro"));
smSet.add(new StudentMaster(26, "Malmo"));
for (StudentMaster sm : smSet) {
System.out.print(sm);
}
}
public static void main(String[] args) {
test();
}
}
TmpComparator.java
(Use TreeSet with customized Comparator.)
import java.util.*;
public class TmpComparator {
static Comparator<StudentMaster> smc = new Comparator() {
#Override
public int compare(Object o1, Object o2) {
StudentMaster sm1 = (StudentMaster) o1, sm2 = (StudentMaster) o2;
int masterFlag = sm1.master.compareTo(sm2.master);
return (masterFlag == 0) ? sm1.id.compareTo(sm2.id) : masterFlag;
}
};
static class StudentMaster {
private Integer id;
private String master;
public StudentMaster(Integer id, String master) {
this.id = id;
this.master = master;
}
#Override
public boolean equals(Object o) {
StudentMaster osm = (StudentMaster) o;
return id == osm.id && master.equals(osm.master);
}
#Override
public int hashCode() {
return Objects.hash(id, master);
}
public String toString() {
StringBuilder sb = new StringBuilder();
Formatter fm = new Formatter(sb);
fm.format("id = %d, master = %s\n", id, master);
fm.close();
return sb.toString();
}
}
public static void test() {
final Set<StudentMaster> smSet = new TreeSet<>(smc);
smSet.add(new StudentMaster(146, "Sweden"));
smSet.add(new StudentMaster(148, "Sweden"));
smSet.add(new StudentMaster(110, "Orebro"));
smSet.add(new StudentMaster(6, "Malmo"));
smSet.add(new StudentMaster(14, "Orebro"));
smSet.add(new StudentMaster(26, "Malmo"));
for (StudentMaster sm : smSet) {
System.out.print(sm);
}
}
public static void main(String[] args) {
test();
}
}
Just run the main() method.
Output of both are the same:
id = 6, master = Malmo
id = 26, master = Malmo
id = 14, master = Orebro
id = 110, master = Orebro
id = 146, master = Sweden
id = 148, master = Sweden
Tips
In production code, the equals() need to be improved, this is a simplified version for test only.
Lets say I have a Product class in Java and 2 Comparators:
1st is price Comparator for asc order.
2nd is price Comparator for desc order.
It can be that if I changed the 1st to be product name Comparator, so, the 2nd will change automatic to name Comparator as well?
Thanks alot!
Exmaple:
class ProductComparatorByPriceDesc implements Comparator<Customer> {
#Override
public int compare(Product o1, Product o2) {
return o1.getPrice() - o2.getPrice();
}
}
Class ProductComparatorByPriceAsc implements Comparator<Customer> {
#Override
public int compare(Customer o1, Customer o2) {
return o2.getPrice() - o1.getPrice();
}
}
So if i changed the 1st comparator to sort by name, not price, the 2nd will changed as well, but not the opposite!
One way would be:
import java.util.*;
class SomeClass {
public int price;
public String name;
public SomeClass(String name, int price) {
this.name = name;
this.price = price;
}
}
class PriceOrNameComparator implements Comparator<SomeClass> {
boolean compareByPrice;
public PriceOrNameComparator byPrice() {
this.compareByPrice = true;
return this;
}
public PriceOrNameComparator byName() {
this.compareByPrice = false;
return this;
}
public int compare(SomeClass a, SomeClass b) {
if (compareByPrice) {
return a.price - b.price;
} else {
return a.name.compareTo(b.name);
}
}
public Comparator<SomeClass> reverseComparator() {
return new Comparator<SomeClass>() {
public int compare(SomeClass a, SomeClass b) {
int res = PriceOrNameComparator.this.compare(a, b);
if (res == 0) {
return 0;
} else {
return (res > 0) ? -1 : 1;
}
}
};
}
}
class Test {
public static void main(String[] args) {
SomeClass s1 = new SomeClass("a", 5);
SomeClass s2 = new SomeClass("b", 4);
PriceOrNameComparator c = new PriceOrNameComparator().byPrice();
Comparator<SomeClass> r = c.reverseComparator();
System.out.println(c.compare(s1, s2)); // 1
System.out.println(r.compare(s1, s2)); // -1
c.byName();
System.out.println(c.compare(s1, s2)); // -1
System.out.println(r.compare(s1, s2)); // 1
}
}
Basically, the outer comparator is configurable, and the inner, reverse order, comparator, being an anonymous inner class, has an implicit reference to the outer comparator and can observe changes in its state.
I would suggest only having a single comparator class for comparing by price, and a separate comparator class to compare by name (or no classes - see the end of my answer). Each class does one thing, and does it well.
Then you can reverse any comparator using the Comparator.reversed default method... and likewise you can chain them together using Comparator.thenComparing, should you wish to order by name and then price, for example:
Comparator<Product> nameThenPrice =
new NameComparator().thenComparing(new PriceComparator());
(If you're not using Java 8, it's easy enough to write a ReversingComparator which takes an existing one, and a CompoundComparator which takes two existing ones.)
You can also use Java 8's static methods in Comparator:
Comparator<Product> byName = Comparator.comparing(p -> p.getName());
Comparator<Product> byPrice = Comparator.comparing(p -> p.getPrice());
Comparator<Product> nameThenPrice = byName.thenComparing(byPrice);
That way you often don't need to implement Comparator at all manually.
So I want an arraylist of objects in java.
I have object1.number and object2.number, object3.number, etc... but those objects have other properties besides number, such as name, distance, etc...
So if it was sorting a string in a array it would just be, put a string in a temporal and let the other string take its place... but in an araryList of objects, how can I do it?
Can I just move objects to that position of the array?
Thanks.
Implement your own comparer:
Arrays.sort(yourArray, new Comparator<YourClass>() {
#Override
public int compare(YourClass o1, YourClass o2) {
//compare object properties
}
});
You need to implement the comparable interface
implements Comparable
the method that does the work is
public int compareTo(Object obj)
{
}
Please note that object is often replaced by a full on type because of generic syntax which can be used in the implements statement (shown below).
A full example is here in the tutorial docs hope this helps
A full example (take from the above link is as follows), I have added this just in case the link goes dead at some point
import java.util.*;
public class Name implements Comparable<Name> {
private final String firstName, lastName;
public Name(String firstName, String lastName) {
if (firstName == null || lastName == null)
throw new NullPointerException();
this.firstName = firstName;
this.lastName = lastName;
}
public String firstName() { return firstName; }
public String lastName() { return lastName; }
public boolean equals(Object o) {
if (o == null || !(o instanceof Name))
return false;
Name n = (Name) o;
return n.firstName.equals(firstName) && n.lastName.equals(lastName);
}
public int hashCode() {
return 31*firstName.hashCode() + lastName.hashCode();
}
public String toString() {
return firstName + " " + lastName;
}
public int compareTo(Name n) {
int lastCmp = lastName.compareTo(n.lastName);
return (lastCmp != 0 ? lastCmp : firstName.compareTo(n.firstName));
}
}
The client code from the article is:
import java.util.*;
public class NameSort {
public static void main(String[] args) {
Name nameArray[] = {
new Name("John", "Smith"),
new Name("Karl", "Ng"),
new Name("Jeff", "Smith"),
new Name("Tom", "Rich")
};
List<Name> names = Arrays.asList(nameArray);
Collections.sort(names);
System.out.println(names);
}
}
You need to use comparator for this purpose.
Based on your question, I take it that you are supposed to be implementing the sorting algorithm yourself. If that is the case, you can manipulate the position of elements within an ArrayList, it just works a bit differently than a regular array. Have a look at the add(int index, E element). The index parameter lets you decide where in the ArrayList to add the element.
To manipulate the entries of an ArrayList like that of a traditional array, use ArrayList.set(int, E).
http://docs.oracle.com/javase/1.5.0/docs/api/java/util/ArrayList.html#set%28int,%20E%29
Use to Collections.sort() to sort an ArrayList in Java 8:
Collections.sort(array, new Comparator<Class>() {
#Override
public int compare(Class o1, Class o2) {
//compare object properties
}
});
This question already has answers here:
Using Comparable for multiple dynamic fields of VO in java
(7 answers)
Closed 8 years ago.
I have a List of Java objects that I want to sort according to more than one field.
public class graduationCeremony {
String campus;
String faculty;
String building;
}
Is it possible to use a Comparator or the Comparable interface to sort the list according to multiple fields? All the examples I have seen sort according to only one field. In other words, one can sort by 'campus' OR 'faculty' OR 'building'. I want to sort by 'campus', then 'faculty', then 'building' (as it exists in SQL: ORDER BY campus, faculty, building)
I think this question has been asked before, but I don't understand the accepted answer. Can someone expand or illustrate this answer?
Your Comparator would look like this:
public class GraduationCeremonyComparator implements Comparator<GraduationCeremony> {
public int compare(GraduationCeremony o1, GraduationCeremony o2) {
int value1 = o1.campus.compareTo(o2.campus);
if (value1 == 0) {
int value2 = o1.faculty.compareTo(o2.faculty);
if (value2 == 0) {
return o1.building.compareTo(o2.building);
} else {
return value2;
}
}
return value1;
}
}
Basically it continues comparing each successive attribute of your class whenever the compared attributes so far are equal (== 0).
Yes, you absolutely can do this. For example:
public class PersonComparator implements Comparator<Person>
{
public int compare(Person p1, Person p2)
{
// Assume no nulls, and simple ordinal comparisons
// First by campus - stop if this gives a result.
int campusResult = p1.getCampus().compareTo(p2.getCampus());
if (campusResult != 0)
{
return campusResult;
}
// Next by faculty
int facultyResult = p1.getFaculty().compareTo(p2.getFaculty());
if (facultyResult != 0)
{
return facultyResult;
}
// Finally by building
return p1.getBuilding().compareTo(p2.getBuilding());
}
}
Basically you're saying, "If I can tell which one comes first just by looking at the campus (before they come from different campuses, and the campus is the most important field) then I'll just return that result. Otherwise, I'll continue on to compare faculties. Again, stop if that's enough to tell them apart. Otherwise, (if the campus and faculty are the same for both people) just use the result of comparing them by building."
If you know in advance which fields to use to make the comparison, then other people gave right answers.
What you may be interested in is to sort your collection in case you don't know at compile-time which criteria to apply.
Imagine you have a program dealing with cities:
protected Set<City> cities;
(...)
Field temperatureField = City.class.getDeclaredField("temperature");
Field numberOfInhabitantsField = City.class.getDeclaredField("numberOfInhabitants");
Field rainfallField = City.class.getDeclaredField("rainfall");
program.showCitiesSortBy(temperatureField, numberOfInhabitantsField, rainfallField);
(...)
public void showCitiesSortBy(Field... fields) {
List<City> sortedCities = new ArrayList<City>(cities);
Collections.sort(sortedCities, new City.CityMultiComparator(fields));
for (City city : sortedCities) {
System.out.println(city.toString());
}
}
where you can replace hard-coded field names by field names deduced from a user request in your program.
In this example, City.CityMultiComparator<City> is a static nested class of class City implementing Comparator:
public static class CityMultiComparator implements Comparator<City> {
protected List<Field> fields;
public CityMultiComparator(Field... orderedFields) {
fields = new ArrayList<Field>();
for (Field field : orderedFields) {
fields.add(field);
}
}
#Override
public int compare(City cityA, City cityB) {
Integer score = 0;
Boolean continueComparison = true;
Iterator itFields = fields.iterator();
while (itFields.hasNext() && continueComparison) {
Field field = itFields.next();
Integer currentScore = 0;
if (field.getName().equalsIgnoreCase("temperature")) {
currentScore = cityA.getTemperature().compareTo(cityB.getTemperature());
} else if (field.getName().equalsIgnoreCase("numberOfInhabitants")) {
currentScore = cityA.getNumberOfInhabitants().compareTo(cityB.getNumberOfInhabitants());
} else if (field.getName().equalsIgnoreCase("rainfall")) {
currentScore = cityA.getRainfall().compareTo(cityB.getRainfall());
}
if (currentScore != 0) {
continueComparison = false;
}
score = currentScore;
}
return score;
}
}
You may want to add an extra layer of precision, to specify, for each field, whether sorting should be ascendant or descendant. I guess a solution is to replace Field objects by objects of a class you could call SortedField, containing a Field object, plus another field meaning ascendant or descendant.
Hope this Helps:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
class Person implements Comparable {
String firstName, lastName;
public Person(String f, String l) {
this.firstName = f;
this.lastName = l;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String toString() {
return "[ firstname=" + firstName + ",lastname=" + lastName + "]";
}
public int compareTo(Object obj) {
Person emp = (Person) obj;
int deptComp = firstName.compareTo(emp.getFirstName());
return ((deptComp == 0) ? lastName.compareTo(emp.getLastName()) : deptComp);
}
public boolean equals(Object obj) {
if (!(obj instanceof Person)) {
return false;
}
Person emp = (Person) obj;
return firstName.equals(emp.getFirstName()) && lastName.equals(emp.getLastName());
}
}
class PersonComparator implements Comparator<Person> {
public int compare(Person emp1, Person emp2) {
int nameComp = emp1.getLastName().compareTo(emp2.getLastName());
return ((nameComp == 0) ? emp1.getFirstName().compareTo(emp2.getFirstName()) : nameComp);
}
}
public class Main {
public static void main(String args[]) {
ArrayList<Person> names = new ArrayList<Person>();
names.add(new Person("E", "T"));
names.add(new Person("A", "G"));
names.add(new Person("B", "H"));
names.add(new Person("C", "J"));
Iterator iter1 = names.iterator();
while (iter1.hasNext()) {
System.out.println(iter1.next());
}
Collections.sort(names, new PersonComparator());
Iterator iter2 = names.iterator();
while (iter2.hasNext()) {
System.out.println(iter2.next());
}
}
}
You just need to have your class inherit from Comparable.
then implement the compareTo method the way you like.
You have to write your own compareTo() method that has the Java code needed to perform the comparison.
If we wanted for example to compare two public fields, campus, then faculty, we might do something like:
int compareTo(GraduationCeremony gc)
{
int c = this.campus.compareTo(gc.campus);
if( c != 0 )
{
//sort by campus if we can
return c;
}
else
{
//campus equal, so sort by faculty
return this.faculty.compareTo(gc.faculty);
}
}
This is simplified but hopefully gives you an idea. Consult the Comparable and Comparator docs for more info.
I have a list of objects. Each object contains a String and a Date (amongst others).
I want to first sort by the String and then by the Date.
How could this be done in the cleanest way possible?
Thanks!
Krt_Malta
With Java 8, this is really easy. Given
class MyClass {
String getString() { ... }
Date getDate() { ... }
}
You can easily sort a list as follows:
List<MyClass> list = ...
list.sort(Comparator.comparing(MyClass::getString).thenComparing(MyClass::getDate));
Given an object class that looks like this:
public class MyObject {
public String getString() { ... }
public Date getDate() { ... }
...
}
Write a custom comparator class like so:
public class ObjectComparator implements Comparator{
public int compare(Object obj1, Object obj2) {
MyObject myObj1 = (MyObject)obj1;
MyObject myObj2 = (MyObject)obj2;
stringResult = myObj1.getString().compareTo(myObj2.getString());
if (stringResult == 0) {
// Strings are equal, sort by date
return myObj1.getDate().compareTo(myObj2.getDate());
}
else {
return stringResult;
}
}
}
Then sort as follows:
Collections.sort(objectList, new ObjectComparator());
Implement a custom Comparator, using a compare(a,b) method like the following:
Plain Java:
public int compare(YourObject o1, YourObject o2) {
int result = o1.getProperty1().compareTo(o2.getProperty1()));
if(result==0) result = o1.getProperty2().compareTo(o2.getProperty2());
return result;
}
With Guava (using ComparisonChain):
public int compare(YourObject o1, YourObject o2) {
return ComparisonChain.start()
.compare(o1.getProperty1(), o2.getProperty1())
.compare(o1.getProperty2(), o2.getProperty2())
.result();
}
With Commons / Lang (using CompareToBuilder):
public int compare(YourObject o1, YourObject o2) {
return new CompareToBuilder()
.append(o1.getProperty1(), o2.getProperty1())
.append(o1.getProperty2(), o2.getProperty2())
.toComparison();
}
(All three versions are equivalent, but the plain Java version is the most verbose and hence most error-prone one. All three solutions assume that both o1.getProperty1() and o1.getProperty2() implement Comparable).
(Taken from this previous answer of mine)
now do Collections.sort(yourList, yourComparator)
The Comparators answer is correct but incomplete.
StringAndDateComparator implements Comparator<MyObject> {
public int compare(MyObject first, MyObject second) {
int result = first.getString().compareTo(second.getString());
if (result != 0) {
return result;
}
else {
return first.getDate().compareTo(second.getDate());
}
}
GlazedLists has a nice utility method to chain together different comparators to save you from writing this boilerplate. See the chainComparators method for more information.
A simple array can be sorted using 2 lambda experessions as:
Arrays.sort(arr, (i, j) -> (i[0] == j[0] ? j[1] - i[1] : i[0] - j[0]));
means two subarrays i & j within a 2D array arr will be sorted in ascending order based on 0th index of arrays. And if 0th index is equal, then based on 1st index.
Try this method:
Collections.sort(list, comparator)
You should of course have a custom Comparator implementation for your object, as stated by Manoj.
Using java 8 and parallel sorting technique, we can also achieve this as follows:
List<Employee> empss = getEmployees();
Comparator<Employee> combinedComparator = Comparator.comparing(Employee::getFName)
.thenComparing(Employee::getLName);
Employee[] emppArr = employees.toArray(new Employee[empss.size()]);
//Parallel sorting
Arrays.parallelSort(emppArr, combinedComparator);
package core.java.collection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class GroupByComparator {
public static void main(String[] args) {
List <StudentTest> studList = new ArrayList<StudentTest>();
StudentTest s1 = new StudentTest(12 ,"Devendra" ,410);
StudentTest s2 = new StudentTest(11 ,"Devendra" ,430);
StudentTest s3 = new StudentTest(13 ,"Devendra" ,402);
StudentTest s4 = new StudentTest(10 ,"Devendra" ,432);
//Assuming that id may be same
StudentTest s5 = new StudentTest(14 ,"Singraul" ,432);
StudentTest s6 = new StudentTest(14 ,"Abhishek" ,432);
StudentTest s7 = new StudentTest(14 ,"Roshan" ,432);
StudentTest s8 = new StudentTest(14 ,"Bikas" ,432);
StudentTest s9 = new StudentTest(15 ,"Devlal" ,450);
StudentTest s10 = new StudentTest(15 ,"Devlal" ,359);
StudentTest s11= new StudentTest(15 ,"Devlal" ,430);
StudentTest s12 = new StudentTest(15 ,"Devlal" ,420);
studList.add(s1); studList.add(s2); studList.add(s3); studList.add(s4); studList.add(s5);
studList.add(s6); studList.add(s7); studList.add(s8); studList.add(s9); studList.add(s10);
studList.add(s11); studList.add(s12);
Collections.sort(studList, new StudentComparator());
// group by sorting
System.out.println(studList);
}
}
// Group by Comparator for ascending order
class StudentComparator implements Comparator<StudentTest>{
#Override
public int compare(StudentTest newObj, StudentTest oldObj) {
int result =0;
// sort by name first
result= newObj.getStudName().compareTo(oldObj.getStudName());
// sort by student id second
if(result == 0) {
result= newObj.getStudId()-oldObj.getStudId() ; // negative means before
}
// sort by marks third
if(result == 0) {
result= Float.compare(newObj.getMarks(), oldObj.getMarks()); ; // negative means before
}
return result;
}
}
class StudentTest{
private int studId ;
private String studName ;
private float marks ;
public StudentTest(int studId, String studName, float marks) {
super();
this.studId = studId;
this.studName = studName;
this.marks = marks;
}
public int getStudId() {
return studId;
}
public void setStudId(int studId) {
this.studId = studId;
}
public String getStudName() {
return studName;
}
public void setStudName(String studName) {
this.studName = studName;
}
public float getMarks() {
return marks;
}
public void setMarks(float marks) {
this.marks = marks;
}
#Override
public String toString() {
return "StudentTest [studId=" + studId + ", studName=" + studName + ", marks=" + marks + "]";
}
}
Try this way....
studentlist.stream().sorted(Comparator.comparing(Student::getAge).thenComparing(Student::getName)).forEach(System.out::println);