4

My apologies for my limited knowledge of generics beforehand.

I have the following situation:

Class 1:

public class PostProcess implements Serializable {
    public int          order;
    // Several other variables.

    // Constructor setting vars
    public PostProcess(){

    }

    /* Getters and setters for variables */
}

Class 2:

public class Application extends PostProcess{
    public int      subOrder;
    // Several other variables.

    // Constructor setting vars
    public Application(){

    }

    /* Getters and setters for variables */

}

Class 3:

public class FileOperation extends PostProcess{
    public int      subOrder;
    // Several other variables (different from class 'Application').

    // Constructor setting vars
    public FileOperation(){

    }

    /* Getters and setters for variables */

}

What I am trying to achieve in a different class is to sort a list containing a mix of 'FileOperation' and 'Application' objects defined as:

private ArrayList<? extends PostProcess> processList = new ArrayList<PostProcess>();

This sort will have to be on two fields of both of these objects, namely: 'order' and 'subOrder'. 'order' is inherited from PostProcess and 'subOrder' is defined in both the 'Application' and 'FileOperation' classes.

Throughout my journey of reading up on Generics, Comparable, Comparators and Interfaces I think I got things mixed up.

I am trying to apply a sort using:

Collections.sort(processList, new PostProcessComparator());

The PostProcessComparator is defined as:

public class PostProcessComparator implements Comparator<? extends PostProcess> {

    @Override
    public int compare(Object p1, Object p2) {

      int mainOrderCompare = p1.getOrder().compareTo(p2.getOrder());
      if (mainOrderCompare != 0) {
        return mainOrderCompare;
      } else {
        return p1.getSubOrder().compareTo(p2.getSubOrder());
      }
    }
}

Questions:

