How to create 2 depends Comparators in java? - java

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.

Related

Getting only first element of ArrayList that matches condition

I've been trying to find possible answers, but found none.
I've got an ArrayList full of custom objects. One of their fields is a boolean.
I want to put this object first, keeping the rest of elements
For instance, if I've got this list and obj5 is the one with this boolean set to true:
obj3, obj2, obj5, obj7, obj9
I'd like to get this:
obj5, obj3, obj2, obj7, obj9
EDIT: CAN'T USE LAMBDAS, JAVA 6
EDIT 2: PLEASE NOTE THAT THE REST OF THE LIST MUST KEEP THE OLD ORDER
EDIT 3: In short words, I need this program to output [B, A, C, D, E]:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Trip {
#Override
public String toString() {
return name;
}
private String name;
private boolean freeCancellation;
public Trip(String name, boolean freeCancellation) {
this.name = name;
this.freeCancellation = freeCancellation;
}
static Comparator<Trip> myOrder = new Comparator<Trip>() {
public int compare(Trip a, Trip b) {
if (a.freeCancellation == b.freeCancellation) return 0;
return a.freeCancellation ? -1 : 1;
}
};
public static void main(String [] args){
Trip t1 = new Trip("A", false);
Trip t2 = new Trip("B", true);
Trip t3 = new Trip("C", false);
Trip t4 = new Trip("D", true);
Trip t5 = new Trip("E", false);
List<Trip> tripList = new ArrayList<>();
tripList.add(t1);
tripList.add(t2);
tripList.add(t3);
tripList.add(t4);
tripList.add(t5);
System.out.println(Arrays.toString(tripList.toArray()));
Collections.sort(tripList, myOrder);
//result should be [B, A, C, D, E]
System.out.println(Arrays.toString(tripList.toArray()));
}
}
Write a Comparator.
Comparator<MyType> myOrder = new Comparator<MyType>() {
public int compare(MyType a, MyType b) {
return (b.booleanField() ? 1 : 0) - (a.booleanField() ? 1 : 0);
}
}
Sort using this comparator.
Collections.sort(myList, myOrder);
See Collections.sort
Edit
So it seems that what you're actually asking for is to move just one matching element to the front of your list. That ought to be pretty easy.
Find the index of the element you want to move:
int foundIndex = -1;
for (int i = 0; i < tripList.size(); ++i) {
if (tripList.get(i).freeCancellation) {
foundIndex = i;
break;
}
}
If you find such an element, and it is not already at the start, move it to the start:
if (foundIndex > 0) {
tripList.add(0, tripList.remove(foundIndex));
}
List<Object> objList = findObj(name);Collections.sort(objList, new Comparator<Object>() {
#Override
public int compare(Object a1, Object a2) {
return (a1.getBooleanField()== a2.getBooleanField())?0:(a1.getBooleanField()?1:-1);
}});
This might help you to resolve this. You modify the results by changing the compare logic
Here is an example of how to achieve this:
class Element {
public boolean shouldBeFirst();
}
List<Element> elements;
elements.sort(Comparator.comparing(Element::shouldBeFirst));
This works because the natural ordering of booleans is true first.
If you can't use Java 8 then the equivalent would be something like:
Collections.sort(elements, new Comparator() {
int compareTo(Element el1, Element el2) {
return (el1.shouldBeFirst() ? 1 : 0) - (el2.shouldBeFirst() ? 1 : 0);
}
}
import java.util.*;
public class Test {
public static void main(String[] args) {
List<A> list = new ArrayList<A>();
list.add(new A(true));
list.add(new A(false));
list.add(new A(true));
list.add(new A(false));
Collections.sort(list);
System.out.println(list);
}
}
class A implements Comparable<A> {
private boolean b;
public A(boolean b) {
this.b = b;
}
public boolean isB() {
return b;
}
public void setB(boolean b) {
this.b = b;
}
#Override
public int compareTo(A a) {
return a.isB() ? 1 : -1;
}
#Override
public String toString() {
return "A [b=" + b + "]";
}
}
Maybe this is what you are looking for.
This is solution if you want to give natural ordering to object, then implement Comparable and use Collections.sort - https://docs.oracle.com/javase/6/docs/api/java/util/Collections.html#sort(java.util.List).
If you have various members inside class, then maybe go with Comparator implementation, that way you can achieve many ways of sorting your objects based on different members.
If I understood what are you asking ,you need to create a new class called "Comparators".
in this class you need to define your methods and they need to be static final ...
then you can use it by calling to Collections.sort(-your array-, Comparator method name);

Java Comparable, mutliple/different implenetations of compareTo() method

I have this class:
public class Sample implements Comparable<Sample> {
public String a;
public String b;
public String c;
public int compareTo (Sample sampleToCompare) {
int compResult = this.a.compareTo(sampleToCompare.a);
return (compResult != 0 ? compResult :
this.b.compareTo(sampleToCompare.b));
}
}
I want compareTo() to behave or sort using different class properties depending if a flag is set.
So, if flag == 1 I'd like compareTo() to using property c, otherwise is flag == 0, whatever is currently in the method.
In other words, sort the same class in different ways.
I am not sure how to achieve this. Please help.
Also, please let me know if more information is needed from my side.
If you want to implement different kind of sorting, you should take a look at java.util.Comparator interface.
public class SampleComparatorA implement Comparator<Sample> {
public int compare(Sample a, Sample b) {
// Your sorting
}
}
And use java.util.Collections.sort() method with the Comparator as the secound parameter instead.
Collections.sort(aSampleList, new SampleComparatorA());
How about:
public int compareTo(Sample sampleToCompare) {
if (flag == 1) {
return this.c.compareTo(sampleToCompare.c);
}
if (flag == 0) {
// current stuff
}
...
}
That's not a very object-oriented way to do it, though. Probably you should have two different comparators and a way to select them based on your "flag" value. Something like:
class Sample {
private String a;
private String b;
private String c;
}
class ASampleComparator implements Comparator<Sample> {
public int compare(Sample o1, Sample o2) {
return o1.a.compareTo(o2.a);
}
}
class BSampleComparator implements Comparator<Sample> {
public int compare(Sample o1, Sample o2) {
return o1.b.compareTo(o2.b);
}
}
class CSampleComparator implements Comparator<Sample> {
public int compare(Sample o1, Sample o2) {
return o1.c.compareTo(o2.c);
}
}
public Comparator<Sample> pickComparator(int flag) {
switch (flag) {
case 0:
return new ASampleComparator();
case 1:
return new BSampleComparator();
case 2:
return new CSampleComparator();
default:
throw new IllegalArgumentException("Bad flag value: " + flag);
}
}
You should make your flag static so the comparison will be consistent (as described in Effective Java, item 12), otherwise, you might get that a.compareTo(b) returns that a > b, but b.compareTo(a) returns that b > a. So the simplest implementation I can think about is:
public class Sample implements Comparable<Sample> {
public String a;
public String b;
public String c;
public static boolean my_flag = false;
public int compareTo (Sample sampleToCompare) {
if (flag) {
return this.c.compareTo(sampleToCompare.c);
}
int compResult = this.a.compareTo(sampleToCompare.a);
return (compResult != 0 ? compResult :
this.b.compareTo(sampleToCompare.b));
}
}

Sorting a list in Java using 2 criteria

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);

