compare more field of a object in compareTo - java

I'd compare more than one only field of a object using the compareTo method. Is it possible?
for istance:
public int compareTo(Object o) {
return field.compareTo(o.field);
}
I create this method to sort a collection. Obviously my object has to implement Comparable interface.
I'm guessing if is possible to compare not only one field in the same method compareTo.

Yes, it's possible. If the result of comparing the first field returns zero, then return the result of comparing the second field.
public int compareTo(SomeClass o) {
int result = field1.compareTo(o.field1);
if ( result == 0 ) {
result = field2.compareTo(o.field2);
}
return result;
}
This gets cumbersome fairly quickly, which is why Guava provides a ComparisonChain. Example use:
public int compareTo(SomeClass o) {
return ComparisonChain.start()
.compare(field1, o.field1)
.compare(field2, o.field2)
.result();
}

Yes, it's possible, for example like so:
public int compareTo(MyClass o){
int ret = field1.compareTo(o.field1);
if (ret != 0) return ret;
ret = field2.compareTo(o.field2);
if (ret != 0) return ret;
...
return fieldN.compareTo(o.fieldN);
}

You can certainly factor in other fields when comparing, but there's typically some order of precedence, just like sorting alphabetically only looks at the second letter if the first is the same:
public int compareTo(Object o){
int comparison = field.compareTo(o.field);
if (comparison != 0)
return comparison;
comparison = field2.compareTo(o.field2);
if (comparison != 0)
return comparison;
//etc...
}

sure you can.
however you must define the rule of comparison .
e.g.
you have
objectA{a=1;b=2;c=3}
objectB{a=20;b=1;c=6}
in your compareTo(Object o) method, you could compare this.fields with o.fields. you can even compare this.a to o.c if you really need. point is you have to define the rule, in which case objectA < objectB. etc..

