Using a custom comparator - java

I've checked a bunch of similar questions but am still very confused. Anyway this was for an assignment that's been and gone.
I have a Present class:
public class Present implements PresentInterface{
private String name;
private String type;
private double cost;
public Present() {
}
public Present(String name, String type, double cost) {
this.name = name;
this.type = type;
this.cost = cost;
}
and then a bunch of code for getting and setting the values.
I have a Child class which contains a bunch of info about the child.
I have a GiftList class which is an arraylist of presents. Each giftlist is associated with at most one child.
I then have a GiftSelector class which is an arraylist of GiftLists.
I would like to have a method in the giftSelector class that creates a hashmap with the keys being children, and the values being a list of presents sorted by cost.
So far I have:
public HashMap<Child, ArrayList<Present>> sortList(){
HashMap<Child, ArrayList<Present>> presentMap = new HashMap<Child, ArrayList<Present>>();
ArrayList<Present> presentList = new ArrayList<Present>();
for (GiftList giftList : giftSelector){
presentList.clear();//clears the present list with each iteration otherwise
//each child would be paired with a list of presents containing those
//of the child before.
Child mapChild = giftList.getChild();
for (Present present : giftList.getAllPresents()){
presentList.add(present);//goes through each present in the giftlist and adds it to presentlist
}
Collections.sort(presentList, new Comparator<Present>());
presentMap.put(mapChild, presentList);
}
return presentMap;
}
}
The comparator isn't defined so of course it doesn't work. Do I define the comparator in the Present class or in the giftSelector class or do I give it an entirely new class of its own?
I think I need something like this somewhere:
public int compare(Present p1, Present p2){
if (p1==null || p2 == null){
return 0;
}
return p1.getCost().compareTo(p2.getCost());
}
and then some stuff which involves overriding and setting the compareTo values and other bits of magic. Any advice would be greatly appreciated :)
Incidentally, when I take out the Collections.sort(presentList, comparator) bit of the sortList() method, it compiles and runs fine, except that each child in the presentMap has the same value. They all have an arraylist containing the presents of the last giftlist that was iterated through. I've probably missed something obvious though.

