I learned about the Comparable interface, for which a class must implement compareTo method. A project I am using that method as:
public class EmployeeAssignmentTotal implements Comparable<EmployeeAssignmentTotal>, Serializable {
private Employee employee;
private int total;
....
public int compareTo(EmployeeAssignmentTotal other) {
return new CompareToBuilder()
.append(employee, other.employee)
.append(total, other.total)
.toComparison();
}
What exacly does CompareToBuilder do here? And how is it interacting with the employee and total attributes?
I did read the javadocs, but I cant make head or tail of what they are doing with the constructor and the multiple appends. Does this question indicate unclear intentions and zero research?
I was trying to figure out how CompareToBuilder works myself and I came across this post, but I wasn't happy with the answer. For instance, the toComparison method should return a negative integer, a positive integer, or zero as the builder has judged the "left-hand" side as less than, greater than, or equal to the "right-hand" side.
So my question was how the order of appending attributes affect the comparison. To answer that, the only thing I could do was check the source code and I found this:
public CompareToBuilder append(int lhs, int rhs) {
if (this.comparison != 0) {
return this;
}
this.comparison = ((lhs > rhs) ? 1 : (lhs < rhs) ? -1 : 0);
return this;
}
So, basically what is happening is that it will compare the attributes based on the order you append them. In your code example, "employee" will be evaluated first. If the left-hand side attributes evaluates as less than or greater then the right-hand side then, total is disregarded.
The attribute "total" will only be evaluated if "employee" evaluates to equal.
This class is meant to assist you in building compareTo()-methods. Imagine you had more than just 2 fields in your class - manually coding your comparison-method could be quite cumbersome.
CompareToBuilder is doing that for you - each append() method is adding a new comparison, and all comparisons are &&ed.
So the code you posted runs equals() on the employee object and total.
Related
I have a class OffbyOne where I declare an interface as a comparator called compare where I'm supposed to take two integers and return True if the difference between two integers is 1, and false if not.
Here is my code so far:
public static class OffbyOne {
public interface Compare {
int x;
int y;
if ((x-y) == 1) {
return true;
} else if ((y-x)==1) {
return true;
}
return false
public boolean equalChars(char x, char y); {
if (Compare(x,y) == true) {
return true;
}
return false;
}
I'm struggling to understand how comparators work in Java and what do I have to do. If anyone can please help me with this and then provide explanations for how it is supposed to be done, it would be great for me.
Thank you.
Comparator already exists in java - java.util.Comparator. It is something completely different from what you describe: Java's own comparator is a thing that you give 2 objects, and the comparator tells you which of the two 'comes earlier' in a sorting. Such an oracle is all you need to efficiently sort stuff.
It's allowed, but a bad idea, to name types the same as core java types. You can make your own String, which is different from java's own String in all ways (your String does not in any way replace java's string), it's just a confusing name, is all. You're doing the same thing with Comparator here. Bad idea. I'd call it OffByOne or similar.
declare an interface as a comparator called compare where I'm supposed to take two integers and return True if the difference between two integers is 1, and false if not.
This makes no sense whatsoever. You must have misunderstood the assignment. An interface describes the what and does not describe the how, whereas what you just said is describing the how. That's just not what interfaces do - they don't get to define the how. They only define the what. You're describing an implementation, not an interface.
public interface Compare {
int x;
int y;
if ((x-y) == 1) {
You can't stick code in types like this. You can stick only methods, fields, constructors, and other types in there. You can stick code in a method and then stick the method in a type, if you want. In addition, given that it is an interface, you can't stick code in one at all - interfaces define what a class can do, not how it does it (there is the default mechanism. That's definitely not what this assignments wants you to do so, so it doesn't apply here).
This would be an interface:
public interface DifferenceOfOne<T> {
public boolean isDifferenceOfOne(T a, T B);
}
This says: There is such a thing as a 'DifferenceOfOne' implementation for any given type. Such a class would implement the method isDifferenceOfOne, which takes in 2 parameters, both of that given type, and which then returns a boolean.
You can then make an implementation for the Integer type:
class IntDiffOfOne implements DifferenceOfOne<Integer> {
public boolean isDifferenceOfOne(Integer a, Integer b) {
return (a - 1 == b || b - 1 == a);
}
}
DifferenceOfOne<Integer> intDiffOfOne = new IntDiffOfOne();
Or in more common, modern java syntax:
DifferenceOfOne<Integer> intDiffOfOne = (a, b) -> (a - 1 == b || b - 1 == a);
And someone else can write a DiffOfOne implementation that, I dunno, tells you if any 2 LocalDate instances differ by exactly 1 day, perhaps, that would be a DifferenceOfOne<LocalDate>.
If this all sounds confusing to you - go back to whomever gave you this assignment, as either the assignment makes no sense, or you misheard/misunderstood it.
I have a class for which equality (as per equals()) must be defined by the object identity, i.e. this == other.
I want to implement Comparable to order such objects (say by some getName() property). To be consistent with equals(), compareTo() must not return 0, even if two objects have the same name.
Is there a way to compare object identities in the sense of compareTo? I could compare System.identityHashCode(o), but that would still return 0 in case of hash collisions.
I think the real answer here is: don't implement Comparable then. Implementing this interface implies that your objects have a natural order. Things that are "equal" should be in the same place when you follow up that thought.
If at all, you should use a custom comparator ... but even that doesn't make much sense. If the thing that defines a < b ... is not allowed to give you a == b (when a and b are "equal" according to your < relation), then the whole approach of comparing is broken for your use case.
In other words: just because you can put code into a class that "somehow" results in what you want ... doesn't make it a good idea to do so.
By definition, by assigning each object a Universally unique identifier (UUID) (or a Globally unique identifier, (GUID)) as it's identity property, the UUID is comparable, and consistent with equals. Java already has a UUID class, and once generated, you can just use the string representation for persistence. The dedicated property will also insure that the identity is stable across versions/threads/machines. You could also just use an incrementing ID if you have a method of insuring everything gets a unique ID, but using a standard UUID implementation will protect you from issues from set merges and parallel systems generating data at the same time.
If you use anything else for the comparable, that means that it is comparable in a way separate from its identity/value. So you will need to define what comparable means for this object, and document that. For example, people are comparable by name, DOB, height, or a combination by order of precedence; most naturally by name as a convention (for easier lookup by humans) which is separate from if two people are the same person. You will also have to accept that compareto and equals are disjoint because they are based on different things.
You could add a second property (say int id or long id) which would be unique for each instance of your class (you can have a static counter variable and use it to initialize the id in your constructor).
Then your compareTo method can first compare the names, and if the names are equal, compare the ids.
Since each instance has a different id, compareTo will never return 0.
While I stick by my original answer that you should use a UUID property for a stable and consistent compare / equality setup, I figured I'd go ahead an answer the question of "how far could you go if you were REALLY paranoid and wanted a guaranteed unique identity for comparable".
Basically, in short if you don't trust UUID uniqueness or identity uniqueness, just use as many UUIDs as it takes to prove god is actively conspiring against you. (Note that while not technically guaranteed not to throw an exception, needing 2 UUID should be overkill in any sane universe.)
import java.time.Instant;
import java.util.ArrayList;
import java.util.UUID;
public class Test implements Comparable<Test>{
private final UUID antiCollisionProp = UUID.randomUUID();
private final ArrayList<UUID> antiuniverseProp = new ArrayList<UUID>();
private UUID getParanoiaLevelId(int i) {
while(antiuniverseProp.size() < i) {
antiuniverseProp.add(UUID.randomUUID());
}
return antiuniverseProp.get(i);
}
#Override
public int compareTo(Test o) {
if(this == o)
return 0;
int temp = System.identityHashCode(this) - System.identityHashCode(o);
if(temp != 0)
return temp;
//If the universe hates you
temp = this.antiCollisionProp.compareTo(o.antiCollisionProp);
if(temp != 0)
return temp;
//If the universe is activly out to get you
temp = System.identityHashCode(this.antiCollisionProp) - System.identityHashCode(o.antiCollisionProp);;
if(temp != 0)
return temp;
for(int i = 0; i < Integer.MAX_VALUE; i++) {
UUID id1 = this.getParanoiaLevelId(i);
UUID id2 = o.getParanoiaLevelId(i);
temp = id1.compareTo(id2);
if(temp != 0)
return temp;
temp = System.identityHashCode(id1) - System.identityHashCode(id2);;
if(temp != 0)
return temp;
}
// If you reach this point, I have no idea what you did to deserve this
throw new IllegalStateException("RAGNAROK HAS COME! THE MIDGARD SERPENT AWAKENS!");
}
}
Assuming that with two objects with same name, if equals() returns false then compareTo() should not return 0. If this is what you want to do then following can help:
Override hashcode() and make sure it doesn't rely solely on name
Implement compareTo() as follows:
public void compareTo(MyObject object) {
this.equals(object) ? this.hashcode() - object.hashcode() : this.getName().compareTo(object.getName());
}
You are having unique objects, but as Eran said you may need an extra counter/rehash code for any collisions.
private static Set<Pair<C, C> collisions = ...;
#Override
public boolean equals(C other) {
return this == other;
}
#Override
public int compareTo(C other) {
...
if (this == other) {
return 0
}
if (super.equals(other)) {
// Some stable order would be fine:
// return either -1 or 1
if (collisions.contains(new Pair(other, this)) {
return 1;
} else if (!collisions.contains(new Pair(this, other)) {
collisions.add(new Par(this, other));
}
return 1;
}
...
}
So go with the answer of Eran or put the requirement as such in question.
One might consider the overhead of non-identical 0 comparisons neglectable.
One might look into ideal hash functions, if at some point of time no longer instances are created. This implies you have a collection of all instances.
There are times (although rare) when it is necessary to implement an identity-based compareTo override. In my case, I was implementing java.util.concurrent.Delayed.
Since the JDK also implements this class, I thought I would share the JDK's solution, which uses an atomically incrementing sequence number. Here is a snippet from ScheduledThreadPoolExecutor (slightly modified for clarity):
/**
* Sequence number to break scheduling ties, and in turn to
* guarantee FIFO order among tied entries.
*/
private static final AtomicLong sequencer = new AtomicLong();
private class ScheduledFutureTask<V>
extends FutureTask<V> implements RunnableScheduledFuture<V> {
/** Sequence number to break ties FIFO */
private final long sequenceNumber = sequencer.getAndIncrement();
}
If the other fields used in compareTo are exhausted, this sequenceNumber value is used to break ties. The range of a 64bit integer (long) is sufficiently large to count on this.
So this was going to be my question, but I actually figured out the problem while I was writing it. Perhaps this will be useful for others (I will remove the question if it's a duplicate or is deemed inappropriate for this site). I know of two possible solutions to my problem, but perhaps someone will come up with a better one than I thought of.
I don't understand why TreeSet isn't removing the first element here. The size of the my TreeSet is supposed to stay bounded, but appears to grow without bound.
Here is what I believe to be the relevant code:
This code resides inside of a double for loop. NUM_GROUPs is a static final int which is set to 100. newGroups is a TreeSet<TeamGroup> object which is initialized (with no elements) before the double for loop (the variables group and team are from the two for-each loops).
final TeamGroup newGroup = new TeamGroup(group, team);
newGroups.add(newGroup);
System.err.println("size of newGroups: " + newGroups.size());
if (newGroups.size() > NUM_GROUPS) {
System.err.println("removing first from newGroups");
newGroups.remove(newGroups.first());
System.err.println("new size of newGroups: "
+ newGroups.size());
}
I included my debugging statements to show that the problem really does appear to happen. I get the following types of output:
size of newGroups: 44011
removing first from newGroups
new size of newGroups: 44011
You see that although the if statement is clearly being entered, the size of the TreeSet<TeamGroup> teamGroups isn't being decremented. It would seem to me that the only way for this to happen is if the remove call doesn't remove anything--but how can it not remove something from a call to first() which should definitely be an element in the TreeSet?
Here is the compareTo method in my TeamGroup class (score is an int which could very reasonably be the same for many different TeamGroup objects hence why I use the R_ID field as a tie-breaker):
public int compareTo(TeamGroup o) {
// sorts low to high so that when you pop off of the TreeSet object, the
// lowest value gets popped off (and keeps the highest values).
if (o.score == this.score)
return this.R_ID - o.R_ID;
return this.score - o.score;
}
Here is the equals method for my TeamGroup class:
#Override
public boolean equals(final Object o) {
return this.R_ID == ((TeamGroup) o).R_ID;
}
...I'm not worried about a ClassCastException here because this is specifically pertaining to my above problem where I never try to compare a TeamGroup object with anything but another TeamGroup object--and this is definitely not the problem (at least not a ClassCastException problem).
The R_ID's are supposed to be unique and I guarantee this by the following:
private static final double WIDTH = (double) Integer.MAX_VALUE
- (double) Integer.MIN_VALUE;
private static final Map<Integer, Integer> MAPPED_IDS =
new HashMap<Integer, Integer>(50000);
...
public final int R_ID = TeamGroup.getNewID();
...
private static int getNewID() {
int randID = randID();
while (MAPPED_IDS.get(randID) != null) {
randID = randID();
}
MAPPED_IDS.put(randID, randID);
return randID;
}
private static int randID() {
return (int) (Integer.MIN_VALUE + Math.random() * WIDTH);
}
The problem is here:
return this.R_ID - o.R_ID;
It should be:
return Integer.compare(this.R_ID, o.R_ID);
Taking the difference of two int or Integer values works if the values are both guaranteed to be non-negative. However, in your example, you are using ID values across the entire range of int / Integer and that means that the subtraction can lead to overflow ... and an incorrect result for compareTo.
The incorrect implementation leads to situations where the compareTo method is not reflexive; i.e. integers I1, I2 and I3 where the compareTo method says that I1 < I2 and I2 < I3, but also I3 < I1. When you plug this into TreeSet, elements get inserted into the tree in the wrong place, and strange behaviours happen. Precisely what is happening is hard to predict - it will depend on the objects that are inserted, and the order they are inserted.
TreeSet.first() should definitely return an object which belongs to the set, right?
Probably ...
So then why can it not remove this object?
Probably because it can't find it ... because of the broken compareTo.
To understand what exactly is going on, you would been to single step through the TreeSet code, etcetera.
I read that the rule for the return value of these methods is that for obj1.compareTo(obj2) for example, if obj2 is under obj1 in the hierarchy, the return value is negative and if it's on top of obj1, then it's positive (and if it's equal then it's 0). However, in my class I saw examples where Math.signum was used in order to get -1 (for negative) and 1 (for positive) in the compareTo method.
Is there any reason for that?
EDIT:
Here is the code I meant:
Comparator comp = new Comparator() {
public int compare(Object obj1, Object obj2) {
Book book1 = (Book) obj1;
Book book2 = (Book) obj2;
int order = book1.getAuthor().compareTo(book2.getAuthor());
if (order == 0) {
order = (int) Math.signum(book1.getPrice() - book2.getPrice());
}
return order;
};
Is there any reason for using Math.signum
Yes there is.
order = (int) Math.signum(book1.getPrice() - book2.getPrice());
Suppose you have replace the above line with this
order = (int)(book1.getPrice() - book2.getPrice());
Now let us assume
book1.getPrice() returns 10.50
book2.getPrice() returns 10.40
If you do not use signum you will never have any compile time or run time error but value of order will be 0. This implies that book1 is equals to book2 which is logically false.
But if you use signum value of order will be 1 which implies book1 > book2.
But it must be mentioned that you should never make any assumption about compare function returning value between 1 and -1.
You can read official document for comparator http://docs.oracle.com/javase/7/docs/api/java/util/Comparator.html.
Any negative number will do to show that a < b. And any positive number will show that a > b. -1 and 1 serve that purpose just fine. There's no sense of being "more less than" or "more greater than"; they are binary attributes. The reason that any negative (or positive) value is permitted is probably historical; for integers it's common to implement the comparator by simple subtraction.
No.
PS: Frequent error in implementation is to use subtraction
public int compareTo(Object o) {
OurClass other = (OurClass)o; //Skip type check
return this.intField - other.intField;
}
It is wrong because if you call new OurClass(Integer.MIN_VALUE).compareTo(new OurClass(Integer.MAX_VALUE)) you get overflow. Probably Math.abs is attempt (failed) to deal with this problem.
The only reason I can see is that if you want to compare two ints for example (a and b), and you write
return a - b;
it might overflow. If you convert them to doubles and use (int)Math.signum( (double)a - (double)b ), you will definitely avoid that. But there are simpler ways of achieving the same effect, Integer.compare( a, b) for example.
Am somewhat confused with Java's compareTo() and Collections.sort() behavior.
I am supposed to sort a column in ascending order using compareTo() & Collections.sort().
My criteria is (if the same number occurs than please sort the next available column).
(1) Document Number
(2) Posting Date
(3) Transaction Date
(4) Transaction Reference Number Comparison
Here's the code (which is executed in a calling method) that implements the Collection.sort() method:
public int compareTo(CreditCardTransactionDetail t) {
int comparison = 0;
int documentNumberComparison = this.getDocumentNumber().compareTo(t.getDocumentNumber());
if (documentNumberComparison != 0) {
comparison = documentNumberComparison;
}
else {
int postingDateComparison = this.getTransactionPostingDate().compareTo(t.getTransactionPostingDate());
if (postingDateComparison != 0) {
comparison = postingDateComparison;
}
else {
int transactionDateComparison = this.getTransactionDate().compareTo(t.getTransactionDate());
if (transactionDateComparison != 0) {
comparison = transactionDateComparison;
}
else {
int transactionRefNumberComparison = this.getTransactionReferenceNumber().compareTo(t.getTransactionReferenceNumber());
LOG.info("\n\n\t\ttransactionRefNumberComparison = " + transactionRefNumberComparison + "\n\n");
if (transactionRefNumberComparison != 0) {
comparison = transactionRefNumberComparison;
}
}
}
return comparison;
}
Question(s):
(1) Am I doing the right thing? When a comparison = 0, it returns as -2. Is this correct behavior because I always thought it to be between -1,0,1.
(2) Should I be using the comparator?
Happy programming...
To address your specific questions:
Yes, that looks fine. The result does not have to be -1, 0 or 1. Your code could be slightly less verbose, though, and just return as soon as it finds a result without using the comparison variable at all.
If you're implementing Comparable, no need to deal with a Comparator. It's for when you need to compare something that isn't Comparable or need to compare in a different way.
Guava's ComparisonChain class makes a compareTo method like this incredibly easy:
public int compareTo(CreditCardTransactionDetail o) {
return ComparisonChain.start()
.compare(getDocumentNumber(), o.getDocumentNumber())
.compare(getTransactionPostingDate(), o.getTransactionPostingDate())
.compare(getTransactionDate(), o.getTransactionDate())
.compare(getTransactionReferenceNumber(), o.getTransactionReferenceNumber())
.result();
}
Answer for (1): It's correct. see javadoc of Comparator.compare(T, T): "a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second."
Or use Google Guava which encapsulates Comparator for easier and powerful usage:
//Write 4 Comparators for each comparable field
Ordering ordering = Ordering.from(comparatorDocumentNumber)
.compound(comparatorTransactionPostingDate)
.compound(comparatorTransactionDate)
.compound(comparatorTransactionReferenceNumber);
Collections.sort(list, ordering);
It decouples each Comparator, it's easy to change/ add/ remove fields order.
EDIT: see ColinD's lighter solution.
Your compareTo is reasonable enough. compareTo can return values other than -1,0,1. Just negative, 0 and positive.
You should be using a comparator.
According to the Comparable Documentation, compareTo():
Returns a negative integer, zero, or a positive integer as this object
is less than, equal to, or greater than the specified object.
So -2 is a valid result.
That's a matter of preference, really. Personally I prefer using a Comparator, but compareTo() works just as well. In either case, your code would look pretty much the same.