4

I'm new on stackoverflow.com but I often used it to search for answers whenever I had a problem, but now I can't find any result searching for my problem so I'm asking here :) I'm studying for the OCPJP SE 7 certification, exam 1Z0-804, and I'm using a book (there is only one available afaik, Ganesh\Sharma's one) In the collections chapter, about the Comparator interface, the book provide this example of using both the Comparator and the Comparable interface to sort an array of Student elements, but the question is about the Comparator:

import java.util.*;

class Student implements Comparable<Student> {
    private String id, name;
    private Double cgpa;
    public String getName() {
        return name;
    }
    public String getId() {
        return id;
    }
    public Double getCgpa() {
        return cgpa;
    }
    public Student(String studentId, String studentName, double studentCGPA) {
        id=studentId;
        name=studentName;
        cgpa=studentCGPA;
    }
    public String toString() {
        return id+" "+name+" "+cgpa;
    }
    public int compareTo(Student that) {
        return this.id.compareTo(that.id);
    }
}

class StudentCGPA implements Comparator<Student> {
    public int compare(Student s1, Student s2) {
        return s1.getCgpa().compareTo(s2.getCgpa());
    }
}

class MyMainClass {
    public static void main(String[] args) {
        Student[] students =    {   new Student("cs011", "Lennon", 3.1),
                                    new Student("cs021", "McCartney", 3.4),
                                    new Student("cs012", "Harrison", 2.7),
                                    new Student("cs022", "Starr", 3.7),
                                };
        Arrays.sort(students, new StudentCGPA());
        System.out.println(Arrays.toString(students));
    }
}

So it creates a new class only for using the Comparator interface with two Student objects, but I think this is very uncomfortable so I wonder: why can't I use a nested class (within Student)? Like this:

import java.util.*;

class Student implements Comparable<Student> {
    private String id, name;
    private Double cgpa;
    public String getName() {
        return name;
    }
    public String getId() {
        return id;
    }
    public Double getCgpa() {
        return cgpa;
    }
    public Student(String studentId, String studentName, double studentCGPA) {
        id=studentId;
        name=studentName;
        cgpa=studentCGPA;
    }
    public String toString() {
        return id+" "+name+" "+cgpa;
    }
    public int compareTo(Student that) {
        return this.id.compareTo(that.id);
    }
    static class StudentCGPA implements Comparator<Student> {
        public int compare(Student s1, Student s2) {
            return s1.getCgpa().compareTo(s2.getCgpa());
        }
    }
}

class MyMainClass {
    public static void main(String[] args) {
        Student[] students =    {   new Student("cs011", "Lennon", 3.1),
                                    new Student("cs021", "McCartney", 3.4),
                                    new Student("cs012", "Harrison", 2.7),
                                    new Student("cs022", "Starr", 3.7),
                                };
        Arrays.sort(students, new Student.StudentCGPA());
        System.out.println(Arrays.toString(students));
    }
}

The book says nothing about using nested classes instead of normal ones, but I can't see why it should be bad to do like this... Is there any problem with my code (the 2nd one)? Should I follow what the book says because my implementation of Comparator is wrong? (Note: the code compiles and runs without problem, with the expected output in both cases)

[cs012 Harrison 2.7, cs011 Lennon 3.1, cs021 McCartney 3.4, cs022 Starr 3.7]

Please help :D Thanks in advance.

RaffoSorr
  • 405
  • 1
  • 5
  • 14
  • 1
    Multiple classes per compilation unit are a virtually deprecated feature of Java. You should always prefer nested classes. – Marko Topolnik Oct 07 '14 at 13:34
  • 1
    If you use a separate `Comparator` then it's good practice to put its source in a separate file. That's not an argument for or against nesting the `Comparator` class. Very likely the **book** from which the original source is drawn presents it as it does for convenience and clarity. – John Bollinger Oct 07 '14 at 14:07

2 Answers2

6

You can implement a Comparator as a static nested class of the one being compared, if you are in control of that class (and if it is a class rather than an interface). It is not at all uncommon, however, that you want to compare instances of a class that you do not control, according to an order that the target class does not support natively (whether by being Comparable or by providing a Comparator class). In that case you must create your own, separate Comparator.