I would define it as a static member class:
public class Present implements PresentInterface {
public static class CostComparator implements Comparator<Present >
{
public int compare(Present p1, Present p2)
{
// use either this line for null
if (p1 == null || p2 == null) throw new NullPointerException();
// or these 2 lines for null:
if (p1 == null) return p2 == null ? 0 : -1;
if (p2 == null) return 1;
// and now do a reference check for speed
if (p1 == p2) return 0;
// and finally the value checks
return Double.compare(p1.cost, p2.cost);
}
}
private String name;
private String type;
private double cost;
}
There are two alternatives for null ordering (as shown) as per the docs:
Unlike Comparable, a comparator may optionally permit comparison of null arguments, while maintaining the requirements for an equivalence relation
Putting it inside the Present class just makes it easy to find, and because it is only relevant to the Present class it makes sense to nest it. I would however document its inconsistency with equals as, according to the docs:
Caution should be exercised when using a comparator capable of imposing an ordering inconsistent with equals to order a sorted set (or sorted map).
i.e. as it now has a different "natural order" definition to equals, you may run into unforeseen problems in various circumstances.
You should also think about how you order two different Present objects that have the same cost - is ordering by cost all you need to do?
Late edit as far as your "second question" goes, you need to instantiate a new ArrayList each time you start the iterator, otherwise every map value refers to the same arraylist (see this answer for more explanation):
for (GiftList giftList : giftSelector){
presentList = new ArrayList<Present>; // create a new instance of a present list with each iteration otherwise ...

Related

What is the best way to compare two objects having multiple list attributes

I have a POJO/DTO class with multiple list attribute like
class Boo {
private List<Foo> foos;
private List<Integer> pointers;
}
I want to compare if both lists contain the same values ignoring the order of the lists. Is it possible to achieve this without opening the object and ordering the lists?
Help would be appreciated. Thanks in Advance
"I want to compare if both contains same values instead of the order of list."
There is not a universal equality operator. Sometimes you want compare objects by certain properties. Probably the canonical example could be comparing strings, sometimes "computer" is equal or not than "Computer" or "Vesterålen" is equal or not than "Vesteralen".
In Java, you can redefine the default equivalence relation between objects (modifying the default behavior!).
The object List use as default equivalence relation the default equivalence relation of the contained objects and checking that equality in order.
The following example ignore the elements order only in one property:
class My {
private final List<String> xs;
private final List<Integer> ys;
My(List<String> xs, List<Integer> ys) {
this.xs = xs;
this.ys = ys;
}
public List<Integer> getYs() {
return ys;
}
public List<String> getXs() {
return xs;
}
#Override
public int hashCode() {
return xs.hashCode() + 7 * ys.hashCode();
}
#Override
public boolean equals(Object obj) {
if(!(obj instanceof My))
return false;
My o = (My) obj;
return
// ignoring order
getXs().stream().sorted().collect(toList()).equals(o.getXs().stream().sorted().collect(toList()))
// checking order
&& getYs().equals(o.getYs());
}
}
public class Callme {
public static void main(String... args) {
My m1 = new My(asList("a", "b"), asList(1, 2));
My m2 = new My(asList("b", "a"), asList(1, 2));
My m3 = new My(asList("a", "b"), asList(2, 1));
System.out.println(m1.equals(m2));
System.out.println(m1.equals(m3));
}
}
with output
true
false
But I can't define YOUR required equivalence relation, for example I do not ignore if one list contains more elements than the other but maybe you wish (eg. to you is equal {a, b, a} than {b, a}).
So, define you equivalence relation for your object and override hashCode and equals.
This boils down to comparing the lists. If the order of the items is irrelevant anyways you might fare better using Set instead of List.
Your equals then would look like
public boolean equals(object other) {
//here be class and null checks
return foos.equals(other.foos) && pointers.equals(other.pointers);
}
If you cannot use Set - either because you can have the same item multiple times or because order matters - you have can do the same as above with a reciprocal containsAll() call. This still would not take duplicate entries into consideration but will work quite fine otherwise.
You state that you cannot edit the class Boo. One solution would be to have a service class which does this for you a bit similar to Objects.equals().
class BooComparer {
public static bool equals(Boo a, Boo b) {
//again do some null checks here
return a.foos.containsAll(b.foos)
&& b.foos.containsAll(a.foos)
&& a.pointers.containsAll(b.pointers)
&& b.pointers.containsAll(a.pointers)
}
}
If this works for you - fine. Maybe you have to compare other members, too. And again: this will ignore if one of the lists has an entry twice.

When using Collections.sort - no instance of Variable T exist so that Collection conforms etc

so I've build these two classes:
1. Genre which implements Comparable
2. GenreManager which takes a Collection of genres and creates an internal copy of it. Later in GenreManager, I will need to add new Genres by getting a name as an input, and I need to assign this Genre the next free id number, which is basically the next smallest positive number after the smallest used id.
I am trying to use Collections.sort() to sort my list but I am getting the following error:
"no instance(s) of type variable(s) T exist so that Collection conforms to List." and I am not sure what this is referring to... I've tried ready a bunch of posts about this on here but couldn't figure out the solution... Here is part of the code:
public class Genre implements Comparable<Genre>{
private int id;
private String name;
public Genre(int id, String name){
this.id = Validate.requireNonNegative(id);
this.name = Validate.requireNonNullNotEmpty(name);
}
#Override
public int compareTo(Genre o) {
int res = Integer.valueOf(id).compareTo(o.id);
if (res != 0){
return res;
}
else{
return this.name.compareToIgnoreCase(o.name);
}
}
}
public class GenreManager{
private Collection<Genre> genres;
private Collection<Genre> sortedTree;
public GenreManager(){
this.genres = new ArrayList<Genre>();
}
public GenreManager(Collection<Genre> genres){
// check for duplicates
for (Genre x : genres){
for (Genre y : genres){
if (x.equals(y) || x.getName().equals(y.getName()))
throw new IllegalArgumentException("List contains duplicates");
}
}
this.genres = new ArrayList<Genre>(Collections.sort(genres));
}
}
I am trying to do the sorting in the constructor above. Can someone tell me how to go around this?
I tried playing around a little bit, trying to change the private variable from Collection<Genre> to List<Genre> for example and similar things but nothing worked... I also tried casting the input of the .sort method to (List<Genre>) but it didn't work either.
PS: I can't change any of the method header or class headers.
Thanks!
As per request, here's a compilation of my comments to answer the question:
The immediate problem is that Collections.sort(List<T>) takes a List parameter and not just a Collection because collections in general don't have to be sortable (e.g. hash sets aren't). Additionally the method returns void and sorts the passed list in place, i.e. the way you call it won't compile.
Taking all this into consideration your code might be changed to something like this:
public class GenreManager{
private List<Genre> genres;
...
public GenreManager(Collection<Genre> genres){
...
//create a list out of the passed collection
this.genres = new ArrayList<Genre>( genres );
//sort the list
Collections.sort(this.genres);
}
}
The other problem with the code you posted is that for any non-empty collection it will throw the IllegalArgumentException because elements are compared to themselves. Adding a check for x != y to the condition would solve that but the code is still somewhat slow because it has a time complexity of O(n2).
This can be solved to use a set instead of a list. However, a HashSet would depend on how equals() and hashCode() define equality, which doesn't seem to match your requirements. That could be solved by using a wrapper object that implements both methods as needed.
A better approach might be to use a TreeSet though. TreeSet uses comparisons to determine order and equality (if the compare result is 0) and thus would allow you to either let your Genre class implement Comparable as you did or provide a separate Comparator (e.g. if you need multiple different definitions of equality).
If you just want to eliminate duplicates, your code could then look like this:
public class GenreManager{
private SortedSet<Genre> genres;
...
public GenreManager(Collection<Genre> genres){
this.genres = new TreeSet<>( genres );
}
}
If you want to know what duplicates are in the collection you could do it like this:
public GenreManager(Collection<Genre> genres){
this.genres = new TreeSet<>(); //the generic type is inferred from this.genres
for( Genre element : genres ) {
//If the element didn't exist in the set add() will return true, false if it existed
boolean nonDuplicate = this.genres.add( element );
//handle the duplicate element here
}
}
As it was mentioned before, your code has several errors which makes it unusable:
Checking equality of elements with themselves.
Collections.sort method takes a List of Comparable as an argument, when Collection is a little higher in a hierarchy, which means you can't use it as a parameter. To resolve it change declaration of variable genres to List.
method Collections.sort returns void, so you can't pass its return value as an argument to ArrayList constructor. Instead, try assigning genres variable first and then sorting it via Collections.sort as
this.genres = new ArrayList/LinkedList(genres)
Collections.sort(this.genres)
Again, you may consider using TreeSet as it holds all elements sorted and without duplicates, so your constructor will just look like
this.genres = new TreeSet(genres)
In addition, it prevents duplicates even during adding, so if you have 10 elements, adding already existing one won't make any changes to your set. But using this data structure you should check variable for null before adding, as it will produce NullPointerException