Collections.sort with multiple fields

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()
));

Strange behaviour: Java Comparator randomizes list entries

I'm curious. What could be the reason that a Comparator shuffles entries on each
application start?
final static class ContactsListComparator implements Comparator
{
public int compare(Object o1, Object o2)
{
if((o1.toString().compareTo(o2.toString()))<0)
{
return -1;
}
if((o1.toString().compareTo(o2.toString()))>0)
{
return 1;
}
else
{
return 0;
}
}
}
First App Start:
Second App Start
As mentioned in one the answer
The Comparator actually compares an custom object Contact
public class Contact
{
// Members
private String _contactFirstName;
private String _contactLastName;
private long _contactLastModified;
// Constructor
public Contact()
{
set_contactLastModified();
}
public Contact(String contactFirstName)
{
_contactFirstName = contactFirstName;
set_contactLastModified();
}
// Accessable Getters
public String get_contactFirstName()
{
return _contactFirstName;
}
public String get_contactLastName()
{
return _contactLastName;
}
public long get_contactLastModified()
{
return _contactLastModified;
}
public void set_contactLastModified()
{
_contactLastModified = System.currentTimeMillis();
}
}
your toString method probably isn't overridden for your objects representing the contacts. It will return a hash string for those objects, which varies every time your app is run.
You can fix this either of two ways:
Override the toString() method in your Contact object to return the contact's name (1), or
Change the Comparator to Comparator<Contact> so it gets Contact objects as parameters (2)
for (1), add this to your Contact class:
#Override public String toString() {
return get_contactFirstName();
}
for (2) you would end up with this Comparator implementation:
final static class ContactsListComparator implements Comparator<Contact> {
public int compare(Contact o1, Contact o2) {
return contact1.get_contactFirstName().compareTo(contact2.get_contactFirstName());
}
}
you don't even need to check for the <0 or >0, but you can just return whatever the String comparison gives.
I would use:
final static class ContactsListComparator implements Comparator<Contact>
{
public int compare(Contact c1,Contact c2)
{
int i=c1.get_contactLastName().compareTo(c2.get_contactLastName());
if(i!=0) return i;
return c1.get_contactFirstName().compareTo(c2.get_contactFirstName());;
}
}
Your first example is basically the same as
final static class ContactsListComparator implements Comparator {
public int compare(Object o1, Object o2) {
return o1.toString().compareTo(o2.toString());
}
}
This would work if you override toString() like
public String toString() {
return _contactFirstName + ' ' + _contactLastName;
}
However, a comparator which compares the intended fields is better as has been suggested.

Categories