Sure. Here's a relatively concise way of doing it.
public int compareTo(MyClass other) {
return
a!=other.a ? Integer.compare(a, other.a) :
b!=other.b ? Integer.compare(b, other.b) :
Integer.compare(c, other.c);
}
(Integer.compare is from Java SE 7, but the implementation isn't difficult. Assumes int fields a, b, c, but is essentially the same for any field types you can compare.)

public int compareTo(Object o){
int res = field.compareTo(o.field);
if(res==0)
res=field1.compareTo(o.field1);
return res;
}
should work

You can do it any of following ways:
public int compareTo(Object o)
{
return (field.compareTo(o.field)==1 && field2.compareTo(o.field2)==0)? 0 : 1;
}
OR
public int compareTo(Object o)
{
// add various if-else blocks
// OR
// call a separate method
}

Related

Not sure how to complete this equals method

Could someone help me with this question please? I've tried looking up other examples of this to find what I need to do and keep running into something called and EqualsBuilder, is that what I need to use? Do I need to have it call on equals again if it satisfies neither of the IFs?
The following code contains a class definition and an incomplete method definition. The equals method is used to compare Buildings.
It is intended to return true if Buildings have the same names and number of floors (but are not necessarily the same Building) and false otherwise.
public class Building {
private String name;
private int noOfFloors;
public boolean equals (Object rhs) {
if (this == rhs) {
return true;
}
if (!(rhs instanceof Building)) {
return false;
}
Building b = (Building) rhs;
// missing return statement
}
}
public boolean equals (Object rhs) {
if (this == rhs) {
return true;
}
if (!(rhs instanceof Building)) {
return false;
}
Building b = (Building) rhs;
// This is what you're supposed to add. It will return true only if both
// object's attributes (name and number of floors) are the same
return this.name.equals(b.name) && this.noOfFloors == b.noOfFloors;
}
The only thing that you have to test for now is the fields of both objects. If they are equal, then you should return true, if at least one of them is not then you should return false.
Since your fields in that case are int and Stringyou can use == for the integer field and .equals() for the String field.
Something like this should do the job just fine:
if(this.name.equals(b.name) && this.noOfFloors == b.noOfFloors){
return true ;
}
else{
return false;
}
After the instanceOf tests you want to compare the fields of the object to the other object. Something like Objects.deepEquals() should do the trick for you nicely.

sort list of objects based on field which can be null

How to to sort list of objects based on field which can be null?
I am trying to do it in following way using Comparator interface and collections sort method.
The class is CustomClass and the field on which sorting is to be done is createDate
Comparator comparator=new Comparator<CustomClass>(){
public int compare(CustomClass o1, CustomClass o2) {
if(o1.getCreateDate()==null && o2.getCreateDate()==null){
return 0;
}
else if(o1.getCreateDate()==null && o2.getCreateDate()!=null){
return 1;
}
else if(o1.getCreateDate()!=null && o2.getCreateDate()==null){
return -1;
}
else{
if(o1.getCreateDate().equals(o2.getCreateDate())){
return 0;
}
else if(o1.getCreateDate().after(o2.getCreateDate())){
return 1;
}
else{
return -1;
}
}
}
};
Is there a better way to do it?
If you're willing to use Google Guava, then you can use ComparisonChain and Ordering to make things more succinct.
public int compare(CustomClass o1, CustomClass o2)
{
return ComparisonChain.start()
.compare(o1.getCreateDate(), o2.getCreateDate(), Ordering.natural().nullsLast())
.result();
}
Assuming getCreateDate() returns an instance of java.util.Date, you can clean up the code a bit. The compareTo's method's contract specifies that it throws a NullPointerException if a date is compared to null (like any Comparable class should do), so you'll have to handle those directly. However, if both are non-null, you shouldn't reimplement the comparing logic, but rely on Date's implementation:
Comparator<CustomClass> comparator = new Comparator<CustomClass>(){
public int compare(CustomClass o1, CustomClass o2) {
if (o1.getCreateDate() == null && o2.getCreateDate() == null) {
return 0;
}
else if (o1.getCreateDate() == null && o2.getCreateDate() != null) {
return 1;
}
else if (o1.getCreateDate() != null && o2.getCreateDate() == null) {
return -1;
}
else{
// Just use java.util.Date's logic:
return o1.getCreateDate().compareTo(o2.getCreateDate());
}
}
};
EDIT:
This is quite an old question, but just to complete the picture, in Java 8, the Comparator class itself can do the heavy lifting for you:
Comparator<CustomClass> comparator =
Comparator.comparing(CustomClass::getCreateDate,
Comparator.nullsLast(Comparator.naturalOrder()));
You do need all of that to ensure that the null values always sort to the end of the list and otherwise you sort by date. You could simplify your code a bit by delegating to the built-in compareTo or by using a declarative Guava Ordering.

contains giving faulty results

I have a class 'CoAutoria' that's suposed to hold 2 instances of an 'Author' class (which only has a name, for now) and the number of articles those authors have in common.
In order to figure out the top 10 of co-authors (regarding number of articles) I created a TreeSet of 'CoAutoria', to hold the total of articles, for each pair.
I need to cycle through a Map of years, gather the different authors and their respective Set of co-Authors. Then, for each pair, create an instance of 'CoAutoria' and: add it to the treeset (if it doesn't already exists); or simply sum its number of articles to the one existing on the set.
I already created the compareTo method, to insert it on the treeset, and created the equals method so that the order of the authors doesn't matter.
Here's the main code:`
public class CoAutoria implements Comparable<CoAutoria>
{
private Autor autor1;
private Autor autor2;
private int artigosComum;
(...)
}
#Override
public int compareTo(CoAutoria a2)
{
String thisAutor1 = autor1.getNome();
String thisAutor2 = autor2.getNome();
String caAutor1 = a2.getAutor1().getNome();
String caAutor2 = a2.getAutor2().getNome();
if((autor1.equals(a2.getAutor1()) && autor2.equals(a2.getAutor2())) || (autor1.equals(a2.getAutor2()) && autor2.equals(a2.getAutor1())))
{
return 0;
}
else
{
return 1;
}
}
#Override
public boolean equals(Object o)
{
if(this == o)
{
return true;
}
if( o == null || o.getClass() != this.getClass())
return false;
CoAutoria ca = (CoAutoria) o;
String thisAutor1 = autor1.getNome();
String thisAutor2 = autor2.getNome();
String caAutor1 = ca.getAutor1().getNome();
String caAutor2 = ca.getAutor2().getNome();
if((thisAutor1.equals(caAutor1) && thisAutor2.equals(caAutor2)) || (thisAutor1.equals(caAutor2) && thisAutor2.equals(caAutor1)))
{
return true;
}
else
{
return false;
}
}
The main problem is: When I check if the set already has a certain instance of 'CoAutoria', (I'm using the contains() method of TreeSet), it gives me faulty results...sometimes it checks correctly that the Pair A-B already exists in that set (on the form of B-A), but sometimes it doesn't... For what I've read, the contains uses the equals method, so that's not suposed to happen..right?
[EDIT:]
Since the first post I started to think that maybe the problem resided on the compareTo..So I changed it to
public int compareTo(CoAutoria a2)
{
String thisAutor1 = autor1.getNome();
String thisAutor2 = autor2.getNome();
String caAutor1 = a2.getAutor1().getNome();
String caAutor2 = a2.getAutor2().getNome();
if(this.equals(a2))
{
System.out.println("return 0");
return 0;
}
else
{
int aux = thisAutor1.compareTo(caAutor1);
if(aux != 0)
{
return aux;
}
else
{
return thisAutor2.compareTo(caAutor2);
}
}
}
But it still gives my bad results..I thought I'd figured it now: if it's the same 'CoAutoria', I return 0, if not I go through the names, and order it by their compareTo values..but something's missing
Your contains method is breaking, because your compareTo method is always returning 0 or positive, no negatives. This means your compareTo is inconsistent. A correct implementation should return 0 if the authors are the same, or positive and negative values when the authors are different.
Example (assuming author1 is different than author2):
int i = author1.compareTo(author2); // i should be positive or negative
int j = author2.compareTo(author1); // j should be the opposite of i
Yours will return 1 for both of the above cases, which will make ordered Collections not work as no element is ever smaller. As another example imagine if you had a Binary Tree(an ordered collection) that had the elements [1-10]. If you were searching for the element 5, your binary tree when comparing 5 against any element would always say that it was equal or greater.
How exactly you should change it is up to you. But an idea would be to sort the authors by name, then iterate over both collections and compare the authors together lexicographically.
EDIT: Even after your edit to your methods they are still not consistent. Try the following, they aren't the most efficient but should work unless you really want to optimize for speed. Notice they first sort to make sure author1 and author2 are in order before they are compared with the other CoAutor which is also sorted. I don't do any null checking and assume both are valid authors.
#Override
public boolean equals(Object o){
if (o == null || !(o instanceof CoAutoria)) return false;
if (o == this) return true;
return this.compareTo((CoAutoria)o) == 0;
}
#Override
public int compareTo(CoAutoria o) {
List<String> authors1 = Arrays.asList(autor1.getNome(), autor2.getNome());
List<String> authors2 = Arrays.asList(o.autor1.getNome(), o.autor2.getNome());
Collections.sort(authors1);
Collections.sort(authors2);
for (int i=0;i<authors1.size();i++){
int compare = authors1.get(i).compareTo(authors2.get(i));
if (compare != 0)
return compare;
}
return 0;
}

How to make a set of arrays in Java?

Since the equals function in array only check the instance, it doesn't work well with Set.
Hence, I wonder how to make a set of arrays in Java?
One possible way could be put each array in an object, and implement equals function for that class, but will that decrease the performance too much?
Don't use raw Arrays unless you absolutely have to because of some legacy API that requires an Array.
Always try and use a type safe ArrayList<T> instead and you won't have these kind of issues.
If you make your Set be an instance of TreeSet, you can specify a custom Comparator which will be used for all comparisons (even equality).
You could create a wrapper class for your array and override hashcode and equals accordingly.
For example:
public class MyArrayContainer {
int[] myArray = new int[100];
#Override
public boolean equals(Object other) {
if (null != other && other instanceof MyArrayContainer) {
MyArrayContainer o = (MyArrayContainer) other;
final int myLength = myArray.length;
if (o.myArray.length != myLength) {
return false;
}
for (int i = 0; i < myLength; i++) {
if (myArray[i] != o.myArray[i]) {
return false;
}
}
return true;
}
return false;
}
#Override
public int hashCode() {
return myArray.length;
}
}
Since Java 9, you can use Arrays::compare method as a comparator for TreeSet that compares the contents of arrays.
Set<String[]> set = new TreeSet<>(Arrays::compare);
String[] val1 = {"one", "two"};
String[] val2 = {"one", "two"};
String[] val3 = {"one", "two"};
set.add(val1);
set.add(val2);
System.out.println(set.size()); // 1
System.out.println(set.contains(val1)); // true
System.out.println(set.contains(val2)); // true
System.out.println(set.contains(val3)); // true
See also: Check if an array exists in a HashSet<int[]>
It is better to use lists for this problem.
Also, use trusted sources to ensure that Java is properly configured on your system; Read the complete information in the documentation
Since the ArrayList class already wraps an array, you can extend it and override the equals and hashCode methods. Here is a sample:
public MyArrayList extends ArrayList<MyClass> {
#Override
public boolean equals(Object o) {
if (o instanceof MyArrayList) {
//place your comparison logic here
return true;
}
return false;
}
#Override
public int hashCode() {
//just a sample, you can place your own code
return super.hashCode();
}
}
UPDATE:
You can even override it for a generic use, just changing the code to:
public MyArrayList<T> extends ArrayList<T> {
//overrides the methods you need
#Override
public boolean equals(Object o) {
if (o instanceof MyArrayList) {
//place your comparison logic here
return true;
}
return false;
}
}
A class that extends Set and override the equals method could do it.

Treeset.contains() problem

So I've been struggling with a problem for a while now, figured I might as well ask for help here.
I'm adding Ticket objects to a TreeSet, Ticket implements Comparable and has overridden equals(), hashCode() and CompareTo() methods. I need to check if an object is already in the TreeSet using contains(). Now after adding 2 elements to the set it all checks out fine, yet after adding a third it gets messed up.
running this little piece of code after adding a third element to the TreeSet, Ticket temp2 is the object I'm checking for(verkoopLijst).
Ticket temp2 = new Ticket(boeking, TicketType.STANDAARD, 1,1);
System.out.println(verkoop.getVerkoopLijst().first().hashCode());
System.out.println(temp2.hashCode());
System.out.println(verkoop.getVerkoopLijst().first().equals(temp2));
System.out.println(verkoop.getVerkoopLijst().first().compareTo(temp2));
System.out.println(verkoop.getVerkoopLijst().contains(temp2));
returns this:
22106622
22106622
true
0
false
Now my question would be how this is even possible?
Edit:
public class Ticket implements Comparable{
private int rijNr, stoelNr;
private TicketType ticketType;
private Boeking boeking;
public Ticket(Boeking boeking, TicketType ticketType, int rijNr, int stoelNr){
//setters
}
#Override
public int hashCode(){
return boeking.getBoekingDatum().hashCode();
}
#Override
#SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
public boolean equals(Object o){
Ticket t = (Ticket) o;
if(this.boeking.equals(t.getBoeking())
&&
this.rijNr == t.getRijNr() && this.stoelNr == t.getStoelNr()
&&
this.ticketType.equals(t.getTicketType()))
{
return true;
}
else return false;
}
/*I adjusted compareTo this way because I need to make sure there are no duplicate Tickets in my treeset. Treeset seems to call CompareTo() to check for equality before adding an object to the set, instead of equals().
*/
#Override
public int compareTo(Object o) {
int output = 0;
if (boeking.compareTo(((Ticket) o).getBoeking())==0)
{
if(this.equals(o))
{
return output;
}
else return 1;
}
else output = boeking.compareTo(((Ticket) o).getBoeking());
return output;
}
//Getters & Setters
On compareTo contract
The problem is in your compareTo. Here's an excerpt from the documentation:
Implementor must ensure sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) for all x and y.
Your original code is reproduced here for reference:
// original compareTo implementation with bug marked
#Override
public int compareTo(Object o) {
int output = 0;
if (boeking.compareTo(((Ticket) o).getBoeking())==0)
{
if(this.equals(o))
{
return output;
}
else return 1; // BUG!!!! See explanation below!
}
else output = boeking.compareTo(((Ticket) o).getBoeking());
return output;
}
Why is the return 1; a bug? Consider the following scenario:
Given Ticket t1, t2
Given t1.boeking.compareTo(t2.boeking) == 0
Given t1.equals(t2) return false
Now we have both of the following:
t1.compareTo(t2) returns 1
t2.compareTo(t1) returns 1
That last consequence is a violation of the compareTo contract.
Fixing the problem
First and foremost, you should have taken advantage of the fact that Comparable<T> is a parameterizable generic type. That is, instead of:
// original declaration; uses raw type!
public class Ticket implements Comparable
it'd be much more appropriate to instead declare something like this:
// improved declaration! uses parameterized Comparable<T>
public class Ticket implements Comparable<Ticket>
Now we can write our compareTo(Ticket) (no longer compareTo(Object)). There are many ways to rewrite this, but here's a rather simplistic one that works:
#Override public int compareTo(Ticket t) {
int v;
v = this.boeking.compareTo(t.boeking);
if (v != 0) return v;
v = compareInt(this.rijNr, t.rijNr);
if (v != 0) return v;
v = compareInt(this.stoelNr, t.stoelNr);
if (v != 0) return v;
v = compareInt(this.ticketType, t.ticketType);
if (v != 0) return v;
return 0;
}
private static int compareInt(int i1, int i2) {
if (i1 < i2) {
return -1;
} else if (i1 > i2) {
return +1;
} else {
return 0;
}
}
Now we can also define equals(Object) in terms of compareTo(Ticket) instead of the other way around:
#Override public boolean equals(Object o) {
return (o instanceof Ticket) && (this.compareTo((Ticket) o) == 0);
}
Note the structure of the compareTo: it has multiple return statements, but in fact, the flow of logic is quite readable. Note also how the priority of the sorting criteria is explicit, and easily reorderable should you have different priorities in mind.
Related questions
What is a raw type and why shouldn't we use it?
How to sort an array or ArrayList ASC first by x and then by y?
Should a function have only one return statement?
This could happen if your compareTo method isn't consistent. I.e. if a.compareTo(b) > 0, then b.compareTo(a) must be < 0. And if a.compareTo(b) > 0 and b.compareTo(c) > 0, then a.compareTo(c) must be > 0. If those aren't true, TreeSet can get all confused.
Firstly, if you are using a TreeSet, the actual behavior of your hashCode methods won't affect the results. TreeSet does not rely on hashing.
Really we need to see more code; e.g. the actual implementations of the equals and compareTo methods, and the code that instantiates the TreeSet.
However, if I was to guess, it would be that you have overloaded the equals method by declaring it with the signature boolean equals(Ticket other). That would lead to the behavior that you are seeing. To get the required behavior, you must override the method; e.g.
#Override
public boolean equals(Object other) { ...
(It is a good idea to put in the #Override annotation to make it clear that the method overrides a method in the superclass, or implements a method in an interface. If your method isn't actually an override, then you'll get a compilation error ... which would be a good thing.)
EDIT
Based on the code that you have added to the question, the problem is not overload vs override. (As I said, I was only guessing ...)
It is most likely that the compareTo and equals are incorrect. It is still not entirely clear exactly where the bug is because the semantics of both methods depends on the compareTo and equals methods of the Boeking class.
The first if statement of the Ticket.compareTo looks highly suspicious. It looks like the return 1; could cause t1.compareTo(t2) and t2.compareTo(t1) to both return 1 for some tickets t1 and t2 ... and that would definitely be wrong.

Categories

Resources