Java compareTo() method returns classCastException

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
}
}

Confusion about Effective Java text

I'm not sure what the author means when he writes that a singleton static factory method can guarantee that no two equal instances exist. Well actually I do kind of understand that but I'm confused by the following text when he demonstrates the equals method vs the literal comparison operator: "a.equals(b) if and only if a==b."
I understand that the equals() method actually compares the contents of an object while the literal == compares to see if they are the same object in memory. This is confusing because he goes on to say that the client can use the == instead of the .equals(object) method. How so? Why would the client use the == comparator if they're guaranteed to only one object?
Could someone write me a short coded example to explain this more concretely?
The authors text is below:
The ability of static factory methods to return the same object from
repeated invocations allows classes to maintain strict control over
what instances exist at any time. Classes that do this are said to be
instance-controlled. There are several reasons to write
instance-controlled classes. Instance control allows a class to
guarantee that it is a singleton (Item 3) or noninstantiable (Item 4).
Also, it allows an immutable class (Item 15) to make the guarantee
that no two equal instances exist: a.equals(b) if and only if a==b. If
a class makes this guarantee, then its clients can use the == operator
instead of the equals(Object) method, which may result in improved
performance. Enum types (Item 30) provide this guarantee.
In the particular snippet you quote at the top he's talking about enforcing one instance for each possible set of values in instances of an immutable class:
Also, it allows an immutable class (Item 15) to make the guarantee
that no two equal instances exist: a.equals(b) if and only if a==b
That is, you might want your static factory to guarantee that if a and b represent the same values, then they are the same instance in memory (i.e. duplicates cannot exist). When this is true, then == works the same as equals(Object), which means that you are free to use == where you think it might help with performance.
As Jon says in the comments, static factories are not restricted to singletons.
I think you've almost got it. The static method makes the following promise, "if you request a new object that would compare .equals() to an existing object, I'll return the existing object instead". Given that guarantee, you know that a.equals(b) means that a == b, and you know that a == b means that a.equals(b). As a result, if you want to see if a and b are equal, you can use the == operator instead of the .equals method. That's useful because == is very fast and, depending on the object types, .equals could be slow.
Here's a concrete example. Suppose we have a person class. A person is defined by their first and last name (pretend that there are no two people in the world with the same name). My class might look like this (didn't try to compile, so no guarantee of correctness):
class Person {
private final String fname;
private final String lname;
// Private constructor - must use the static method
private Person(String first, String last) {fname = first; lname = last;}
// Note that this is slow - the time it takes is proportional to the length of the
// two names
public boolean equals(Object o) {
// Should check types here, etc.
Person other = (Person) o;
if (!person.fname.equals(other.fname)) {return false;}
if (!person.lname.equals(other.lname)) {return false;}
return true;
}
// Registry of all existing people
private static Map<String, Person> registry = new TreeMap<String, Person>();
public static getPerson(String fname, String lname) {
String fullName = fname + "-" + lname;
// If we already have this person, return that object, don't construct a new one.
// This ensures that p1.equals(p2) means that p1 == p2
if (registry.containsKey(fullName)) {return registry.get(fullName);}
Person p = new Person(fname, lname);
registry.put(fullName, p);
return p;
}
}
And then you can use it like this:
public boolean isSamePerson(Person p1, Person p2) {
// Guaranteed to have the same result as "return p1.equals(p2)" but will be faster
return p1 == p2;
}
If you can guarantee (perhaps with a Flyweight pattern) that equal objects will have the same referent, then callers may use == (and get a performance benefit); as an example consider an enum type... you can use == to determine if any two enum instances are the same.

