Using Comparable for multiple dynamic fields of VO

2019-01-07 19:06发布

问题:

I have a class

public class StudentVO {
   int age;
   String name;  
}

I used the same class in two different areas. At one place i need to sort based on the age. In another place I need to sort based on the name and in another place i may need sorting based on both age and name. How can I do this? If one field I can override compareTo().

Is it possible to do this?

回答1:

1)You should write two Comparator for sorting on age and name separately, and then use the Collections.sort(List,Comparator). Something like this:

class StudentVO {
  private String name;
  private int age;
  public String getName() {
      return name;
  }
  public void setName(String name) {
      this.name = name;
  }
  public int getAge() {
      return age;
  }
  public void setAge(int age) {
      this.age = age;
  }
}

class AgeComparator implements Comparator<StudentVO> {

@Override
public int compare(StudentVO o1, StudentVO o2) {
    Integer age1 = o1.getAge();
    Integer age2 = o2.getAge();
    return age1.compareTo(age2);
  }

}

class NameComparator implements Comparator<StudentVO> {

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

}

And then use them, To sort based on age:

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

to sort based on name:

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

2) If you think that the List of StudentVO has some natural order of sorting, say suppose sort by age. Then, use Comparable for age and Comparator for name.

 class StudentVO implements Comparable<StudentVO>{
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public int compareTo(StudentVO o) {
        return ((Integer)getAge()).compareTo(o.getAge());
    }
}

class NameComparator implements Comparator<StudentVO> {

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

 }

And then use them, To sort based on age:

Collections.sort(list);

to sort based on name:

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


回答2:

There is new approach for this in java-8 see Comparator#comparing and Comparator#thenComparing. All you need is to provide a lamda expression/method reference either to Stream#sorted() or List#sort() method.

For example sorting by one field:

List<StudentVO> students = Arrays.asList(
        new StudentVO(20,"Bob"),
        new StudentVO(19, "Jane")
);
// sort by age
students.stream()
        .sorted(Comparator.comparing(StudentVO::getAge))
        .forEach(System.out::println);
// [StudentVO{age=19, name='Jane'},StudentVO{age=20, name='Bob'}]
// sort by name
students.stream()
        .sorted(Comparator.comparing(StudentVO::getName))
        .forEach(System.out::println);
// [StudentVO{age=20, name='Bob'}, StudentVO{age=19, name='Jane'}]

Sorting by a few fields:

List<StudentVO> students = Arrays.asList(
        new StudentVO(20,"Bob"),
        new StudentVO(19, "Jane"),
        new StudentVO(21,"Bob")
);
// by age and then by name
students.stream()
        .sorted(Comparator
                .comparing(StudentVO::getAge)
                .thenComparing(StudentVO::getName)
        ).forEach(System.out::println);
// [StudentVO{age=19, name='Jane'}, StudentVO{age=20, name='Bob'}, StudentVO{age=21, name='Bob'}]
// by name an then by age
students.stream()
        .sorted(Comparator
                .comparing(StudentVO::getName)
                .thenComparing(StudentVO::getAge)
        ).forEach(System.out::println);
// [StudentVO{age=20, name='Bob'}, StudentVO{age=21, name='Bob'}, StudentVO{age=19, name='Jane'}]


回答3:

Here is the code snippet:

public class StudentNameComparator implements Comparator<StudentVO>{

@Override
public int compare(StudentVO s1, StudentVO s2) {

        //ascending order
        return s1.getName().compareTo(s2.getName());

        //descending order
        //return s2.getName().compareTo(s1.getName());
       }
}

According to your question, it will also work when values of specified field will change. You have only to remember to call sort method with this comparator.



回答4:

In java, you have two main way to compare objects. The first is for the class itself to implement the Comparable interface which will mean only one implementations. The second is to have classes implements Comparator interface. This way, you can have multiple comparators for the same class.

This mean that you could define for exemple 3 diffenrent Comparators on your StudentVo class : one that compare only on the name, another that compare the ages and the last one that both properties.

In your application, you use the implementation that suit you need based on what you want to compare. In one place, you will compare students on age Collections.sort(myStudents , new CompareStudentOnAge()). In another place, you use another implementation.

You can find some explanations in this blog post : http://javarevisited.blogspot.fr/2011/06/comparator-and-comparable-in-java.html



回答5:

I recently had to solve this problem, too. Not sure if this is exactly the same scenario as yours, but I had to write an in-memory sort for zero or more columns of a grid, handwaving over OOM conditions, etc, because my problem was very limited in scope.

I wrote a comparator for each column and a comparator that took a list of comparators. As I identified which columns needed to be sorted and in what order, I added an instance of the corresponding comparator to the list of comparators. Then, use the chained comparator to execute the actual sort.

public class MyObject
{
    private String name;
    private int age;
    private Date registered;
}

So, something like this for each comparator:

public class NameComparator
    implements Comparator<MyObject>
{
    public int compare(MyObject o1, MyObject o2)
    {
        return o1.getName().compareTo(o2.getName);
    }
}

This for the chained comparator:

public class ChainedComparator
    implements Comparator<MyObject>
{
    public int compare(MyObject o1, MyObject o2) {
        for(Comparator<MyObject> comparator : comparators) {
            int result = comparator.compare(o1,o2);
            if(result != 0) {
                return result;
            }
        }
        return 0;
    }
}
    private List<Comparator<MyObject>> comparators = new ArrayList<>();
}

Left to your imagination is parsing the sorts and building the chained comparator. I actually made this a bit more complicated because I also incorporated a direction which I implemented by swapping the order of parameters in the call to the sub-comparator in the chained comparator as needed.