-3

so I have a problem with my generics code. I've created class Person and Student extends Person. When I was trying to compare both students it suddenly perceived it as a Person and group number wasn't even included. Here is my Person class

public class Person implements Comparable<Person>{...
        public int compareTo(Person o) {
        if(this.name.compareTo(o.name) > 0 )
            return 1;
        else if(this.name.compareTo(o.name) < 0)
            return -1;
        else{
            if(this.age > o.age)
                return 1;   
            else if(this.age < o.age)
                return -1;
            else{
                return 0;}
        }
}

My Student, where compareTo seems quite similar but with additional group id

public class Student extends Person implements Comparable<Person>{
      public int compareTo(Student s) {

    if(this.name.compareTo(s.name) > 0 ){
        System.out.println(this + " > " + s);
        return 1;}

    else if(this.name.compareTo(s.name) < 0){
        System.out.println(this + " < " + s);
        return -1;}

    else{
        if(this.age > s.age){
            System.out.println(this + " > " + s);
            return 1;}

        else if(this.age < s.age){
            System.out.println(this + " < " + s);
            return -1;}

        else{
            if(groupId > s.groupId){
                System.out.println(this + " > " + s);
                return 1;
            }
            else if(groupId < s.groupId){
                System.out.println(this + " < " + s);
                return -1;}
            else{
                System.out.println(this + " = " + s);
                return 0;}

        }   
            } 
        }

And finally a Box class that is using generics to find maximum and minimum of these classes and other types.

