I have multiple classes, each one extending the other:
class Person {
private String name;
private int age;
}
class Employee extends Person {
private double salary;
}
class Developer extends Employee {
private double experienceYears;
}
And a comparator class for each of the classes, sorting first by name, then age and so on. How can I sort a single array, containing Persons, Employees and Developers, using these comparators?
I tried using a sort method in a separate class like this:
class SortingUtil {
public static void sortPeople(Person[] array, Comparator<T extends Person> comparator) {
Arrays.sort(array, comparator);
}
}
But I seem to be making some mistake with generics and got stuck at this point.
Comparator classes are as follows:
class PersonComparator implements Comparator<Person>{
@Override
public int compare(Person o1, Person o2) {
if(o1.getName().compareTo(o2.getName())!=0)
return o1.getName().compareTo(o2.getName());
else return o1.getAge() - o2.getAge();
}
}
class EmployeeComparator implements Comparator<Employee>{
@Override
public int compare(Employee o1, Employee o2) {
if(o1.getName().compareTo(o2.getName())!=0)
return o1.getName().compareTo(o2.getName());
else if (o1.getAge() - o2.getAge()!=0)
return o1.getAge() - o2.getAge();
else return o1.getSalary() - o2.getSalary();
}
}
class DeveloperComparator implements Comparator<Developer>{
@Override
public int compare(Developer o1, Developer o2) {
if(o1.getName().compareTo(o2.getName())!=0)
return o1.getName().compareTo(o2.getName());
else if (o1.getAge() - o2.getAge()!=0)
return o1.getAge() - o2.getAge();
else if (o1.getSalary() - o2.getSalary()!=0)
return o1.getSalary() - o2.getSalary();
return o1.getLevel().compareTo(o2.getLevel());
}
}
>Solution :
Given a Person[]
array, java knows that every item in that array could be a Person. Or an Employee. Or a Developer.
Let’s say you pass a Person[]
array where every element in it, is a Developer. Okay, that’s possible, but you don’t have to – and java is not going to let you abuse the type system simply because ‘well, maaaaybe it will work’.
Thus, to sort a Person
array, the only comparator that will do is a Comparator<Person>
, or a Comparator<Object>
.
A Comparator<Person>
can already compare 2 developers – because all developers are persons. In contrast, a Comparator<Developer>
can only compare developers; it is not capable of comparing 2 instances created with new Person()
, nor can it sort Employee objects.
In contract, a Comparator<X extends Person>
is heading in the direction that you can accept either a Comparator<Person>
, or a Comparator<Developer>
, or a Comparator<Employee>
, and that’s no good – because what if you pass a Comparator<Developer>
and that Person[]
array contains some employees?
And a comparator class for each of the classes
This doesn’t make sense. If you already have a comparator that can compare any 2 persons, then there is no further need for a comparator for developers, or for employees. Given that all employees and all developers are also persons, it doesn’t matter.
You appear to be looking for, perhaps, ‘reverse types’, where your Person comparator is capable only of comparing specifically instances of Person itself (i.e. stuff made as new Person()
), and is not capable of comparing objects created as new Developer()
. Java does not work that way – you said class Employee extends Person
, therefore, all Employees are also Persons, and there is no way to say ‘I have a comparator that can compare all X, but not any subtypes of X’.
Hence, your mental model doesn’t work here and your question is not directly answerable because of it.
You have two options:
Heterogenous arrays
You have a single array that contains a combo of persons, developers, and employees all mixed and matched. The only way to sort it, is to have a comparator that can compare 2 persons (which could be new Person()
, or new Developer()
, or new Employee()
– those are all persons!).
Homogenous arrays
You have a single array and it is in fact bound to contain only Xs, where X is either Person or Employee or Developer. There is no way to say ‘I have an array with SOLELY objects created with new Person()
and not objects created as new Developer()
, so if it’s Person[]
, we’re in the same boat as the heterogenous array situation. However, it’s possible you want a way to say: Given an array of Xs, and a Comparator of that same X, sort em.
You can do that. But, you are confused about generics syntax.
Type var declaration vs usage
You wrote:
public static void sortPeople(Person[] array, Comparator<T extends Person> comparator) { Arrays.sort(array, comparator); }
T is being used, but you declared it nowhere, hence, this code makes no sense. Given that it’s a static method, there are no type vars available except those you declare. So, you have to declare them. You put bounds in as you declare, usually. You CAN do this:
public static <T extends Person> void sortPeople(T[] array, Comparator<T> comparator) {
Arrays.sort(array, comparator);
}
Here we first declare a new type var named T
(you put these after the modifiers and before the return type, it’s the <T extends Person>
part there), and, once declared, we use it – twice, once for the array type, once for the comparator. This code works fine, and you can invoke it with:
Developer[] devs = ...;
Comparator<Developer> c = ...;
SortingUtil.sortPeople(devs, c);
// or
Person[] persons = ...;
Comparator<Person> p = ...;
SortingUtil.sortPeople(persons, p);
// BUT NOT:
Person[] persons = new Person[2];
persons[0] = new Developer();
persons[1] = new Developer();
Comparator<Developer> c = ...;
SortingUtil.sortPeople(persons, c);
That last one does not work – your eyeballs can tell that this person array contains only developers, but the type system doesn’t say that. The type system says that persons array could contain an employee too, or a new Person()
, and your comparator can’t compare those, so that code does not compile.
An important note
Arrays and generics do not mix. Do not do this. Do not use arrays of non-primitive types. Use a List<Person>
instead of a Person[]
.
The common confusion about generics
You may be thinking: I have a list of persons. Actually, it’s not JUST new Person()
, it could be new Developer()
too, so I want a List<? extends Person>
, not a List<Person>
right?
No. You’re missing that generics is one abstraction layer higher. A list that contains all of new Person()
, new Developer()
, and new Employee()
is a List<Person>
, not a List<? extends Person>
– all developers are persons, after all! There is no way to express ‘a list that contains only new Person()
and not new Developer()
.
No, a List<? extends Person>
expresses the idea that the list ITSELF is bound to contain Persons (which also means developers or employees, all mixed together, if you want), OR only developers, OR only employees. You’re capturing the notion that you want this variable or parameter to allow containing any of new ArrayList<Person>()
, new ArrayList<Developer>()
, as well as new ArrayList<Employee>()
, and restricts you to write only code that would work for all 3 of those.
For example, a List<? extends Person>
has no add method at all!! – or rather, it does, but there is NOTHING you could pass that wouldn’t be a compiler error, except literally list.add(null)
because null
fits all types. After all, it COULD be a List<Employee>
, and it COULD be a List<Developer>
, and there isn’t any object that can be both an employee and a developer at the same time, therefore, there is simply no way to add anything.
This is great if you are writing a method that isn’t going to add stuff anyway.
Read up on PECS, that’ll help.