Assume you have some objects which have several fields they can be compared by:
public class Person {
private String firstName;
private String lastName;
private String age;
/* Constructors */
/* Methods */
}
So in this example, when you ask if:
a.compareTo(b) > 0
you might be asking if a's last name comes before b's, or if a is older than b, etc...
What is the cleanest way to enable multiple comparison between these kinds of objects without adding unnecessary clutter or overhead?
java.lang.Comparable interface allows comparison by one field only
Adding numerous compare methods (i.e. compareByFirstName(), compareByAge(), etc...) is cluttered in my opinion.
So what is the best way to go about this?
With Java 8:
Comparator.comparing((Person p)->p.firstName)
.thenComparing(p->p.lastName)
.thenComparingInt(p->p.age);
If you have accessor methods:
Comparator.comparing(Person::getFirstName)
.thenComparing(Person::getLastName)
.thenComparingInt(Person::getAge);
If a class implements Comparable then such comparator may be used in compareTo method:
#Override
public int compareTo(Person o){
return Comparator.comparing(Person::getFirstName)
.thenComparing(Person::getLastName)
.thenComparingInt(Person::getAge)
.compare(this, o);
}
You should implement Comparable <Person>. Assuming all fields will not be null (for simplicity sake), that age is an int, and compare ranking is first, last, age, the compareTo method is quite simple:
public int compareTo(Person other) {
int i = firstName.compareTo(other.firstName);
if (i != 0) return i;
i = lastName.compareTo(other.lastName);
if (i != 0) return i;
return Integer.compare(age, other.age);
}
(from Ways to sort lists of objects in Java based on multiple fields)
Working code in this gist
Using Java 8 lambda's (added April 10, 2019)
Java 8 solves this nicely by lambda's (though Guava and Apache Commons might still offer more flexibility):
Collections.sort(reportList, Comparator.comparing(Report::getReportKey)
.thenComparing(Report::getStudentNumber)
.thenComparing(Report::getSchool));
Thanks to #gaoagong's answer below.
Messy and convoluted: Sorting by hand
Collections.sort(pizzas, new Comparator<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 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).
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.
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));
You can implement a Comparator which compares two Person objects, and you can examine as many of the fields as you like. You can put in a variable in your comparator that tells it which field to compare to, although it would probably be simpler to just write multiple comparators.
#Patrick To sort more than one field consecutively try ComparatorChain
A ComparatorChain is a Comparator that wraps one or more Comparators in sequence. The ComparatorChain calls each Comparator in sequence until either 1) any single Comparator returns a non-zero result (and that result is then returned), or 2) the ComparatorChain is exhausted (and zero is returned). This type of sorting is very similar to multi-column sorting in SQL, and this class allows Java classes to emulate that kind of behaviour when sorting a List.
To further facilitate SQL-like sorting, the order of any single Comparator in the list can >be reversed.
Calling a method that adds new Comparators or changes the ascend/descend sort after compare(Object, Object) has been called will result in an UnsupportedOperationException. However, take care to not alter the underlying List of Comparators or the BitSet that defines the sort order.
Instances of ComparatorChain are not synchronized. The class is not thread-safe at construction time, but it is thread-safe to perform multiple comparisons after all the setup operations are complete.
Another option you can always consider is Apache Commons. It provides a lot of options.
import org.apache.commons.lang3.builder.CompareToBuilder;
Ex:
public int compare(Person a, Person b){
return new CompareToBuilder()
.append(a.getName(), b.getName())
.append(a.getAddress(), b.getAddress())
.toComparison();
}
import com.google.common.collect.ComparisonChain;
/**
* #author radler
* Class Description ...
*/
public class Attribute implements Comparable<Attribute> {
private String type;
private String value;
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public String getValue() { return value; }
public void setValue(String value) { this.value = value; }
#Override
public String toString() {
return "Attribute [type=" + type + ", value=" + value + "]";
}
#Override
public int compareTo(Attribute that) {
return ComparisonChain.start()
.compare(this.type, that.type)
.compare(this.value, that.value)
.result();
}
}
You can also have a look at Enum that implements Comparator.
http://tobega.blogspot.com/2008/05/beautiful-enums.html
e.g.
Collections.sort(myChildren, Child.Order.ByAge.descending());
For those able to use the Java 8 streaming API, there is a neater approach that is well documented here:
Lambdas and sorting
I was looking for the equivalent of the C# LINQ:
.ThenBy(...)
I found the mechanism in Java 8 on the Comparator:
.thenComparing(...)
So here is the snippet that demonstrates the algorithm.
Comparator<Person> comparator = Comparator.comparing(person -> person.name);
comparator = comparator.thenComparing(Comparator.comparing(person -> person.age));
Check out the link above for a neater way and an explanation about how Java's type inference makes it a bit more clunky to define compared to LINQ.
Here is the full unit test for reference:
#Test
public void testChainedSorting()
{
// Create the collection of people:
ArrayList<Person> people = new ArrayList<>();
people.add(new Person("Dan", 4));
people.add(new Person("Andi", 2));
people.add(new Person("Bob", 42));
people.add(new Person("Debby", 3));
people.add(new Person("Bob", 72));
people.add(new Person("Barry", 20));
people.add(new Person("Cathy", 40));
people.add(new Person("Bob", 40));
people.add(new Person("Barry", 50));
// Define chained comparators:
// Great article explaining this and how to make it even neater:
// http://blog.jooq.org/2014/01/31/java-8-friday-goodies-lambdas-and-sorting/
Comparator<Person> comparator = Comparator.comparing(person -> person.name);
comparator = comparator.thenComparing(Comparator.comparing(person -> person.age));
// Sort the stream:
Stream<Person> personStream = people.stream().sorted(comparator);
// Make sure that the output is as expected:
List<Person> sortedPeople = personStream.collect(Collectors.toList());
Assert.assertEquals("Andi", sortedPeople.get(0).name); Assert.assertEquals(2, sortedPeople.get(0).age);
Assert.assertEquals("Barry", sortedPeople.get(1).name); Assert.assertEquals(20, sortedPeople.get(1).age);
Assert.assertEquals("Barry", sortedPeople.get(2).name); Assert.assertEquals(50, sortedPeople.get(2).age);
Assert.assertEquals("Bob", sortedPeople.get(3).name); Assert.assertEquals(40, sortedPeople.get(3).age);
Assert.assertEquals("Bob", sortedPeople.get(4).name); Assert.assertEquals(42, sortedPeople.get(4).age);
Assert.assertEquals("Bob", sortedPeople.get(5).name); Assert.assertEquals(72, sortedPeople.get(5).age);
Assert.assertEquals("Cathy", sortedPeople.get(6).name); Assert.assertEquals(40, sortedPeople.get(6).age);
Assert.assertEquals("Dan", sortedPeople.get(7).name); Assert.assertEquals(4, sortedPeople.get(7).age);
Assert.assertEquals("Debby", sortedPeople.get(8).name); Assert.assertEquals(3, sortedPeople.get(8).age);
// Andi : 2
// Barry : 20
// Barry : 50
// Bob : 40
// Bob : 42
// Bob : 72
// Cathy : 40
// Dan : 4
// Debby : 3
}
/**
* A person in our system.
*/
public static class Person
{
/**
* Creates a new person.
* #param name The name of the person.
* #param age The age of the person.
*/
public Person(String name, int age)
{
this.age = age;
this.name = name;
}
/**
* The name of the person.
*/
public String name;
/**
* The age of the person.
*/
public int age;
#Override
public String toString()
{
if (name == null) return super.toString();
else return String.format("%s : %d", this.name, this.age);
}
}
Writing a Comparator manually for such an use case is a terrible solution IMO. Such ad hoc approaches have many drawbacks:
No code reuse. Violates DRY.
Boilerplate.
Increased possibility of errors.
So what's the solution?
First some theory.
Let us denote the proposition "type A supports comparison" by Ord A. (From program perspective, you can think of Ord A as an object containing logic for comparing two As. Yes, just like Comparator.)
Now, if Ord A and Ord B, then their composite (A, B) should also support comparison. i.e. Ord (A, B). If Ord A, Ord B, and Ord C, then Ord (A, B, C).
We can extend this argument to arbitrary arity, and say:
Ord A, Ord B, Ord C, ..., Ord Z ⇒ Ord (A, B, C, .., Z)
Let's call this statement 1.
The comparison of the composites will work just as you described in your question: the first comparison will be tried first, then the next one, then the next, and so on.
That's the first part of our solution. Now the second part.
If you know that Ord A, and know how to transform B to A (call that transformation function f), then you can also have Ord B. How? Well, when the two B instances are to be compared, you first transform them to A using f and then apply Ord A.
Here, we are mapping the transformation B → A to Ord A → Ord B. This is known as contravariant mapping (or comap for short).
Ord A, (B → A) ⇒comap Ord B
Let's call this statement 2.
Now let's apply this to your example.
You have a data type named Person that comprises three fields of type String.
We know that Ord String. By statement 1, Ord (String, String, String).
We can easily write a function from Person to (String, String, String). (Just return the three fields.) Since we know Ord (String, String, String) and Person → (String, String, String), by statement 2, we can use comap to get Ord Person.
QED.
How do I implement all these concepts?
The good news is you don't have to. There already exists a library which implements all the ideas described in this post. (If you are curious how these are implemented, you can look under the hood.)
This is how the code will look with it:
Ord<Person> personOrd =
p3Ord(stringOrd, stringOrd, stringOrd).comap(
new F<Person, P3<String, String, String>>() {
public P3<String, String, String> f(Person x) {
return p(x.getFirstName(), x.getLastname(), x.getAge());
}
}
);
Explanation:
stringOrd is an object of type Ord<String>. This corresponds to our original "supports comparison" proposition.
p3Ord is a method that takes Ord<A>, Ord<B>, Ord<C>, and returns Ord<P3<A, B, C>>. This corresponds to statement 1. (P3 stands for product with three elements. Product is an algebraic term for composites.)
comap corresponds to well, comap.
F<A, B> represents a transformation function A → B.
p is a factory method for creating products.
The whole expression corresponds to statement 2.
Hope that helps.
Instead of comparison methods you may want to just define several types of "Comparator" subclasses inside the Person class. That way you can pass them into standard Collections sorting methods.
//Following is the example in jdk 1.8
package com;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
class User {
private String firstName;
private String lastName;
private Integer age;
public Integer getAge() {
return age;
}
public User setAge(Integer age) {
this.age = age;
return this;
}
public String getFirstName() {
return firstName;
}
public User setFirstName(String firstName) {
this.firstName = firstName;
return this;
}
public String getLastName() {
return lastName;
}
public User setLastName(String lastName) {
this.lastName = lastName;
return this;
}
}
public class MultiFieldsComparision {
public static void main(String[] args) {
List<User> users = new ArrayList<User>();
User u1 = new User().setFirstName("Pawan").setLastName("Singh").setAge(38);
User u2 = new User().setFirstName("Pawan").setLastName("Payal").setAge(37);
User u3 = new User().setFirstName("Anuj").setLastName("Kumar").setAge(60);
User u4 = new User().setFirstName("Anuj").setLastName("Kumar").setAge(43);
User u5 = new User().setFirstName("Pawan").setLastName("Chamoli").setAge(44);
User u6 = new User().setFirstName("Pawan").setLastName("Singh").setAge(5);
users.add(u1);
users.add(u2);
users.add(u3);
users.add(u4);
users.add(u5);
users.add(u6);
System.out.println("****** Before Sorting ******");
users.forEach(user -> {
System.out.println(user.getFirstName() + " , " + user.getLastName() + " , " + user.getAge());
});
System.out.println("****** Aftre Sorting ******");
users.sort(
Comparator.comparing(User::getFirstName).thenComparing(User::getLastName).thenComparing(User::getAge));
users.forEach(user -> {
System.out.println(user.getFirstName() + " , " + user.getLastName() + " , " + user.getAge());
});
}
}
Code implementation of the same is here if we have to sort the Person object based on multiple fields.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class Person {
private String firstName;
private String lastName;
private int age;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
static class PersonSortingComparator implements Comparator<Person> {
#Override
public int compare(Person person1, Person person2) {
// for first name comparison
int firstNameCompare = person1.getFirstName().compareTo(person2.getFirstName());
// for last name comparison
int lastNameCompare = person1.getLastName().compareTo(person2.getLastName());
// for last name comparison
int ageCompare = person1.getAge() - person2.getAge();
// Now comparing
if (firstNameCompare == 0) {
if (lastNameCompare == 0) {
return ageCompare;
}
return lastNameCompare;
}
return firstNameCompare;
}
}
public static void main(String[] args) {
Person person1 = new Person("Ajay", "Kumar", 27);
Person person2 = new Person("Ajay","Gupta", 23);
Person person3 = new Person("Ajay","Kumar", 22);
ArrayList<Person> persons = new ArrayList<>();
persons.add(person1);
persons.add(person2);
persons.add(person3);
System.out.println("Before Sorting:\n");
for (Person person : persons) {
System.out.println(person.firstName + " " + person.lastName + " " + person.age);
}
Collections.sort(persons, new PersonSortingComparator());
System.out.println("After Sorting:\n");
for (Person person : persons) {
System.out.println(person.firstName + " " + person.lastName + " " + person.age);
}
}
}
I think it'd be more confusing if your comparison algorithm were "clever". I'd go with the numerous comparison methods you suggested.
The only exception for me would be equality. For unit testing, it's been useful to me to override the .Equals (in .net) in order to determine if several fields are equal between two objects (and not that the references are equal).
If there are multiple ways a user might order person, you could also have multiple Comparators setup as constants somewhere. Most of the sort operations and sorted collections take a comparator as a parameter.
Java 8 through lambda way we can compare by method reference.
Student POJO
public class Student {
int id;
String firstName;
String lastName;
String subject;
public Student(int id, String firstName, String lastName, String subject) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.subject = subject;
}
enter code here
Now we can sort based on
1. id->FirstName->LastName->Subject 2. Subject->id->FirstName->LastName
We will use Comparator in array Stream
public class TestComprator {
public static void main(String[] args) {
Student s1= new Student(108, "James", "Testo", "Physics");
Student s2= new Student(101, "Fundu", "Barito", "Chem");
Student s3= new Student(105, "Sindhu", "Sharan", "Math");
Student s4= new Student(98, "Rechel", "Stephen", "Physics");
System.out.printf("----------id->FirstName->LastName->Subject-------------");
Arrays.asList(s1,s2,s3,s4).stream()
.sorted(Comparator.comparing(Student::getId)
.thenComparing(Student::getFirstName)
.thenComparing(Student::getLastName)
.thenComparing(Student::getSubject))
.forEach(System.out::println);
System.out.printf("----Subject->id->FirstName->LastName ------\n");
Arrays.asList(s1,s2,s3,s4).stream()
.sorted(Comparator. comparing(Student::getSubject)
.thenComparing(Student::getId)
.thenComparing(Student::getFirstName)
.thenComparing(Student::getLastName)
)
.forEach(System.out::println);
}
}
Output:-
`----------id->FirstName->LastName->Subject-------------
Student{id=98, firstName='Rechel', lastName='Stephen', subject='Physics'}
Student{id=101, firstName='Fundu', lastName='Barito', subject='Chem'}
Student{id=105, firstName='Sindhu', lastName='Sharan', subject='Math'}
Student{id=108, firstName='James', lastName='Testo', subject='Physics'}
----Subject->id->FirstName->LastName ------
Student{id=101, firstName='Fundu', lastName='Barito', subject='Chem'}
Student{id=105, firstName='Sindhu', lastName='Sharan', subject='Math'}
Student{id=98, firstName='Rechel', lastName='Stephen', subject='Physics'}
Student{id=108, firstName='James', lastName='Testo', subject='Physics'}
//here threshold,buyRange,targetPercentage are three keys on that i have sorted my arraylist
final Comparator<BasicDBObject>
sortOrder = new Comparator<BasicDBObject>() {
public int compare(BasicDBObject e1, BasicDBObject e2) {
int threshold = new Double(e1.getDouble("threshold"))
.compareTo(new Double(e2.getDouble("threshold")));
if (threshold != 0)
return threshold;
int buyRange = new Double(e1.getDouble("buyRange"))
.compareTo(new Double(e2.getDouble("buyRange")));
if (buyRange != 0)
return buyRange;
return (new Double(e1.getDouble("targetPercentage")) < new Double(
e2.getDouble("targetPercentage")) ? -1 : (new Double(
e1.getDouble("targetPercentage")) == new Double(
e2.getDouble("targetPercentage")) ? 0 : 1));
}
};
Collections.sort(objectList, sortOrder);
Better late than never - if you're looking for unnecessary clutter or overhead then it's hard to beat the following in terms of least code/fast execution at the same time.
Data class:
public class MyData {
int id;
boolean relevant;
String name;
float value;
}
Comparator:
public class MultiFieldComparator implements Comparator<MyData> {
#Override
public int compare(MyData dataA, MyData dataB) {
int result;
if((result = Integer.compare(dataA.id, dataB.id)) == 0 &&
(result = Boolean.compare(dataA.relevant, dataB.relevant)) == 0 &&
(result = dataA.name.compareTo(dataB.name)) == 0)
result = Float.compare(dataA.value, dataB.value);
return result;
}
}
If you are just looking to sort a collection by a custom order then the following is even cleaner:
myDataList.sort((dataA, dataB) -> {
int result;
if((result = Integer.compare(dataA.id, dataB.id)) == 0 &&
(result = Boolean.compare(dataA.relevant, dataB.relevant)) == 0 &&
(result = dataA.name.compareTo(dataB.name)) == 0)
result = Float.compare(dataA.value, dataB.value);
return result;
});
If you implement the Comparable interface, you'll want to choose one simple property to order by. This is known as natural ordering. Think of it as the default. It's always used when no specific comparator is supplied. Usually this is name, but your use case may call for something different. You are free to use any number of other Comparators you can supply to various collections APIs to override the natural ordering.
Also note that typically if a.compareTo(b) == 0, then a.equals(b) == true. It's ok if not but there are side effects to be aware of. See the excellent javadocs on the Comparable interface and you'll find lots of great information on this.
Following blog given good chained Comparator example
http://www.codejava.net/java-core/collections/sorting-a-list-by-multiple-attributes-example
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
/**
* This is a chained comparator that is used to sort a list by multiple
* attributes by chaining a sequence of comparators of individual fields
* together.
*
*/
public class EmployeeChainedComparator implements Comparator<Employee> {
private List<Comparator<Employee>> listComparators;
#SafeVarargs
public EmployeeChainedComparator(Comparator<Employee>... comparators) {
this.listComparators = Arrays.asList(comparators);
}
#Override
public int compare(Employee emp1, Employee emp2) {
for (Comparator<Employee> comparator : listComparators) {
int result = comparator.compare(emp1, emp2);
if (result != 0) {
return result;
}
}
return 0;
}
}
Calling Comparator:
Collections.sort(listEmployees, new EmployeeChainedComparator(
new EmployeeJobTitleComparator(),
new EmployeeAgeComparator(),
new EmployeeSalaryComparator())
);
Starting from Steve's answer the ternary operator can be used:
public int compareTo(Person other) {
int f = firstName.compareTo(other.firstName);
int l = lastName.compareTo(other.lastName);
return f != 0 ? f : l != 0 ? l : Integer.compare(age, other.age);
}
It is easy to compare two objects with hashcode method in java`
public class Sample{
String a=null;
String b=null;
public Sample(){
a="s";
b="a";
}
public Sample(String a,String b){
this.a=a;
this.b=b;
}
public static void main(String args[]){
Sample f=new Sample("b","12");
Sample s=new Sample("b","12");
//will return true
System.out.println((s.a.hashCode()+s.b.hashCode())==(f.a.hashCode()+f.b.hashCode()));
//will return false
Sample f=new Sample("b","12");
Sample s=new Sample("b","13");
System.out.println((s.a.hashCode()+s.b.hashCode())==(f.a.hashCode()+f.b.hashCode()));
}
Its easy to do using Google's Guava library.
e.g. Objects.equal(name, name2) && Objects.equal(age, age2) && ...
More examples:
https://stackoverflow.com/a/5039178/1180621
I have this object ClientSearch
public class ClientSearch{
private Long clientNumber;
private String queueNumber;
private String clientName;
private String customerName;
.....
}
I need to sort this by this rule:
Show the clientNumber descending first then by customerName descending and after all records with clientNumber are displayed, sort by queueNumber in descending order and customerName in descending order.
This should be the result --- ClientNumber desc then, CustomerName desc, then, QueueNumber desc then, CustomerName desc
ClientNumber ClientName CustomerName
987654 Client1 Xyz1
987654 Client2 Abc2
555555 Client3 zzzzz1
21212 XYZ1 aaaab
111111 Client10 cZ
111111 Client11 cY
Z1Z1Z1 ClientX zzzzz
Z1Z1Z1 ClientY yyyyy
X2X2X2 Clienxyz aaaaaa
HHHHHH ClientH bbbbbb
AAAAAA ClientA xxxxxx
Basically each ClientSearch object has either a clientNumber or a queueNumber (if there is no client number then the queuNumber is considered a client number thats why it is displayed under that column),
what I thought of doing is, after I receive a List from my query (which I have no control, I just received the List<>), i will iterate through the list with the condition.
if clientNumber is present then add to a List<ClientSearch> withClientNumbers
else if queueNumber is present then add to a List<ClientSearch> withQueueNumbers
After doing this, I'll sort the withClientNumbers with a Comparator that compares the ClientSearch's clientNumber then if they are equal to zero ill do another compare with ClientSearch's customerName. I just need to multiply these with -1 to make sure it is DESC.
I'll do the same with queueNumbers but with a different comparator.
then Ill create a new List newList and then will do this
newList.addAll(withClientNumbers); //make sure this is first in the list
newList.addAll(queueNumbers); // so to satisfy the condition that queueNumbers are shown after a sorted clientNumbers.
Can you suggest any other elegant way to do this? I feel that my method is not the most optimized one. Take note that I'm using Java 1.6
When it comes to sorting, it is usually just about how you are going to implement the Comparator. In such case, you simply need a Comparator that compares two ClientSearch objects with the way you described.
It will be much cleaner if you can simplify your requirement to sort by
Customer Number (nulls last)
Queue ID, then
Customer Name
The comparator will be as easy as this, with Java 8:
import static java.util.Comparator.*;
....
Comparator<Customer> comparator =
comparing(Customer::getCustNumber, nullsLast(naturalOrder()))
.thenComparing(Customer::getQueueId)
.thenComparing(Customer::getCustName);
It is a bit different from what you originally asked for. What you asked for is
If both have Customer Number, compare by
Customer Number
Customer Name
If both does not have Customer Number, compare by
Queue ID
Customer Name
If one has Customer Number while the other does not, the one with null Customer Number is treated as greater.
If you really need what you are looking for, it is not difficult to do. For example, you can have two Comparators each for the separate case, and combine then as one Comparator, looks something like (with aid of Java 8, should not be difficult to write one for prev Java version if you get the idea):
public class ClientSearchComparator implements Comparator<ClientSearch> {
private static Comparator<ClientSearch> custNumberComparator =
Comparator.comparing(ClientSearch::getCustNumber)
.thenComparing(ClientSearch::getCustName);
private static Comparator<ClientSearch> queueIdComparator =
Comparator.comparing(ClientSearch::getQueueId)
.thenComparing(ClientSearch::getCustName);
#Override
public int compare(ClientSearch c1, ClientSearch c2) {
if (c1.getCustNumber() != null && c2.getCustNumber() != null) {
return custIdComparator.compare(c1, c2);
} else if (c1.getCustNumber() == null && c2.getCustNumber() == null) {
return queueIdComparator.compare(c1, c2);
} else if (c1.getCustNumber() != null && c2.getCustNumber() == null) {
return -1;
} else { // (c1.getCustNumber() == null && c2.getCustNumber() != null)
return 1;
}
}
}
(I believe I do not need to tell you how to do sorting with an Comparator right?)
Update:
As you have mentioned that you are using Java 6, here is the basic idea on what that Comparator will look like (pseudo-code):
public class ClientSearchComparator implements Comparator<ClientSearch> {
#Override
public int compare(ClientSearch c1, ClientSearch c2) {
if (c1.custNum != null && c2.custNum != null) {
if (c1.custNum != c2.custNum) {
return c1.custNum.compareTo(c2.custNum);
}
return c1.custName.compareTo(c2.custName);
} else if (c1.custNum == null && c2.custNum == null) {
if (c1.queueId != c2.queueId) {
return c1.queueId .compareTo(c2.queueId);
}
return c1.custName.compareTo(c2.custName);
} else if (c1.custNum == null) { // c1 null && c2 not null
return 1;
} else { // c1 not null && c2 null
return -1;
}
}
(it will look better with some reorganization, or with aid of tools like Guava or Apache Common Langs)
Yours is correct way, I just tested it as below. Note optimization is done in sorting and not in comparing functions. Since you will be using Java own's sorting method you don't have to worry about optimization.
Basically you are just breaking off ties of equality of clientNumber with customerNames. Here I am taking queuenumbers as clientnumbers only for simplicity. Since you are already making different lists same solution can be applied to both lists and then merge the lists. Sample code below :
public class ClientSearch{
private String clientNumber;
// private String queueNumber;
private String clientName;
private String customerName;
public ClientSearch(String clientNumber, String clientName, String customerName) {
this.clientNumber = clientNumber;
//this.queueNumber = queueNumber;
this.clientName = clientName;
this.customerName = customerName;
}
public String toString(){
return clientNumber+" "+clientName+" "+customerName;
}
public static void main(String[] args) {
try {
BufferedReader br = new BufferedReader(new FileReader("input.txt"));
String tmp = null;
List<ClientSearch> list = new ArrayList<>();
while((tmp=br.readLine())!=null){
String split [] = tmp.split(" ");
list.add(new ClientSearch(split[0],split[1],split[2]));
}
System.out.println("Sorting.....");
list.sort(new Comparator<ClientSearch>() {
#Override
public int compare(ClientSearch o1, ClientSearch o2) {
int diff = o1.clientNumber.compareTo(o2.clientNumber);
return diff ==0 ? o1.customerName.compareTo(o2.customerName) : diff;
}
});
for (ClientSearch c : list){
System.out.println(c);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
I am looking to implement a sort feature for my address book application.
I want to sort an ArrayList<Contact> contactArray. Contact is a class which contains four fields: name, home number, mobile number and address. I want to sort on name.
How can I write a custom sort function to do this?
Here's a tutorial about ordering objects:
The Java Tutorials - Collections - Object Ordering
Although I will give some examples, I would recommend to read it anyway.
There are various way to sort an ArrayList. If you want to define a natural (default) ordering, then you need to let Contact implement Comparable. Assuming that you want to sort by default on name, then do (nullchecks omitted for simplicity):
public class Contact implements Comparable<Contact> {
private String name;
private String phone;
private Address address;
#Override
public int compareTo(Contact other) {
return name.compareTo(other.name);
}
// Add/generate getters/setters and other boilerplate.
}
so that you can just do
List<Contact> contacts = new ArrayList<Contact>();
// Fill it.
Collections.sort(contacts);
If you want to define an external controllable ordering (which overrides the natural ordering), then you need to create a Comparator:
List<Contact> contacts = new ArrayList<Contact>();
// Fill it.
// Now sort by address instead of name (default).
Collections.sort(contacts, new Comparator<Contact>() {
public int compare(Contact one, Contact other) {
return one.getAddress().compareTo(other.getAddress());
}
});
You can even define the Comparators in the Contact itself so that you can reuse them instead of recreating them everytime:
public class Contact {
private String name;
private String phone;
private Address address;
// ...
public static Comparator<Contact> COMPARE_BY_PHONE = new Comparator<Contact>() {
public int compare(Contact one, Contact other) {
return one.phone.compareTo(other.phone);
}
};
public static Comparator<Contact> COMPARE_BY_ADDRESS = new Comparator<Contact>() {
public int compare(Contact one, Contact other) {
return one.address.compareTo(other.address);
}
};
}
which can be used as follows:
List<Contact> contacts = new ArrayList<Contact>();
// Fill it.
// Sort by address.
Collections.sort(contacts, Contact.COMPARE_BY_ADDRESS);
// Sort later by phone.
Collections.sort(contacts, Contact.COMPARE_BY_PHONE);
And to cream the top off, you could consider to use a generic javabean comparator:
public class BeanComparator implements Comparator<Object> {
private String getter;
public BeanComparator(String field) {
this.getter = "get" + field.substring(0, 1).toUpperCase() + field.substring(1);
}
public int compare(Object o1, Object o2) {
try {
if (o1 != null && o2 != null) {
o1 = o1.getClass().getMethod(getter, new Class[0]).invoke(o1, new Object[0]);
o2 = o2.getClass().getMethod(getter, new Class[0]).invoke(o2, new Object[0]);
}
} catch (Exception e) {
// If this exception occurs, then it is usually a fault of the developer.
throw new RuntimeException("Cannot compare " + o1 + " with " + o2 + " on " + getter, e);
}
return (o1 == null) ? -1 : ((o2 == null) ? 1 : ((Comparable<Object>) o1).compareTo(o2));
}
}
which you can use as follows:
// Sort on "phone" field of the Contact bean.
Collections.sort(contacts, new BeanComparator("phone"));
(as you see in the code, possibly null fields are already covered to avoid NPE's during sort)
In addition to what was already posted by BalusC it may be worth pointing that since Java 8 we can shorten our code and write it like:
Collection.sort(yourList, Comparator.comparing(YourClass::getSomeComparableField));
or since List now have sort method also like
yourList.sort(Comparator.comparing(YourClass::getSomeComparableField));
Explanation:
Since Java 8, functional interfaces (interfaces with only one abstract method - they can have more default or static methods) can be easily implemented using:
lambdas arguments -> body
or method references source::method.
Since Comparator<T> has only one abstract method int compare(T o1, T o2) it is functional interface.
So instead of (example from #BalusC answer)
Collections.sort(contacts, new Comparator<Contact>() {
public int compare(Contact one, Contact other) {
return one.getAddress().compareTo(other.getAddress());
}
});
we can reduce this code to:
Collections.sort(contacts, (Contact one, Contact other) -> {
return one.getAddress().compareTo(other.getAddress());
});
We can simplify this (or any) lambda by skipping
argument types (Java will infer them based on method signature)
or {return ... }
So instead of
(Contact one, Contact other) -> {
return one.getAddress().compareTo(other.getAddress();
}
we can write
(one, other) -> one.getAddress().compareTo(other.getAddress())
Also now Comparator has static methods like comparing(FunctionToComparableValue) or comparing(FunctionToValue, ValueComparator) which we could use to easily create Comparators which should compare some specific values from objects.
In other words we can rewrite above code as
Collections.sort(contacts, Comparator.comparing(Contact::getAddress));
//assuming that Address implements Comparable (provides default order).
This page tells you all you need to know about sorting collections, such as ArrayList.
Basically you need to
make your Contact class implement the Comparable interface by
creating a method public int compareTo(Contact anotherContact) within it.
Once you do this, you can just call Collections.sort(myContactList);,
where myContactList is ArrayList<Contact> (or any other collection of Contact).
There's another way as well, involving creating a Comparator class, and you can read about that from the linked page as well.
Example:
public class Contact implements Comparable<Contact> {
....
//return -1 for less than, 0 for equals, and 1 for more than
public compareTo(Contact anotherContact) {
int result = 0;
result = getName().compareTo(anotherContact.getName());
if (result != 0)
{
return result;
}
result = getNunmber().compareTo(anotherContact.getNumber());
if (result != 0)
{
return result;
}
...
}
}
BalusC and bguiz have already given very complete answers on how to use Java's built-in Comparators.
I just want to add that google-collections has an Ordering class which is more "powerful" than the standard Comparators.
It might be worth checking out. You can do cool things such as compounding Orderings, reversing them, ordering depending on a function's result for your objects...
Here is a blog post that mentions some of its benefits.
You need make your Contact classes implement Comparable, and then implement the compareTo(Contact) method. That way, the Collections.sort will be able to sort them for you. Per the page I linked to, compareTo 'returns a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object.'
For example, if you wanted to sort by name (A to Z), your class would look like this:
public class Contact implements Comparable<Contact> {
private String name;
// all the other attributes and methods
public compareTo(Contact other) {
return this.name.compareTo(other.name);
}
}
By using lambdaj you can sort a collection of your contacts (for example by their name) as it follows
sort(contacts, on(Contact.class).getName());
or by their address:
sort(contacts, on(Contacts.class).getAddress());
and so on. More in general, it offers a DSL to access and manipulate your collections in many ways, like filtering or grouping your contacts based on some conditions, aggregate some of their property values, etc.
Ok, I know this was answered a long time ago... but, here's some new info:
Say the Contact class in question already has a defined natural ordering via implementing Comparable, but you want to override that ordering, say by name. Here's the modern way to do it:
List<Contact> contacts = ...;
contacts.sort(Comparator.comparing(Contact::getName).reversed().thenComparing(Comparator.naturalOrder());
This way it will sort by name first (in reverse order), and then for name collisions it will fall back to the 'natural' ordering implemented by the Contact class itself.
The Collections.sort is a good sort implementation. If you don't have The comparable implemented for Contact, you will need to pass in a Comparator implementation
Of note:
The sorting algorithm is a modified mergesort (in which the merge is omitted if the highest element in the low sublist is less than the lowest element in the high sublist). This algorithm offers guaranteed n log(n) performance. The specified list must be modifiable, but need not be resizable. This implementation dumps the specified list into an array, sorts the array, and iterates over the list resetting each element from the corresponding position in the array. This avoids the n2 log(n) performance that would result from attempting to sort a linked list in place.
The merge sort is probably better than most search algorithm you can do.
I did it by the following way.
number and name are two arraylist. I have to sort name .If any change happen to name arralist order then the number arraylist also change its order.
public void sortval(){
String tempname="",tempnum="";
if (name.size()>1) // check if the number of orders is larger than 1
{
for (int x=0; x<name.size(); x++) // bubble sort outer loop
{
for (int i=0; i < name.size()-x-1; i++) {
if (name.get(i).compareTo(name.get(i+1)) > 0)
{
tempname = name.get(i);
tempnum=number.get(i);
name.set(i,name.get(i+1) );
name.set(i+1, tempname);
number.set(i,number.get(i+1) );
number.set(i+1, tempnum);
}
}
}
}
}
use this method:
private ArrayList<myClass> sortList(ArrayList<myClass> list) {
if (list != null && list.size() > 1) {
Collections.sort(list, new Comparator<myClass>() {
public int compare(myClass o1, myClass o2) {
if (o1.getsortnumber() == o2.getsortnumber()) return 0;
return o1.getsortnumber() < o2.getsortnumber() ? 1 : -1;
}
});
}
return list;
}
`
and use: mySortedlist = sortList(myList);
No need to implement comparator in your class.
If you want inverse order swap 1 and -1
With java 8 feature
List<Contact> contact = contactArray.stream().sorted((c1, c2) -> ((c1.getName().compareTo(c2.getName())))).collect(Collectors.toList());
You shoud use the Arrays.sort function. The containing classes should implement Comparable.