    public class Box<T extends Comparable> extends ArrayList<T> {

public T min() {
            T t = get(0);   

            for(int i = 0; i < this.size(); i++){
                    if(t.compareTo(get(i)) > 0)
                        t = super.get(i);
                    }   
            return t;
        }
    }

I haven't shown you entire code, but I think it is enough. I've made Box object and still it is comparing them as Person not as Students. Can someone explain what is the problem here? Edit: my Array

Box<Student> bst = new Box<>();
        bst.add(new Student("Nowacka", 24, 100));        
        bst.add(new Student("Nowacka", 24, 300));
        bst.add(new Student("Nowacka", 24, 200));
        bst.print();
        System.out.println(bst.max());                                          // Student: Nowacka, 24, 300

the answer should be 300, yet is 100 and in Person's compareTo i've added earliel some System.out.println to see which method it is using

Szadury
  • 3
  • 2
  • Show more of your `Student` class. How is `compareTo` method declared? – lexicore Apr 13 '18 at 19:12
  • And what makes you think it's comparing as `Person`, not `Student`s? What does your list contain, exactly? – lexicore Apr 13 '18 at 19:13
  • I think you will need to post [Minimal, Complete, and Verifiable Examples](http://stackoverflow.com/help/mcve). Judging from snippets of code is invidious, the error may be somewhere else. – lexicore Apr 13 '18 at 19:14
  • 1
    Inheritance doesn't work with Comparable. You should use Comparable only with the type of your class. In any other cases create Comparator and do whatever you need. – Bogdan Lukiyanchuk Apr 13 '18 at 19:22
  • I've edited my post, put some example and shown entire student's compareTo method – Szadury Apr 13 '18 at 19:24
  • @BogdanLukiyanchuk that is simply wrong. Inheritance does of course work with interfaces (and thus `Comparable`). The problem is that method calls are determined by the static type, not the runtime type. – Turing85 Apr 13 '18 at 19:25
  • if you want to shoot your leg, ofc it works – Bogdan Lukiyanchuk Apr 13 '18 at 19:26
  • @BogdanLukiyanchuk My task was to do this with Comparable and I've done it before, but completely forgot how it works. :P – Szadury Apr 13 '18 at 19:27

2 Answers2

1

Let us analyze what Comparable<T> actually needs and does. If SomeClass implements Comparable<T>, you are forced to implement a method public int compareTo(T other). The T will become important in a second, keep it int he back of your head. This is what you did with your Person-class:

public class Person implements Comparable<Person> {
    // ...
    public int compareTo(Person o) {
    if (this.name.compareTo(o.name) > 0 ) {
        return 1;
    } else if (this.name.compareTo(o.name) < 0) {
        return -1;
    }
    else {
        if (this.age > o.age) {
            return 1;   
        } else if (this.age < o.age) {
            return -1;
        } else {
            return 0;
        }
    }
}

Now, let's examine the Student-class:

public class Student extends Person implements Comparable<Person>{
    public int compareTo(Student s) { 
        ...
    }
}

We implement Comparable<Person>, but we do not have a public int compareTo(Person p), we only have a public int compareTo(Student s). So why does it get compiled? Because we inherit public int compareTo(Person p) from Person. The method public int compareTo(Student s) is actually never used, at least not in the context of the Comparable<Person> interface.

Well then, let us public class Student extends Person implements Comparable<Student>! Sadly, the compiler does not like this. We cannot implement the same generic interface multiple times with different type parameters. We are stuck. As soon as a Comparable is implemented along the object hierachy, we cannot re-implement it.

It is tempting to override the compareTo(...) method. This, however, will break the contract of Comparable:

The implementor must ensure sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) for all x and y. (This implies that x.compareTo(y) must throw an exception iff y.compareTo(x) throws an exception.)

The implementor must also ensure that the relation is transitive: (x.compareTo(y) > 0 && y.compareTo(z) > 0) implies x.compareTo(z) > 0.

Finally, the implementor must ensure that x.compareTo(y) == 0 implies that sgn(x.compareTo(z)) == sgn(y.compareTo(z)), for all z.

Let us try to not break the contract. A possible implementation for your Student class may look like this:

// CAUTION! This implementation is BROKEN! Keep reading to learn why.
public class Student extends Person {
    @Override
    public int compareTo(Person that) {
        int comp = super.compareTo(that);
        if (comp == 0 && that instanceof Person == true) {
            Student thatStuden = ((Student) that);
            comp = ...
        }
        return comp;
    }
}

But this solution breaks part of the contract of Comparable as well:

Finally, the implementor must ensure that x.compareTo(y)==0 implies that sgn(x.compareTo(z)) == sgn(y.compareTo(z)), for all z.

In a less formal way, this says "if two things x and y are equal (x.compareTo(y) == 0), then it does not matter whether I compare x or y to something else, the result must be the same (x.compareTo(z) == y.compareTo(z)1)."

How can we break it? Let us assume we have two Students s1 and s2 with s1.compareTo(s2) != 0. Let's assume further that we have a Person p3 with p3.compareTo(s1) == 0 and p3.compareTo(2) == 0. Because p3.compareTo(s1) == 0 and p3.compareTo(s2) == 0, the contract demands that s1.compareTo(s2) == 0. That is a contradiction.

There is an alternative in form of Comparator<T>. You separate the sort criteria from the actual object.

For your example, it might look like this:

public class StudentComparator implements Comparator<Student> {

    @Override
    public int compare(Student o1, Student o2) {
        int comp = o1.name.compareTo(o2.name);
        if (comp == 0) {
            comp = o1.age - o2.age;
            if (comp == 0) {
                comp = o1.groupId - o2.groupId;
            }
        }
        return comp;
    }
}

Acknowledgement

I initially thought that it should be possible to onverride compareTo(...) in a way that does not break the contract. But @user2357112 taught me the opposite =). Thank you for pointing out my two mistakes!


1I neglected the sgn(...) for readability.

Turing85
  • 18,217
  • 7
  • 33
  • 58
  • I initially thought we could use a `compareTo` implementation like what you suggest, but it still breaks the contract of `Comparable`, because two students could compare equal to a non-student Person and yet compare unequal to each other. There simply isn't any way for `Student` to change the comparison logic between students without breaking the contract. – user2357112 Apr 13 '18 at 20:02
  • The Comparator is the way to go. – user2357112 Apr 13 '18 at 20:03
  • Still broken. First, you're testing `that instanceof Person` and then casting `that` to `Student`. That's probably a typo. Even if you test `that instanceof Student`, though, it still fails in the same case I mentioned before; we could have `studentOne.compareTo(person) == 0` and `studentTwo.compareTo(person) == 0`, but `studentOne.compareTo(studentTwo) != 0`. – user2357112 Apr 13 '18 at 20:13
  • Thanks for your advice @Turing85. Your explanation was great. I have used the first solution and now it works properly. – Szadury Apr 13 '18 at 20:25
  • @Szadury I hope by "first solution", you mean the `Comparator` solution? All other "solution" break the contract of `Comparable` and thus are broken. – Turing85 Apr 13 '18 at 20:27
  • To be honest the program worked for me and I have no clue why it is "broken" – Szadury Apr 13 '18 at 20:28
  • @Szadury re-read my answer from the part that starts with "Let us try to not break the contract". – Turing85 Apr 13 '18 at 20:32
0

I think it's comparing them as Persons because you've implemented Comparable<Person> in the Student class:

public class Student extends Person implements Comparable<Person>

Try replacing with

public class Student extends Person implements Comparable<Student>
nvioli
  • 4,137
  • 3
  • 22
  • 38
  • Can't do that, because superclass Person already has Comparable and it cannot be implemented more than once and with different types – Szadury Apr 13 '18 at 19:23