Deciding to use Comparable or Comparator

My program implements a Product class whose objects contain the following instance variables: name, priority, price, and amount.
I have a LinkedList of Product objects that I need to sort before doing any other operations on the LinkedList.
I want to sort the list first by priority (from lowest to highest). If priority is the same, then look at the price (lowest to highest), then the name (alphabetical order).
I have done a lot of reading about Collections.sort, Comparable, and Comparator. I believe I need to use the Comparable interface and implement a compareTo method. My thinking is that because both priority, price, and name have a "natural" ordering it makes more sense to use Comparable.
public class Product extends ProductBase implements PrintInterface, Comparable<Product>{
private String name;
private int priority;
private int cents;
private int quantity;
// setters and getters
/**
* Compare current Product object with compareToThis
* return 0 if priority, price and name are the same for both
* return -1 if current Product is less than compareToThis
* return 1 if current Product is greater than compareToThis
*/
#override
public int compareTo(Product compareToThis)
}
Then when I want to sort my LinkedList I just call Collections.sort(LinkedList). Before I start writing the code, can you tell me if I am I missing or forgetting anything?
*************UPDATE*******************************
I just created a separate class called ProductComparator with a compare method.
This is part of the LinkedList class..
import java.util.Collections;
public class LinkedList {
private ListNode head;
public LinkedList() {
head = null;
}
// this method will sort the LinkedList using a ProductComparator
public void sortList() {
ListNode position = head;
if (position != null) {
Collections.sort(this, new ProductComparator());
}
}
// ListNode inner class
private class ListNode {
private Product item;
private ListNode link;
// constructor
public ListNode(Product newItem, ListNode newLink) {
item= newItem;
link = newLink;
}
}
}
I am getting the following error from the IDE when I compile.
The method sort(List, Comparator) in the type Collections is not applicable for the arguments (LinkedList, ProductComparator).
Does anyone know why I am getting this error and can point me in the right direction to resolve it?
If there is a "natural" ordering, use Comparable. Rule of thumb for figuring out if the ordering is "natural" is, whether the order of the objects will always be that.
Having said that, the decision whether to use Comparable or Camparator is not the kind of decision you need to think too much about. Most IDEs have refactoring tools which makes the conversion between a Comparable and a Comparator very easy. So if you choose to walk the wrong path now, changing that will not require too much effort.
The order you define here on your Product is very specific and
will probably change in future versions of your program
might be enriched with contextual parameterization
won't cover new features
So it can hardly been said "natural".
I'd suggest to define a constant, for example
public static Comparator<Product> STANDARD_COMPARATOR = new Comparator<Product>() {
public int compare(Product p1, Product p1) {
return ...
}
};
then you'll be able to easily sort anywhere with
Collections.sort(myProductList, Product.STANDARD_COMPARATOR);
Your code will evolve in a better manner as you'll add other comparators.
Just like you should generally prefer composition over inheritance, you should try to avoid defining the behavior of your objects in immutable manner.
If your order was based only on numbers, Comparable would be fine.
However, since your order (sometimes) involves lexical order of text,
a Comparator class is better, since use of Comparable would mean using
String.compareTo which would prevent you from having internationalization.
A separate class which implements Comparator can make use of a
localized Collator for comparing Strings. For instance:
public class ProductComparator
implements Comparator<Product> {
private final Collator collator;
public ProductComparator() {
this(Locale.getDefault());
}
public ProductComparator(Locale locale) {
this.collator = Collator.getInstance(locale);
}
public int compare(Product product1,
Product product2) {
int c = product1.getPriority() - product2.getPriority();
if (c == 0) {
c = product1.getPrice() - product2.getPrice();
}
if (c == 0) {
c = collator.compare(product1.getName(), product2.getName());
}
return c;
}
}
Regardless of whether you go with Comparable or Comparator, it is wise
to make sure Product has an equals method which checks the same
attributes as the comparison code.

Categories