5
\$\begingroup\$

I have a list of factories. Each factory has an item of its own and a list of other items from competitors.

I need to sort the list of factories based on price of their items and also sort list of other items from competitors for each factory.

What I am doing require to sort collection of factories and loop through all factories and sort collection of their competitors. I am wondering if there is any easier way to do it.

Factory

class Factory implements Comparator<Factory>{
String item;
double price;
List<Competitor> competitors;
@Override
 public int compare(Factory o1, Factory o2) {
 if (o1.getPrice() < o2.getPrice())
 return -1;
 if (o1.getPrice() > o2.getPrice())
 return 1;
 return 0;
 }}

Competitor

class Competitor implements Comparator<Competitor>{
String facName;
double price;
@Override
public int compare(Competitor o1, Competitor o2) {
 if (o1.getPrice() < o2.getPrice())
 return -1;
 if (o1.getPrice() > o2.getPrice())
 return 1;
 return 0;
}

Sort

Collection.sort(factoriesList); //sort list of factories
//sort list of competitors of each factory
for(int i=0;i<factoriesList.size();i++){
 Collection.sort(factoriesList.getCometitors());
}

Sample result

Factory1 Item1 222 Com1 444 Com2 555
Factory2 Item2 223 Com3 555 Com4 676
Factory3 Item3 224 Com1 543 
Factory4 Item4 225 Com9 322
Tunaki
9,3011 gold badge31 silver badges46 bronze badges
asked May 28, 2016 at 10:19
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Do you have java 8 available? \$\endgroup\$ Commented May 28, 2016 at 10:57
  • \$\begingroup\$ Did you mean Collection.sort(factoriesList.get(i).getCometitors());? was it a copy paste problem? \$\endgroup\$ Commented May 28, 2016 at 11:32

1 Answer 1

7
\$\begingroup\$

There are others concerns with your code, without going into the sort:

Getter returning mutable data

getCompetitors() returns directly the internal list stored by your factory object. This is generally not a good idea: it means a client of Factory can modify its internal structure, which defeats the OOP principle. It would be preferable instead to have a method sortCompetitors(), that would sort the list, without leaking it:

private List<Competitor> competitors;
public void sortCompetitors() {
 Collections.sort(competitors);
}

and remove completely the method getCompetitors().

Comparator, Comparable

class Factory implements Comparator<Factory>

will be problematic in the future. It seems what you want would be to use Comparable instead, but even this isn't a good idea in this case. There is a difference between the two: a class is Comparable when it can compare itself to another class of the same type, which is what you are doing here: one Factory is comparing itself to another object. On the other hand, a Comparator is a class that is comparing 2 objects of the same type (it does not compare this with another object).

Therefore:

  • It is wrong to have your class implement Comparator: it means the class is doing too much: it knows itself and it is also responsible for comparing any 2 factories (See also on Stack Overflow). More often than not, there are several ways to compare 2 objects given a context: sort by name or sort by price...
  • It is also probably wrong to have your class implements Comparable, but for a different reason. As said before, Comparable is used to compare this with another object of the same type. But this interface is closely related to equals and hashCode: any class that is Comparable should provide a consistent equals method (It is strongly recommended, but not strictly required that (x.compareTo(y)==0) == (x.equals(y))). In your case, it would imply that two factories are equal when their price is equal, which is probably not what you want.

The solution here is not to make your class implements Comparator and define a custom comparator class, like

class FactoryPriceComparator implements Comparator<Factory>

Using built-in methods

Your compare methods are currently doing:

@Override
public int compare(Factory o1, Factory o2) {
 if (o1.getPrice() < o2.getPrice())
 return -1;
 if (o1.getPrice() > o2.getPrice())
 return 1;
 return 0;
}

This can be written more concisely with the built-in Double.compare (since Java 7), which also properly handles NaN, -0.0 and 0.0, contrary to your current code:

public int compare(Factory o1, Factory o2) {
 return Double.compare(o1.getPrice(), o2.getPrice());
}

Your whole comparator would then become:

public class FactoryPriceComparator implements Comparator<Factory> {
 @Override
 public int compare(Factory o1, Factory o2) {
 return Double.compare(o1.getPrice(), o2.getPrice());
 }
}

Note that you would have the same implementation for the Comparator<Competitor>.

Java 8 API

If you're using Java 8, you can even get rid of the above FactoryPriceComparator and use the built-in Comparator.comparingDouble(keyExtractor), which creates a comparator comparing the double values returned by the key extractor. In this case, the key extractor could be the method reference Factory::getPrice (resp. Competitor::getPrice). So you could simply have:

Collections.sort(factoriesList, Comparator.comparingDouble(Factory::getPrice));

or even

factoriesList.sort(Comparator.comparingDouble(Factory::getPrice));

Finally:

What I am doing require to sort collection of factories and loop through all factories and sort collection of their competitors

This is actually the proper way of doing it: when you sort a Factory, you cannot sort the inner competitors at the same time, because different objects are being compared.

answered May 28, 2016 at 13:33
\$\endgroup\$
4
  • \$\begingroup\$ Thanks for your answer, I learned a lot. I am a bit confused with FactoryPriceComparator class. My question is how to call compare method of factoryPriceComparator to sort factories? Do I need to loop through them and pass them to the compare method? \$\endgroup\$ Commented May 29, 2016 at 1:00
  • \$\begingroup\$ getCompetitors() returns directly the internal list stored by your factory object. This is generally not a good idea: it means a client of Factory can modify its internal structure. This is only true if the returned list is not readonly. \$\endgroup\$ Commented May 29, 2016 at 9:20
  • \$\begingroup\$ @Jack Yes, like what I did in the last example. You can have an instance of the comparator (let's call it factoryPriceComparator) and use it like: Collections.sort(factoriesList, factoryPriceComparator);. This will sort all factories according to their price. Then, yep, you need to loop through them and sort the competitors. You don't call directly the compare method, it will be called during the sort operation. \$\endgroup\$ Commented May 29, 2016 at 14:45
  • 1
    \$\begingroup\$ @BrunoCosta Correct, I assumed it wasn't readonly since the OP called Collection.sort(factoriesList.getCometitors());, meaning that the getter returns the list itself. If there is such a getter, you'd want to wrap it with Collections.unmodifiableList for example. \$\endgroup\$ Commented May 29, 2016 at 14:49

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.