How do I write a compareTo method which compares objects? - java

I am learning about arrays, and basically I have an array that collects a last name, first name, and score.
I need to write a compareTo method that will compare the last name and then the first name so the list could be sorted alphabetically starting with the last names, and then if two people have the same last name then it will sort the first name.
I'm confused, because all of the information in my book is comparing numbers, not objects and Strings.
Here is what I have coded so far. I know it's wrong but it at least explains what I think I'm doing:
public int compare(Object obj) // creating a method to compare
{
Student s = (Student) obj; // creating a student object
// I guess here I'm telling it to compare the last names?
int studentCompare = this.lastName.compareTo(s.getLastName());
if (studentCompare != 0)
return studentCompare;
else
{
if (this.getLastName() < s.getLastName())
return - 1;
if (this.getLastName() > s.getLastName())
return 1;
}
return 0;
}
I know the < and > symbols are wrong, but like I said my book only shows you how to use the compareTo.

This is the right way to compare strings:
int studentCompare = this.lastName.compareTo(s.getLastName());
This won't even compile:
if (this.getLastName() < s.getLastName())
Use
if (this.getLastName().compareTo(s.getLastName()) < 0) instead.
So to compare fist/last name order you need:
int d = getFirstName().compareTo(s.getFirstName());
if (d == 0)
d = getLastName().compareTo(s.getLastName());
return d;

The compareTo method is described as follows:
Compares this object with the specified object for order. Returns a
negative integer, zero, or a positive integer as this object is less
than, equal to, or greater than the specified object.
Let's say we would like to compare Jedis by their age:
class Jedi implements Comparable<Jedi> {
private final String name;
private final int age;
//...
}
Then if our Jedi is older than the provided one, you must return a positive, if they are the same age, you return 0, and if our Jedi is younger you return a negative.
public int compareTo(Jedi jedi){
return this.age > jedi.age ? 1 : this.age < jedi.age ? -1 : 0;
}
By implementing the compareTo method (coming from the Comparable interface) your are defining what is called a natural order. All sorting methods in JDK will use this ordering by default.
There are ocassions in which you may want to base your comparision in other objects, and not on a primitive type. For instance, copare Jedis based on their names. In this case, if the objects being compared already implement Comparable then you can do the comparison using its compareTo method.
public int compareTo(Jedi jedi){
return this.name.compareTo(jedi.getName());
}
It would be simpler in this case.
Now, if you inted to use both name and age as the comparison criteria then you have to decide your oder of comparison, what has precedence. For instance, if two Jedis are named the same, then you can use their age to decide which goes first and which goes second.
public int compareTo(Jedi jedi){
int result = this.name.compareTo(jedi.getName());
if(result == 0){
result = this.age > jedi.age ? 1 : this.age < jedi.age ? -1 : 0;
}
return result;
}
If you had an array of Jedis
Jedi[] jediAcademy = {new Jedi("Obiwan",80), new Jedi("Anakin", 30), ..}
All you have to do is to ask to the class java.util.Arrays to use its sort method.
Arrays.sort(jediAcademy);
This Arrays.sort method will use your compareTo method to sort the objects one by one.

Listen to #milkplusvellocet, I'd recommend you to implement the Comparable interface to your class as well.
Just contributing to the answers of others:
String.compareTo() will tell you how different a string is from another.
e.g. System.out.println( "Test".compareTo("Tesu") ); will print -1
and System.out.println( "Test".compareTo("Tesa") ); will print 19
and nerdy and geeky one-line solution to this task would be:
return this.lastName.equals(s.getLastName()) ? this.lastName.compareTo(s.getLastName()) : this.firstName.compareTo(s.getFirstName());
Explanation:
this.lastName.equals(s.getLastName()) checks whether lastnames are the same or not
this.lastName.compareTo(s.getLastName()) if yes, then returns comparison of last name.
this.firstName.compareTo(s.getFirstName()) if not, returns the comparison of first name.