I know my Comparator (and possibly more) is wrong, but I dont know where specifically. Im here to learn ;)

  1. I noticed that defining the 'processList' List isnt the right way to do it. When I try to add an 'FileOperation' or 'Application' object to the List the compiler slaps me in the face with "no suitable method found for add(Application)" (the same for FileOperation). Did I incorrectly assume that I could use generics to declare my processList types? should be correct as both classes have PostProcess as their superclass right?
  2. Defining the PostProcessComparator with class bounds should work in my eyes as I only want to be able to compare objects that have PostProcess as a superclass (and thus have access to the same methods).
  3. How do I access the argumented objects in the Comparator class for p1 and p2 (because I still need to declare their types for the arguments:

    @Override
    public int compare(<What to put here?> p1, <And here?> p2) {
    
    }
    

I really hope you guys can help! If I was unclear in something, do let me know and ill elaborate.

Thanks!

EDIT

Thanks to NickJ and Winzu I have made the necessary changes to the comparator and the ArrayList definition.

  • I have moved subOrder from Application and FileOperation to the parent class (and made them protected)
  • Redefined parameterization of the comparator to:

    public class PostProcessComparator<T extends PostProcess> implements Comparator<T> 
    
  • Made use of the Integer.compare(p1.getOrder(), p2.getOrder()) for initial comparator comparisons.

Now the final challenge (compiler warnings) Upon calling:

    Collections.sort(processList, new PostProcessComparator());

I get the warning: - [unchecked] unchecked method invocation: method sort in class Collections is applied to given types

    required:  List<T>,Comparator<? super T>
    found: ArrayList<PostProcess>,PostProcessComparator

The parameterization is correct for this comparator in my eyes, aside from the fact that i havent checked the object types.

Where does this go wrong?

Rhizosis
  • 43
  • 1
  • 5
  • 1
    Design question - why you don't put subOrder property in PostProcess class, and make it protected for example, so derived classes can use it? – Alex Adas Oct 16 '13 at 09:23
  • @AlexAdas Thats a good idea. I havent thought about that. Let me try it out! Thank you – Rhizosis Oct 16 '13 at 10:43
  • 1
    Updated my answer for your last concern – Winzu Oct 16 '13 at 12:29
  • Many thanks Winzu. The worked perfectly. Its difficult choosing the right answer now as it came in two parts. +2 for you. Many thanks for your help. – Rhizosis Oct 16 '13 at 12:49

3 Answers3

4

The only problem I found was that you need to parameterise PostProcessComparator like this:

public class PostProcessComparator<T extends PostProcess> implements Comparator<T> {

  @Override
  public int compare(T p1, T p2) {

    int mainOrderCompare = p1.getOrder().compareTo(p2.getOrder());
    if (mainOrderCompare != 0) {
      return mainOrderCompare;
    } else {
      return p1.getSubOrder().compareTo(p2.getSubOrder());
    }
  }
}

Now the compare() method will accept the correct class (extends PostProcess) and so any public methods of PostProcess may be called from within comlpare()

Finally, your fields should not be public. I suggest making the fields protected, so subclasses may still inherit them, but encapsulation is maintained.

NickJ
  • 9,380
  • 9
  • 51
  • 74
  • 1
    You can't use compareTo on primitive type int. Either use the operator " - " or Integer.compare(p1.getOrder(), p2.getOrder()); – Winzu Oct 16 '13 at 09:28
  • Good point! I just copied the original code changed the generics parameterization. – NickJ Oct 16 '13 at 09:33
  • @NickJ Many thanks it indeed does seem to work. However another compiler error tells me that my p1 and p2 objects do not have the method getSubOrder() (Cannot find symbol). Which could be correct because I declared my list to contain only types and not its children? Dont the objects p1 (of type T) and p2 have access to the getSubOrder() method? – Rhizosis Oct 16 '13 at 10:38
  • 1
    No - since T extends PostProcess, the comparator only knows about methods defined in PostProcess, not any of its subclasses. If you need to call subclass methods, either use separate Comparators, one for each subclass, or check the actual class using instanceof and them cast them. – NickJ Oct 16 '13 at 10:56
2

NickJ's answer above shows how to implement the comparator.

For what you're doing you also want to change

private ArrayList<? extends PostProcess> processList = new ArrayList<PostProcess>();.

to

ArrayList<PostProcess> processList = new ArrayList<PostProcess>();

This is why you can't add your Application et FileOperation objects into your list.

It's a bit tricky. Maybe this post can help you understand

Java using generics with lists and interfaces

You also want SubOrder in the parent class.

import java.io.Serializable;

public class PostProcess implements Serializable {
private int          order;
private int      subOrder;
// Several other variables.

public int getSubOrder() {
    return subOrder;
}

public void setSubOrder(int subOrder) {
    this.subOrder = subOrder;
}

public int getOrder() {
    return order;
}

public void setOrder(int order) {
    this.order = order;
}

// Constructor setting vars
public PostProcess(){

}

}

Finally call the comparator like this to avoid unchcked warning:

 Collections.sort(processList, new PostProcessComparator<PostProcess>());
Community
  • 1
  • 1
Winzu
  • 391
  • 2
  • 10
  • Thank you Winzu! That did the trick (for the compiler) but the defined p1 and p2 objects (of type T) in the comparator now dont have access (cannot find symbol) to the getSubOrder method. Is this because the ArrayList limits the accessible methods? – Rhizosis Oct 16 '13 at 10:42
  • 1
    Since both your Application et FileOperation objects have a SubOrder, i see no reason for SubOrder not to be in PostProcess class instead. Let me edit the answer. – Winzu Oct 16 '13 at 10:43
  • Thank you. I have made the edit with the current solutions and the last compiler warning that showed up. – Rhizosis Oct 16 '13 at 11:00
0

Just change it to:

public class PostProcessComparator implements Comparator<PostProcess> {
    @Override
    public int compare(PostProcess p1, PostProcess p2) {
      //...
    }
}

That's it. You have a comparator that can compare any two instances of PostProcess (and instances of subclasses of PostProcess are instances of PostProcess).

newacct
  • 119,665
  • 29
  • 163
  • 224
  • Initially I had added the 'subOrder' field to the children 'FileOperation' and 'Application'. By casting the passed on objects to 'PostProcess' I wouldnt have access to these accessor methods because they werent inherited by 'PostProcess' right? With the revision of my design (moving subOrder to the parent class PostProcess) this would be a solution. But if I understand correctly your solution wouldn't have worked when subOrder was part of the children only? Or am I mistaken here? – Rhizosis Oct 17 '13 at 08:19
  • @Rhizosis: but `PostProcess` would have a `getSubOrder()` method, right? Because otherwise, the accepted solution wouldn't work either, because the only way that `T` would have a `getSubOrder()` method, is if `PostProcess` has it. – newacct Oct 18 '13 at 06:48