Even when you control everything, it is somewhat a matter of taste whether to implement Comparators as top-level classes. I'm not sure why you call it "uncomfortable"; myself, I usually prefer to avoid nested classes when I can. Note, too, that whether you nest or not, the Comparator implementation class will be compiled to a separate class file.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • 1
    The perfect answer I was hoping for! :D I didn't thought about the particular case you explained, effectively if I couldn't access to a class I wouldn't be able to implement the Comparator as a nested class, so I better always implement it as a separate class :) Thank you (and the others too, of course) a lot! – RaffoSorr Oct 07 '14 at 14:14
-2

There's no real reason why you shouldn't actually implement both Comparable and Comparator.

class Student implements Comparable<Student>, Comparator<Student> {

    private final String id;
    private final String name;
    private final Double cgpa;

    public String getName() {
        return name;
    }

    public String getId() {
        return id;
    }

    public Double getCgpa() {
        return cgpa;
    }

    public Student(String studentId, String studentName, double studentCGPA) {
        id = studentId;
        name = studentName;
        cgpa = studentCGPA;
    }

    @Override
    public String toString() {
        return id + " " + name + " " + cgpa;
    }

    @Override
    public int compareTo(Student that) {
        return this.id.compareTo(that.id);
    }

    @Override
    public int compare(Student o1, Student o2) {
        return o1.getCgpa().compareTo(o2.getCgpa());
    }
}

It is often more appropriate, however, to implement only Comparable and use other methods (such as inner classes or anonymous classes) to choose different orders.

    class ByCgpa implements Comparator<Student> {

        @Override
        public int compare(Student o1, Student o2) {
            return o1.getCgpa().compareTo(o2.getCgpa());
        }

    }

    Collections.sort(list, new ByCgpa());

static void sortByCgpaAndName(Collection<Student> students) {
    Collections.sort(students, new Comparator<Student> () {

        @Override
        public int compare(Student o1, Student o2) {
            int byCgpa = o1.getCgpa().compareTo(o2.getCgpa());
            return byCgpa != 0 ? byCgpa : o1.name.compareTo(o2.name);
        }
    });
}

See here for further discussion.

Community
  • 1
  • 1
OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • @Serhiy - How is that relevant to the question about using a nested class to implement `Comparator`? – OldCurmudgeon Oct 07 '14 at 13:41
  • Sure, but you may need to order Students by their name in some cases and by their cgpa in other cases, and when you need two or more different implementations of the compare method you can't implement it in the class that needs to be compared, right? But if I need two different implementation I can simply create two nested classes, say Student.StudentCGPA and Student.StudentName – RaffoSorr Oct 07 '14 at 13:42
  • `compareTo(): To give an idea about sorting the objects to the user of your class, here user who is going to use "Student" class compare() : If the developer didn't provide the compareTo() method to compare or user wants to change the logic to compare two objects of the same class without changing the code of the class itself(Student).` – VijayD Oct 07 '14 at 13:46
  • @OldCurmudgeon well maybe my question is not relevant to his question, but then your answer has nothing to do with his question either.. because what was asked is: "why can't I use a nested class (within Student)?". And not "why shouldn't I implement `Comprataor` and `Comparable`?"... – Serhiy Oct 07 '14 at 13:47
  • @Serhiy for compareTo you don't have to write nested class, right? and sometimes you only have .class file of such classes like "Student" whose source code you cant change so you cant write the nested class, but yes, if you have the src code, then you can do it, but in practical, you use campare() in the first case(i.e. src not available) – VijayD Oct 07 '14 at 13:49
  • @Raffolox - That is correct - inner classes and/or anonymous classes can be used to more closely control the ordering of your objects. I will add some code to demonstrate. – OldCurmudgeon Oct 07 '14 at 13:50
  • @Viktor honestly I think the example is simply bad(the one which OP has posted) not sure if it is the same in book.. Obviously I only would use `Comparator` only when I do not have access to the code which I would like to compare or when multiple comparisons are required on different attributes.. – Serhiy Oct 07 '14 at 13:59
  • @Serhiy the example is equal to the book's one, check page 189 if you own it too... – RaffoSorr Oct 07 '14 at 14:03