简体   繁体   中英

Sorting an array in java with certain conditions

I have a class called Athlete which is a sublass of Human. In the class Human I implement the interface comparable and use the method compareTo in order to compare the ages of different athletes. In the athletes class I have an extra field called year which corresponds to the year the athlete started competing. In my main method in my program I have an arraylist that I add both Athletes and Humans. I would like to so that if an athlete is of the same age to sort according to the year the athlete started competing. I use instanceof to check in my class human if the instance is the object is of type Athlete but after that I don't know how to get it to work.

    public int compareTo(Human other)
    {

        if(this instanceof Athlete && other instanceof Athlete && this.age == other.age){
            return ;
        }
        return this.age - other.age;
    }
}

Use polymorphism , instead of the operator instanceof .

That is: overload the compareTo method in the Athlete class.

public int compareTo(Athlete other) {
//This method will be invoked if and only if you compare an athlete with another athlete
}

Also, consider that the equals method result should be consistent with the compareTo method results.

One possible solution is to add a compareTo method in the Athlete class also, something in the lines of (needs rewriting as I haven't been working on Java since a long time ago):

public int compareTo(Athlete other){
    int result = super.compareTo((Human)other);

    if(result == 0){
        return this.year - other.year;
    }

    return result;
}

As a code review, I'd say that the complete code should be something like the following:

Human.java

public int compareTo(Human other){
    return age - other.age;
}

Athlete.java

@Override
public int compareTo(Human other){
    if(other instanceof Athlete){
        return compareTo((Athlete)other);
    }

    return super.compareTo(other);
}

public int compareTo(Athlete other){
    int result = super.compareTo((Human)other);

    if(result == 0){
        return this.year - other.year;
    }

    return result;
}

Using your example, you could just compare the year as well:

public int compareTo(Human other)
{

    if(this instanceof Athlete && other instanceof Athlete && this.age == other.age){
        String tYear = ((Athlete)this).getYear();
        String oYear = ((Athlete)other).getYear();
        int tYearInt = 0;
        int oYearInt = 0;
        try {
           tYearInt = Integer.parseInt(tYear);
           oYearInt = Integer.parseInt(oYear);
        } catch (Exception e){
           e.printStackTrace();
        }
        return  tYearInt - oYearInt;
    }
    return this.age - other.age;
}

However, having said that, please consider @Andres answer, anytime you use instanceof, you should question whether your design is wrong.

Like Andres said, use polymorphism. Here is how to do that:

First of all, this instanceof Athlete in the Human class is not good style, because from the perspective of the Human class, Athlete is a subclass and referencing subclasses can lead to problems in certain cases. Instead, put another compareTo() method into the Athlete class. If Athlete.compareTo() gets called, you already know that this is of type Athlete and if you want to compare the year field, you only have to check if other instanceof Athlete , which is ok, because now we are in the perspective of the Athlete class and from here, Athlete is not a subclass.

That said, in the Athlete class, use this:

public int compareTo(Human other) {
    int humanComp = super.compareTo(other);
    if (humanComp != 0){
        return humanComp;
    }

    if (other instanceof Athlete) {
        return ((Athlete)other).year - this.year;
    }

    return 0;
}

This solution first uses Human.compareTo() (called with super.compareTo(other) ) to check if the Human class already knows how to order our instances this and other . If not, ie if this call returns 0, we have to go on with comparing more details, in our case the year field.
Because we used Human.compareTo() , we have to make sure it exists in the Human class and that it works properly:

public int compareTo(Human other) {
    return this.age - other.age;
}

This one simply compares by age , because that's the only field in the Human class we know we can use for comparison.

The documentation for compareTo says:

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

Your proposed method does not meet this requirement. For example, suppose

x = Athlete, age = 35, start date = 2000
y = Human,   age = 35
z = Athlete, age = 35, start date = 2001

In this example

x.compareTo(y) == 0   // y is not an athlete
x.compareTo(z) < 0    // Both athletes, different start dates.
y.compareTo(z) == 0   // y is not an athlete

If you do not obey the contract for Comparable , the behaviour of Arrays.sort or Collections.sort is unspecified. Ideally you'd get an exception, but the trouble is these sorting methods use different algorithms depending on the size of the array or list, and you are more likely to get an exception thrown for an incorrect compareTo method if the input array or list has a large number of elements . This means that you should test your compareTo method very carefully using long randomly generated arrays, otherwise you may have subtle, hard-to-detect bugs in your code.

A correct compareTo method looks something like this:

public int compareTo(Human other) {
    int result = Integer.compare(age, other.age);
    if (result != 0)
        return result;
    if (!(this instanceof Athlete))
        return other instanceof Athlete ? -1 : 0;
    return other instanceof Athlete 
               ? Long.compare(((Athlete) this).startDate(), ((Athlete) other).startDate()) 
               : 1;
}

This method sorts first by age. If two humans have the same age they are sorted first by type (with athletes coming first). Athletes with the same age are sorted by start date. Non-athletes with the same age do not appear in any particular order.

Finally, note that it is generally better to use polymorphism rather than instanceof . The problem here is that Human implements Comparable<Human> . Therefore the compareTo method must accept a Human , not an Athlete . Even if you override compareTo in Athlete , the argument must be a Human , and you'd have to use instanceof to check the type of the argument anyway (as in @GentianKasa's answer) or write a completely separate method compareToAthlete(Athlete athlete) and do the following in Athlete

@Override 
public int compareTo(Human human) {
    return -human.compareToAthlete(this);  // Note the minus sign!
}

compareToAthlete would need two versions as well. While this works, it means that the logic of the compareTo method is spread over four methods, making it harder to reason about its correctness. In this case, I'd hold my nose and use instanceof .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM