0

I have a base class "People" which two other classes inherit from: Employee and Student. The Student class includes a GPA (type double) and the Employee class does not. I have an ArrayList of People, containing both Employees and Students. I want to sort the Students by GPA in descending order, but I'm struggling to make a sorting function when not all the objects in the list are of type Student.

4
  • 6
    Well, how do you want to sort the list? Should non-students be first, last, randomly ordered? Nov 8, 2023 at 16:08
  • 7
    The idea of sorting a list based on a property not all members makes me wonder if you have a bigger design problem. You haven't explained why you would want to do this but it seems fishy.
    – JimmyJames
    Nov 8, 2023 at 16:34
  • @JimmyJames You might for example want to sort cars, even if some are electric or hybrid and others have combustion engines. Just make sure the comparison is transitive, for example.
    – gnasher729
    Nov 8, 2023 at 16:38
  • @gnasher729 Sure, there are scenarios where you might actually want to do something like this but there might be more options such as filtering the list depending on the goal.
    – JimmyJames
    Nov 8, 2023 at 16:42

5 Answers 5

3

The “different member variables” is irrelevant. It’s an implementation detail. What you need is a set of rules which of two people comes first.

You could for example sort by family name, then given name, then date of birth, and if these are all three equal, take the name of the school, university or company (which will be different member variables) and compare them as strings. If that is equal, you might have student and employee ids, and the student ids might be unique, and the employee ids might be unique, but student and employee ids might be the same. So you could sort then students first ordered by id, followed by employees sorted by id, if you might sort by if first if student and employee ids are comparable.

(University or school and employer might be the same, because universities are also employers).

3

Comparing objects with different fields sounds like bad polymorphic design, whether it's Java or any other OOP language:

  • If your comparator needs to know the precise subtype of an object to do the comparison, you mess-up with the the open-closed principle, since for every new subclassing, you'd potentially need to modify the comparator to select the relevant fields.
  • If your comparator needs uses reflexion to find on its own the relevant fields to compare, you indirectly mess up with the principle of encapsulation, since you create a hidden requirement that information to be compared must be in some predetermined field.

If you want to sort People properly in a clean polymorphic design:

  • you need to rely either on a field, available for any kind of People, including Student, or
  • you may call some function/transformation that provides a unique value (e.g. a string) that allows to sort any People. People and Student may then just use a different transformation that will be passed to the comparator; Or
  • you only sort among homogeneous subtypes.
2

Java has two interfaces that can be used for sorting (Comparator<T> and Comparable<T>) the first compares two objects of the same type (T) the second can be implemented by a class to provide its natural sort order.

The Crux of the problem is "What is T" in your example, there are two logical choices: Student or People.

If you choose Student it dramatically simplifies the sorting logic since you know each entry in the list has a GPA, so you can simply compare the GPAs in order to determine the overall sort order. However you will first have to filter your People list to retrieve only Students before you can sort them.

If you choose People you can defined a base sort order by implementing Comparable<People> in the People class, i.e.:

@Override
public int compareTo(@NotNull People person) {
    return this.getClass().getName().compareTo(person.getClass().getName());
}

In this case, I am sorting based on the name of the class, so all Employees would come before Students

Then you can override the compareTo in the child classes for example Student might be:

@Override
public int compareTo(@NotNull People person) {
    if (person instanceof Student) {
        return Float.compare(this.gpa, ((Student) person).gpa);
    }

    return super.compareTo(person);
}

The first line checks the other person is also a student - if so it can compare gpa's - if not we are comparing a student to a non student so we simply fall through to the base class comparator.

You would then implement similar methods in any other subclasses to sort them appropriately.

--

Note: It is possible to do the entire logic in the base class comparator, however I felt it was a bit clearer, if I split out the logic into each class so you can see what each is doing.

0

If you don't have any requirement as to the order of the non-students, you can pick an arbitrary one.

You might enquire if there are any requirements for distinguishing among students with equal gpa and/or non students, although for a homework task you probably don't need to.

class ByGPAComparator : Comparator<People> {
    private static Comparator<double> gpaComparator = Comparator<double>.naturalOrder();
    private static Comparator<bool> classComparator = Comparator<bool>.naturalOrder();

    int compare(People a, People b) {
        bool isAStudent = a instanceof Student;
        bool isBStudent = b instanceof Student;

        if (isAStudent && isBStudent) {
            return gpaComparator.compare(((Student)a).GPA, ((Student)b).GPA);
        }

        return classComparator.compare(isAStudent, isBStudent); 
    }
}
6
  • I don't think you meant classComparator should compare bools. Also not sure GPA should be a double. It does funny things to decimal numbers. Nov 8, 2023 at 16:45
  • 1
    @candied_orange I did, as it orders all non-students as equivalent. The questionable double is from the question.
    – Caleth
    Nov 8, 2023 at 16:47
  • that results in non-students not being sorted against non-students at all. Nov 8, 2023 at 16:50
  • @candied_orange yes, exactly
    – Caleth
    Nov 8, 2023 at 16:53
  • Sounds like calling a bug a feature. I'd prefer a design that supported a default sort for non-students if it was ever created. Nov 8, 2023 at 16:55
0

Beyond the arguments supporting the comparison based on properties of different types following hint is to detail a possible implementation that uses java.lang.Comparable together with java.util.Comparator and static initialisers to register comparators.

The People class should implement java.lang.Comparable interface and in compareTo method it should compare People instances only when compared instances are of different classes otherwise it should use one of the comparators previously registered by the subclasses.

import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;

public class People<T extends People> implements Comparable<T> {

    protected static final Map<Class, Comparator> COMPARATORS = new HashMap<>();
    static {
        People.COMPARATORS.put(People.class, new Comparator<People>() {

            @Override
            public int compare(People o1, People o2) {
               
                // implementation of People comparison

                return 0;
            }
        });
    }
    
    protected Class<? extends People> thisClass;
    
    public People() {
        this.thisClass = this.getClass();
    }
    
    @Override
    public int compareTo(T o) {
        
        if ( thisClass == o.thisClass) { 
            return People.COMPARATORS.get(this.thisClass).compare(this, o);
        }
        
        return People.COMPARATORS.get(People.class).compare(this, o);
    }
}

The thisClass field of People class is added for performance wise reasons.

The Student class registers dedicated comparator using a static initialiser.

import java.util.Comparator;

public class Student extends People<Student> {

    static {
        People.COMPARATORS.put(Student.class, new Comparator<Student>() {

            @Override
            public int compare(Student o1, Student o2) {

                // implementation of Student comparison
                
                return 0;
            }});
    }
}

Same it does the Employee class.

import java.util.Comparator;

public class Employee extends People<Employee> {

    static {
        People.COMPARATORS.put(Employee.class, new Comparator<Employee>() {

            @Override
            public int compare(Employee o1, Employee o2) {

                // implementation of Employee comparison
                
                return 0;
            }});
    }
}

Now it is possible to write in some method for example:

List list = new ArrayList<>();

list.add(new Student());
list.add(new Employee());
list.add(new Student());
list.add(new Employee());

Set<? extends People> set = new TreeSet<>();
set.addAll(list);

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.