I'm working on a project where I need to be able to sort an ArrayList of Car objects by price. In my Car class, I have
public class Car implements Comparable
and in the body of the code is the compareTo method:
public int compareTo(Object o)
{
Car rhs = (Car)o;
if (price > rhs.price)
return 1;
else if (price < rhs.price)
return -1;
else
return 0;
}
I just don't understand how to implement this method to sort by price- what does carList need to be compared to? I know this isn't correct but so far this is the sorting method.
public void sortByPrice()
{
Collections.sort(carList.compareTo(o));
}
Two problems: one syntatical and one conceptual.
The first issue is that while your compareTo is technically correct, you want to type-bind it to Car instead of Object.
public class Car implements Comparable<Car>
Inside of your compareTo method you'd then substitute Object for Car. You would also want to check for null.
The second is that sortByPrice sounds specific, but since compareTo is comparing based on price, that's somewhat okay.
All you'd need to do is call Collections.sort on the actual collection:
Collections.sort(carList);
Normally, one sorts a collection using
Collections.sort(collection)
while collection has to implement Comparable and sort uses the compareTo method to sort collection.
Your Car class must implement Comparable<Car>. Then your compareTo method will have signature:
public int compareTo(Car other) {}
As per the documentation, this method should:
Returns a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object.
Then given a List<Car>, say list, you can call Collections.sort(list).
You're almost done! Only call to Collections.sort(carList); and that will by itself, use the overridden compareTo.
Actually, when you're not implementing compareTo, you'll have the very basic implementation, and calling Collections.sort(..) will use the basic implementation, which is comparing pointers in that case.
Related
It is generally said that comparator is used to have multiple sorting sequences of collection of objects while comparable is used to have single sorting sequence.
What is the use of comparator interface in java when it is possible to have multiple sorting sequences using comparable interface?
import java.util.*;
enum CompareValue {RollNo, Marks;}
class Student implements Comparable<Student> {
public int marks;
public int rollNo;
public static CompareValue comparator = CompareValue.RollNo;
Student (int marks, int rollNo) {
this.marks = marks;
this.rollNo = rollNo;
}
public int compareTo(Student s) {
switch (comparator) {
case RollNo:
return this.rollNo - s.rollNo;
case Marks:
return this.marks - s.marks;
}
return 0;
}
}
public class Test
{
public static void main (String[] args)
{
Student s1 = new Student(59, 103);
Student s2 = new Student(87, 102);
Student s3 = new Student(78, 101);
Student students[] = {s1, s2, s3};
Arrays.sort(students);
System.out.println("Student list sorted by rollno");
for (Student s:students) {
System.out.println(s.rollNo + " - " + s.marks);
}
Student.comparator = CompareValue.Marks;
System.out.println("Student list sorted by marks");
Arrays.sort(students);
for (Student s:students) {
System.out.println(s.rollNo + " - " + s.marks);
}
}
}
When your compareTo method has different behaviors based on the value of some static variable, you are basically introducing a global setting that controls the natural ordering of the Student class.
This could be confusing and counter intuitive to users of your class.
Besides, it makes the implementation of compareTo awkward, especially if you have more than two implementations, and each implementation depends on multiple instance variables.
Comparator is a much more suitable interface to supply multiple different comparisons for instances of the same class, each implementation having its own compare() logic.
When you have objects that do not implement comparable, but you would like to sort a collection consisting them, you would either have to extend them just to sort your collection or provide a comparator that compares them even though they are not comparable.
Or you might want to compare sort those objects in a different manner then their natural sort.
Imagine such an example.
String is an object that is comparable. Imagine you want to sort a collection of strings based on their hashCode instead of the string natural order. How would you do it without creating a comparator?
What you have shown there is indeed multiple sort orders using Comparable, but don't you think it's too much boiler plate code? Let's say if you have added a new field to the class called name, and now you want to sort by name. You'd have to:
add a new case to the enum
add a new case to the compareTo.
Another disadvantage of using the approach you showed is that it is not necessarily clear what this means:
Arrays.sort(student);
You would have to look through your code and check what value you have set the comparator.
Also, if I were using your class and I want to sort by something else, I would have to create a Comparator anyway, because I can't edit your class.
But if you use Comparator, you solve all of these problems:
Arrays.sort(students, Comparator.comparing(Student::getName));
Therefore, Comparable is only useful when there is one natural order, like dates and times for example.
If we look at the Comparable and Comparator interfaces and what they mean, everything will be clear.
Comparable:
This is an internal property of a JAVA class i.e. it assumes that whenever one uses the internal compareTo() method, one is using it for the specified object.
public int compareTo(T o);
Therefore, in implementation of this method we use this which is the current object and compare it to some other object of same type. These can be treated as defaults or use for natural ordering.
Like 1 comes before 2 and so on. This is the natural ordering.
Comparator:
This is property which actually is not tightly bound to the Java class itself. Comparators are used to actually provide a method to be used by some other services (like Collections.sort()) for achieving a particular goal.
int compare(T o1, T o2);
By this we mean, You can have multiple Comparators, providing different ways of achieving different goals wherein the actual service can pick any two objects and compare them.
This can be used to provide custom ordering, like using some equation we can come up with an ordering where f(1) actually comes after f(2) and so on. This equation will likely be achieving some order which solves a use-case.
Let's assume I have an Employee base class and Manager subclass which extends Employee.Now let's say I create an object x of type Employee and object y of type Manager and call x.compareTo(y) no exception is triggered and x and y is compared as Employees namely y is cast to an Employee but when I call y.compareTo(x) I get a classCastException.I need to know why this happens and how to prevent x.compareTo(y) to execute as x and y are from different classes.My idea is to use getclass() method in Reflection class like this:
if (getClass() != other.getClass())
throw new ClassCastException();
I also want to know is there any other way to implement this.
You should implement compareTo() in the class Employee and start it with:
Employee o = (Employee)other;
Then continue with comparing this to o - this will ensure you're comparing two Employees (which is the lowest common denominator).
Because your Manager is an Employee but Employee is not a Manager See below
http://docs.oracle.com/javase/tutorial/java/IandI/subclasses.html
instance of can be usefull in such cases
here Manager is a Employee.
but Employee is not Manager.
Quote from Effective Java, Item 12:
Let’s go over the provisions of the compareTo contract. The first provision says that if you reverse the direction of a comparison between two object refer- ences, the expected thing happens: if the first object is less than the second, then the second must be greater than the first; if the first object is equal to the second, then the second must be equal to the first; and if the first object is greater than the second, then the second must be less than the first. The second provision says that if one object is greater than a second, and the second is greater than a third, then the first must be greater than the third. The final provision says that all objects that compare as equal must yield the same results when compared to any other object.
One consequence of these three provisions is that the equality test imposed by acompareTo method must obey the same restrictions imposed by the equals con- tract: reflexivity, symmetry, and transitivity. Therefore the same caveat applies: there is no way to extend an instantiable class with a new value component while preserving the compareTo contract, unless you are willing to forgo the benefits of object-oriented abstraction (Item 8). The same workaround applies, too. If you want to add a value component to a class that implements Comparable, don’t extend it; write an unrelated class containing an instance of the first class. Then provide a “view” method that returns this instance. This frees you to implement whatever compareTo method you like on the second class, while allowing its cli- ent to view an instance of the second class as an instance of the first class when needed.
All Manager are Employee but not all Employee are Managers. Since all the attributes of Employee are available in Manager,Manager can be casted to Employee. But attributes of Manager is unavailable to Employee, so cast is not possible.
My suggestion is to override compareTo() method in your classes and cast the object Employee.
If your are using compareTo method then i am excepting you have implemented Comparable interface in your class and provide a implementation of the method compareTo. let me know how you are comparing object on what logic ,based on that only you get the solution.
I have little bit confuse on this
if (getClass() != other.getClass())
throw new ClassCastException();
if it is the code in your compareTo method then rather then doing this create one more interface say "XYZ" and implement that Interface to both the class
check the logic
public int compareTo(T obj){
if(this instanceof XYZ && obj instanceof XYZ)){
return 0;
}else{
throw new ClassCastException();
}
}
You could perhaps use isAssignableFrom which will return true or false and then use it for doing further comparison or equals etc. Not sure why you would need this in compareTo; however.
Anyways assuming name , salary for an employee and set of reportees for manager and then for example further just comparing salaries as part of compareTo.
public class Test{
public static void main(String[] args) {
class Employee implements Comparable<Employee> {
public Employee(String string, int salary) {
this.name = string;
this.salary = salary;
}
public Employee() {
name = "";
salary = 0;
}
String name;
Integer salary;
public int compareTo(Employee o) {
return o!=null && getClass().isAssignableFrom(Employee.class)
? salary.compareTo(o.salary) : Integer.MIN_VALUE;
}
}
class Manager extends Employee {
public Manager(String name, String[] subordinates) {
super(name, 1000000);
reportees = subordinates;
}
String[] reportees;
}
Employee e = new Employee("me", 1000);
Employee e1 = new Employee("mycolleague", 2000);
Manager m = new Manager("myboss", "me mycolleague".split(" "));
System.out.println(e1.compareTo(e));
System.out.println(e.compareTo(m));
System.out.println(m.compareTo(e)); // this gives INT.MIN as you cannot compare manager to employee
}
}
I am trying to understand TreeSet. I have created am Employee object and trying to add the employee object to TreeSet. to realize this I have created class called sortName, which sorts the employee objects based on name. and I have written an equals method too (just to understand the execution flow). I read that in TreeSet to add elements in some sorted we must implement comparator interface and overwrite two methods (compare and equals) ofcourse equals is optional. When i try to run the program it works, I observed that the equals method is never invoked, why is that?
Lets draw a comparison between HashSet and TreeSet. in HashSet when the hashCode is same then it checks for equals method, otherwise not. I am interested in knowing how is the working in TreeSet?
Can anyone give me a example where even equals method is invoked for TreeSet?
public int compare(Object Obj1, Object Obj2) {
System.out.println("compare");
if (Obj1 instanceof Employee19 && Obj2 instanceof Employee19) {
Employee19 emp1=(Employee19) Obj1;
Employee19 emp2=(Employee19) Obj2;
return emp1.sname.compareTo(emp2.sname);
}
return 0;
}
public boolean equals(Object obj){
System.out.println("equals");
return true;
}
I even checked this link but that was not what I was looking...
HashSet with two equals object?
The TreeSet doesn't need to use the equals method, because it gets all the information it needs from the Comparator's compare method (or the compareTo method if it is relying on its elements being Comparable). Either way can tell if elements are equivalent, if the appropriate method returns 0.
I use this function in java:
Collections.sort(an Arraylist, new CustomComparator);
The compare method in CustomComparator class will return an int. What this means to the sort procedure? what's the number of this value and how will affect the sorting procedure?
More specifically i want in compare method to compare two values. Here is my code:
import java.util.Comparator;
public abstract class CustomComparator implements Comparator<HLine> {
#Override
public int compare(HLine hl1, HLine hl2) {
return hl1.y < hl2.y;
}
}
and i call for sorting:
Collections.sort(hlines, new comparator());
hlines is an Arraylist of a object's with a Point and two doubles. I want to compare the second double in two object's.
Basically, as stated in the Javadoc of Comparator.compare and Comparable.compareTo these methods return
a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.
Meaning that if you call
new Integer(1).compareTo(0)
it will return a negative integer, which indicates, that 0 has to be ordered before 1.
new Integer(1).compareTo(1)
it will return 0, which indicates, that both values has to be ordered on the same level.
new Integer(1).compareTo(2)
it will return a positive integer, which indicates, that 2 has to be ordered after 1.
To fix your codesample, you need to rewrite compare() so it will return an Integer, as it is now it returns a boolean and will not compile.
Because you are trying to compare doubles you can simply change to
#Override
public int compare(HLine hl1, HLine hl2) {
return hl1.y - hl2.y;
}
sort() method sort the elements but first they are compared.For the comparison purpose, sort() method can use compare() or compareTo() methods.
Now, if you want to sort the elements on the basis of only one attribute the use compareTo() method of comparable interface.
And if you want to sort the elements on the basis of more than one elements then use cmopare() method of comparator interface.
I have a Sorts class that sorts (based on insertion sort, which was the assignment's direction) any ArrayList of any type passed through it, and uses insertion sort to sort the items in the list lexicographically:
public class Sorts
{
public static void sort(ArrayList objects)
{
for (int i=1; i<objects.size(); i++)
{
Comparable key = (Comparable)objects.get(i);
int position = i;
while (position>0 && (((Comparable)objects.get(position)).compareTo(objects.get(position-1)) < 0))
{
objects.set(position, objects.get(position-1));
position--;
}
objects.set(position, key);
}
}
}
In one of my other files, I use a method (that is called in main later) that sorts objects of type Owner, and we have to sort them by last name (if they are the same, then first name):
Directions: "Sort the list of owners by last name from A to Z. If more than one owner have the same last name, compare their first names. This method calls the sort method defined in the Sorts class."
What I thought first was to get the last name of each owner in a for loop, add it to a temporary ArrayList of type string, call Sorts.sort(), and then re-add it back into the ArrayList ownerList:
public void sortOwners() {
ArrayList<String> temp = new ArrayList<String>();
for (int i=0; i<ownerList.size(); i++)
temp.add(((Owner)ownerList.get(i)).getLastName());
Sorts.sort(temp);
for (int i=0; i<temp.size(); i++)
ownerList.get(i).setLastName(temp.get(i));
}
I guess this was the wrong way to approach it, as it is not sorting when I compile.
What I now think I should do is create two ArrayLists (one is firstName, one is LastName) and say that, in a for loop, that if (lastName is the same) then compare firstName, but I'm not sure if I would need two ArrayLists for that, as it seems needlessly complicated.
So what do you think?
Edit: I am adding a version of compareTo(Object other):
public int compareTo(Object other)
{
int result = 0;
if (lastName.compareTo(((Owner)other).getLastName()) < 0)
result = -1;
else if (lastName.compareTo(((Owner)other).getLastName()) > 0)
result = 1;
else if (lastName.equals(((Owner)other).getLastName()))
{
if (firstName.compareTo(((Owner)other).getFirstName()) < 0)
result = -1;
else if (firstName.compareTo(((Owner)other).getFirstName()) > 0)
result = 1;
else if (firstName.equals(((Owner)other).getFirstName()))
result = 0;
}
return result;
}
I think the object should implement a compareTo method that follows the normal Comparable contract--search for sorting on multiple fields. You are correct that having two lists is unnecessary.
If you have control over the Owner code to begin with, then change the code so that it implements Comparable. Its compareTo() method performs the lastName / firstName test described in the assignment. Your sortOwners() method will pass a List<Owner> directly to Sorts.sort().
If you don't have control over Owner, then create a subclass of Owner that implements Comparable. Call it OwnerSortable or the like. It accepts a regular Owner object in its constructor and simply delegates all methods other than compareTo() to the wrapped object. Its compareTo() will function as above. Your sortOwners() method will create a new List<OwnerSortable> out of the Owner list. It can then pass this on to Sorts.sort().
Since you have an ArrayList of objects, ordinarily we would use the Collections.sort() method to accomplish this task. Note the method signature:
public static <T extends Comparable<? super T>> void sort(List<T> list)
What's important here is that all the objects being sorted must implement the Comparable interface, which allows objects to be compared to another in numerical fashion. To clarify, a Comparable object has a method called compareTo with the following signature:
int compareTo(T o)
Now we're getting to the good part. When an object is Comparable, it can be compared numerically to another object. Let's look at a sample call.
String a = "bananas";
String b = "zebras";
System.out.println(a.compareTo(b));
The result will be -24. Semantically, since zebras is farther in the back of the dictionary compared to bananas, we say that bananas is comparatively less than zebras (not as far in the dictionary).
So the solution should be clear now. Use compareTo to compare your objects in such a way that they are sorted alphabetically. Since I've shown you how to compare strings, you should hopefully have a general idea of what needs to be written.
Once you have numerical comparisons, you would use the Collections class to sort your list. But since you have your own sorting ability, not having access to it is no great loss. You can still compare numerically, which was the goal all along! So this should make the necessary steps clearer, now that I have laid them out.
Since this is homework, here's some hints:
Assuming that the aim is to implement a sort algorithm yourself, you will find that it is much easier (and more performant) to extract the list elements into an array, sort the array and then rebuild the list (or create a new one).
If that's not the aim, then look at the Collections class.
Implement a custom Comparator, or change the object class to implement Comparable.