You're almost all the way there.
Your first few lines, comparing the last name, are right on track. The compareTo() method on string will return a negative number for a string in alphabetical order before, and a positive number for one in alphabetical order after.
Now, you just need to do the same thing for your first name and score.
In other words, if Last Name 1 == Last Name 2, go on a check your first name next. If the first name is the same, check your score next. (Think about nesting your if/then blocks.)

Consider using the Comparator interface described here which uses generics so you can avoid casting Object to Student.
As Eugene Retunsky said, your first part is the correct way to compare Strings. Also if the lastNames are equal I think you meant to compare firstNames, in which case just use compareTo in the same way.

if (s.compareTo(t) > 0) will compare string s to string t and return the int value you want.
public int Compare(Object obj) // creating a method to compare {
Student s = (Student) obj; //creating a student object
// compare last names
return this.lastName.compareTo(s.getLastName());
}
Now just test for a positive negative return from the method as you would have normally.
Cheers

A String is an object in Java.
you could compare like so,
if(this.lastName.compareTo(s.getLastName() == 0)//last names are the same

I wouldn't have an Object type parameter, no point in casting it to Student if we know it will always be type Student.
As for an explanation, "result == 0" will only occur when the last names are identical, at which point we compare the first names and return that value instead.
public int Compare(Object obj)
{
Student student = (Student) obj;
int result = this.getLastName().compareTo( student.getLastName() );
if ( result == 0 )
{
result = this.getFirstName().compareTo( student.getFirstName() );
}
return result;
}

If you using compare To method of the Comparable interface in any class.
This can be used to arrange the string in Lexicographically.
public class Student() implements Comparable<Student>{
public int compareTo(Object obj){
if(this==obj){
return 0;
}
if(obj!=null){
String objName = ((Student)obj).getName();
return this.name.comapreTo.(objName);
}
}

Related

How to implement compareTo method in Java and what does it mean [duplicate]

This question already has answers here:
What does compareTo method return in java
(1 answer)
What do the return values of Comparable.compareTo mean in Java?
(7 answers)
Closed 10 months ago.
I'm very new to java. I know that the Java Comparable interface has only one method, which has an int return type. However, I'm not sure what I am actually doing when I want to override the compareTo method. It's important to me to understand programming and not just memorizing it.
For example, in the following code we have an int variable "spaceshipClassComparison", which is equal to "this.spaceshipClass.compareTo(other.spaceshipClass)". I know that that this method returns the values of 1,0,-1, but I don't understand what we are doing when we call compareTo() again for this.spaceship but this time with a new object and how does it turn to integer?
public class Spaceship implements Comparable<Spaceship> {
private String spaceshipClass = null;
private String registrationNo = null;
public Spaceship(String spaceshipClass, String registrationNo) {
this.spaceshipClass = spaceshipClass;
this.registrationNo = registrationNo;
}
#Override
public int compareTo(Spaceship other) {
int spaceshipClassComparison =
this.spaceshipClass.compareTo(other.spaceshipClass);
if(spaceshipClassComparison != 0) {
return spaceshipClassComparison;
}
return this.registrationNo.compareTo(other.registrationNo);
}
}
In cases when we compare obvious things like numbers, we don't need a custom compareTo method. So for example, if we want to know if 2 is greater than 1 we just use the appropriate operator: 2 > 1
However when we're dealing with things that aren't obviously comparable, like objects and spaceships (in your example), we need to explain to the compiler what criterion is used to say that one spaceship is "bigger" than another.
This is where writing a custom (#Override) compareTo method comes in. By default the compareTo method returns either 1, 0 or -1. There is no inherent meaning to these numbers, in fact they can be any numbers you like (as long as they are different). Their purpose is to match three cases when two things are compared:
thing 1 is greater than thing 2
thing 1 is equal to thing 2
thing 1 is less than thing 2
The compareTo method in your example specifies that when we compare two spaceships we are going to use the string value of their spaceshipClass. Furthermore, the compareTo method in your example uses the default implementation of comparing strings: This means that the strings are compared lexicographically (you can think of it as alphabetical).
The compareTo method is used to establish a natural ordering among the elements of a class. Here is a link which expands the concept of natural ordering.
https://docs.oracle.com/javase/tutorial/collections/interfaces/order.html
To get back to your example, I think you're having troubles in understanding the idea of it because you're comparing two String objects which are in turn invoking their compareTo implementation which does other operations under the hood. Basically, they just perform an alphabetical comparison to establish if the current string is lower, equal or greater than the given parameter.
Let's make a simpler example with another class, though. Let's say we have a Person class with just name, last name and age, and that we want to establish its natural ordering by comparing people by their age.
class Person implements Comparable<Person> {
private String name, lastName;
private int age;
public Person(String name, String lastName, int age) {
this.name = name;
this.lastName = lastName;
this.age = age;
}
public String getName() {
return name;
}
public String getLastName() {
return lastName;
}
public int getAge() {
return age;
}
#Override
public int compareTo(Person otherPerson) {
//If the current person's age is lower than otherPerson's age than we return -1, as the current person is "lower" than the other (ordering-wise)
if (age < otherPerson.getAge()) {
return -1;
}
//If the current person's age is equal to the otherPerson's age than we return 0, as the current person is "equal" to the other (ordering-wise)
if (age == otherPerson.getAge()) {
return 0;
}
//Here, there are no other cases left so the current person's age is greater than the otherPerson's age, so we return 1.
return 1;
}
public static void main(String[] args) {
Person p1 = new Person("Matt", "O'Brien", 20);
Person p2 = new Person("Matt", "O'Brien", 25);
Person p3 = new Person("George", "Lucas", 25);
System.out.println(p1.compareTo(p2)); //Prints -1 because 20 is lower than 25
System.out.println(p2.compareTo(p1)); //Prints 1 because 25 is greater than 20
System.out.println(p2.compareTo(p2)); //Prints 0 because 20 is equal to 20 even though we're comparing different people, because in the comprateTo we're only confronting the age
}
}
What you're doing in your compareTo example is basically establishing a natural ordering between Spaceship elements, first by their class and then by their number. In fact, if the alphabetical order of their class is different from 0, then this value is immediately returned. Otherwise, if their class is the same, another comparison is performed (by registration number) and its value returned.
public int compareTo(Spaceship other) {
//Saving the comparison value between the spaceships' class
int spaceshipClassComparison =
this.spaceshipClass.compareTo(other.spaceshipClass);
//If they are already different by class we return the ordering value (no point in comparing also the number)
if(spaceshipClassComparison != 0) {
return spaceshipClassComparison;
}
//If the spaceships have the same class then we return the comparison between their registration number
return this.registrationNo.compareTo(other.registrationNo);
}

dosent understand how sort works

I'm trying to understand how the comparable compareTo method sort the input. Below is the compareTo method implemented:
#Override
public int compareTo(Name n) {
int lastCmp = lastName.compareTo(n.lastName);
return (lastCmp != 0 ? lastCmp : firstName.compareTo(n.firstName));
}
The input array to the Collections.sort method is:
Name nameArray[] = {
new Name("John","Smith"),
new Name("Karl","Ng"),
new Name("Jeff","Smith"),
new Name("Tom","Rich")
};
List<Name> names = Arrays.asList(nameArray);
Collections.sort(names);
I don't understand what values are taken in to the compareTo method. (n.lastName and lastname) in which order?
The Collections.sort() method uses different algorithms to sort depending on the collection length and type (I think...)
The compareTo method should return negatives, 0, or positives if the object is less-than, equal or greater-than respectively.
the lastName variable refers to the Last Name in the Name class, composed of firstName:String and lastName:String.
The method first compares the lastName (object of type String) to the lastName of the passed object "n" (of type Name). If it is different than 0 (meaning not equal) return that value. If it is equal then compare the first name and return that.
So it is only comparing the two strings (firstName and lastName of the Name objects).

Sorting an array in java with certain conditions

I have a class called Athlete which is a sublass of Human. In the class Human I implement the interface comparable and use the method compareTo in order to compare the ages of different athletes. In the athletes class I have an extra field called year which corresponds to the year the athlete started competing. In my main method in my program I have an arraylist that I add both Athletes and Humans. I would like to so that if an athlete is of the same age to sort according to the year the athlete started competing. I use instanceof to check in my class human if the instance is the object is of type Athlete but after that I don't know how to get it to work.
public int compareTo(Human other)
{
if(this instanceof Athlete && other instanceof Athlete && this.age == other.age){
return ;
}
return this.age - other.age;
}
}
One possible solution is to add a compareTo method in the Athlete class also, something in the lines of (needs rewriting as I haven't been working on Java since a long time ago):
public int compareTo(Athlete other){
int result = super.compareTo((Human)other);
if(result == 0){
return this.year - other.year;
}
return result;
}
As a code review, I'd say that the complete code should be something like the following:
Human.java
public int compareTo(Human other){
return age - other.age;
}
Athlete.java
#Override
public int compareTo(Human other){
if(other instanceof Athlete){
return compareTo((Athlete)other);
}
return super.compareTo(other);
}
public int compareTo(Athlete other){
int result = super.compareTo((Human)other);
if(result == 0){
return this.year - other.year;
}
return result;
}
Use polymorphism, instead of the operator instanceof.
That is: overload the compareTo method in the Athlete class.
public int compareTo(Athlete other) {
//This method will be invoked if and only if you compare an athlete with another athlete
}
Also, consider that the equals method result should be consistent with the compareTo method results.
Using your example, you could just compare the year as well:
public int compareTo(Human other)
{
if(this instanceof Athlete && other instanceof Athlete && this.age == other.age){
String tYear = ((Athlete)this).getYear();
String oYear = ((Athlete)other).getYear();
int tYearInt = 0;
int oYearInt = 0;
try {
tYearInt = Integer.parseInt(tYear);
oYearInt = Integer.parseInt(oYear);
} catch (Exception e){
e.printStackTrace();
}
return tYearInt - oYearInt;
}
return this.age - other.age;
}
However, having said that, please consider #Andres answer, anytime you use instanceof, you should question whether your design is wrong.
Like Andres said, use polymorphism. Here is how to do that:
First of all, this instanceof Athlete in the Human class is not good style, because from the perspective of the Human class, Athlete is a subclass and referencing subclasses can lead to problems in certain cases. Instead, put another compareTo() method into the Athlete class. If Athlete.compareTo() gets called, you already know that this is of type Athlete and if you want to compare the year field, you only have to check if other instanceof Athlete, which is ok, because now we are in the perspective of the Athlete class and from here, Athlete is not a subclass.
That said, in the Athlete class, use this:
public int compareTo(Human other) {
int humanComp = super.compareTo(other);
if (humanComp != 0){
return humanComp;
}
if (other instanceof Athlete) {
return ((Athlete)other).year - this.year;
}
return 0;
}
This solution first uses Human.compareTo() (called with super.compareTo(other)) to check if the Human class already knows how to order our instances this and other. If not, i.e. if this call returns 0, we have to go on with comparing more details, in our case the year field.
Because we used Human.compareTo(), we have to make sure it exists in the Human class and that it works properly:
public int compareTo(Human other) {
return this.age - other.age;
}
This one simply compares by age, because that's the only field in the Human class we know we can use for comparison.
The documentation for compareTo says:
Finally, the implementor must ensure that x.compareTo(y)==0 implies that sgn(x.compareTo(z)) == sgn(y.compareTo(z)), for all z.
Your proposed method does not meet this requirement. For example, suppose
x = Athlete, age = 35, start date = 2000
y = Human, age = 35
z = Athlete, age = 35, start date = 2001
In this example
x.compareTo(y) == 0 // y is not an athlete
x.compareTo(z) < 0 // Both athletes, different start dates.
y.compareTo(z) == 0 // y is not an athlete
If you do not obey the contract for Comparable, the behaviour of Arrays.sort or Collections.sort is unspecified. Ideally you'd get an exception, but the trouble is these sorting methods use different algorithms depending on the size of the array or list, and you are more likely to get an exception thrown for an incorrect compareTo method if the input array or list has a large number of elements. This means that you should test your compareTo method very carefully using long randomly generated arrays, otherwise you may have subtle, hard-to-detect bugs in your code.
A correct compareTo method looks something like this:
public int compareTo(Human other) {
int result = Integer.compare(age, other.age);
if (result != 0)
return result;
if (!(this instanceof Athlete))
return other instanceof Athlete ? -1 : 0;
return other instanceof Athlete
? Long.compare(((Athlete) this).startDate(), ((Athlete) other).startDate())
: 1;
}
This method sorts first by age. If two humans have the same age they are sorted first by type (with athletes coming first). Athletes with the same age are sorted by start date. Non-athletes with the same age do not appear in any particular order.
Finally, note that it is generally better to use polymorphism rather than instanceof. The problem here is that Human implements Comparable<Human>. Therefore the compareTo method must accept a Human, not an Athlete. Even if you override compareTo in Athlete, the argument must be a Human, and you'd have to use instanceof to check the type of the argument anyway (as in #GentianKasa's answer) or write a completely separate method compareToAthlete(Athlete athlete) and do the following in Athlete
#Override
public int compareTo(Human human) {
return -human.compareToAthlete(this); // Note the minus sign!
}
compareToAthlete would need two versions as well. While this works, it means that the logic of the compareTo method is spread over four methods, making it harder to reason about its correctness. In this case, I'd hold my nose and use instanceof.

Custom hashCode when using part of a List for comparision

I am trying to write a custom hashCode fn, but I am not able to figure out the correct way to do that.
public class Person {
String name;
List<String> attributes;
#Override
public boolean equals(Object o) {
// Persons are equal if name is equal & if >= 2 of attributes are equal
// This I have implemented
}
#Override
public int hashCode() {
final int PRIME = 59;
int result = 1;
result = (result*PRIME) + (this.name == null ? 0 : this.name.hashCode());
//Not sure what to do here to account for attributes
return result;
}
}
I want the hashCode fn to be such that:
"If object1 and object2 are equal according to their equals() method, they must also have the same hash code"
Not sure how to do that?
As Oli points out in the comments, you cannot solve this by implementing equals() and relying on a Set to de-duplicate for you. Weird things could happen.
Thus you must resort to coding this yourself. Add the first item from your list into your new de-duplicated list. Then for each remaining item in your original list, compare it with those already present in your de-duplicated list and only add it if it passes your non-duplicate test.
Easiest way to fulfill the contract of the equals/hashcCode methods is to return a constant:
#Override
public int hashCode() {return 13;}
Otherwise your solution with a hash code based only on name will work.

Why does my TreeSet not add anything beyond the first element?

I have several arrays in the form:
private static String[] patientNames = { "John Lennon", "Paul McCartney", "George Harrison", "Ringo Starr" };
Then I make a TreeSet like this:
TreeSet<Patient> patTreeSet = new TreeSet<Patient>();
Where Patient is a different class that makes "Patient" objects.
Then I loop through each element in my arrays to create several patients and add them to my patTreeSet like this:
for(int i = 0; i< patientNames.length; i++){
Date dob = date.getDate("MM/dd/yyyy", patientBirthDates[i]);
Patient p = new PatientImpl(patientNames[i], patientSSN[i], dob);
patTreeSet.add(p);
}
But when I go to check my patTreeSet.size() it only returns "1" - why is this?
I know my objects are working well because when I try to do the same thing but with ArrayList instead, everything works fine. So I'm guessing I'm using the TreeSet wrong.
If it helps, Patient has a method called getFirstName(), and when I try to do the following:
Iterator<Patient> patItr = patTreeSet.iterator();
while(patItr.hasNext()){
System.out.println(patItr.next().getFirstName());
}
Then only "John" prints, which obviously shouldn't be the case... So, am I totally misusing the TreeSet?
Thanks in advance for any help!
EDIT below
================PatientImpl Class====================
public class PatientImpl implements Patient, Comparable{
Calendar cal = new GregorianCalendar();
private String firstName;
private String lastName;
private String SSN;
private Date dob;
private int age;
private int thisID;
public static int ID = 0;
public PatientImpl(String fullName, String SSN, Date dob){
String[] name = fullName.split(" ");
firstName = name[0];
lastName = name[1];
this.SSN = SSN;
this.dob = dob;
thisID = ID += 1;
}
#Override
public boolean equals(Object p) {
//for some reason casting here and reassigning the value of p doesn't take care of the need to cast in the if statement...
p = (PatientImpl) p;
Boolean equal = false;
//make sure p is a patient before we even compare anything
if (p instanceof Patient) {
Patient temp = (Patient) p;
if (this.firstName.equalsIgnoreCase(temp.getFirstName())) {
if (this.lastName.equalsIgnoreCase(temp.getLastName())) {
if (this.SSN.equalsIgnoreCase(temp.getSSN())) {
if(this.dob.toString().equalsIgnoreCase(((PatientImpl) p).getDOB().toString())){
if(this.getID() == temp.getID()){
equal = true;
}
}
}
}
}
}
return equal;
}
and then all the getters are below, as well as the compareTo() method from the Comparable interface
If you put your objects in a TreeSet, you need to either provide an implementation of the Comparator interface in the constructor, or you need your objects to be of a class that implements Comparable.
You said you implement compareTo from the Comparable interface, but in your comment you say that you didn't, so am I correct in assuming that you just return 0; in the compareTo method? That would explain your problem, because TreeSet would then think that all your objects are 'the same' based on the compareTo method result.
Basically, in a TreeSet, your objects are maintained in a sorted order, and the sorting is determined by the outcome of the Comparable/Comparator method. This is used to quickly find duplicates in a TreeSet and has the added benefit that when you iterate over the TreeSet, you get the results in sorted order.
The Javadoc of TreeSet says:
Note that the ordering maintained by a set (whether or not an explicit
comparator is provided) must be consistent with equals if it is
to correctly implement the Set interface.
The easiest way to achieve that is to let your equals method call the compareTo method and check if the result is 0.
Given your PatientImpl class, I assume that you would want to sort patients first by their last name, then by their first name, and then by the rest of the fields in the class.
You could implement a compareTo method like this:
#Override
public int compareTo(Object o) {
if (!(o instanceof Patient))
return -1;
Patient temp = (Patient) o;
int r = this.lastName.compareToIgnoreCase(temp.getLastName());
if (r == 0)
r = this.firstName.compareToIgnoreCase(temp.getFirstName());
if (r == 0)
r = this.SSN.compareToIgnoreCase(temp.getSSN());
if (r == 0)
r = this.dob.toString().compareToIgnoreCase(temp.getDOB().toString());
if (r == 0)
r = Integer.compare(this.getID(), temp.getID());
return r;
}
I believe that would solve the problem you described.
I would advise you to read up (Javadoc or books) on TreeSet and HashSet and the importance of the equals, compareTo and hashCode methods.
If you want to put your objects in a Set or a Map, you need to know about these to implement that correctly.
Note
I based this compareTo method on your equals method.
You were comparing the date-of-birth by first calling toString. That's not a very good way of doing that - you can use the equals method in java.util.Date directly. In a compareTo method the problem gets worse because dates do not sort correctly when you sort them alphabetically.
java.util.Date also implements Comparable so you can replace that comparison in the method with:
if (r == 0)
r = this.dob.compareTo(temp.getDOB());
In addition, if any of the fields could be null, you need to check for that as well.

Categories