How to sort a list by a private field?

こ雲淡風輕ζ 提交于 2020-04-29 10:22:17

问题


My entity class looks like this:

public class Student {

   private int grade;

   // other fields and methods
 }

and I use it like that:

List<Student> students = ...;

How can I sort students by grade, taking into account that it is a private field?


回答1:


You have these options:

  1. make grade visible
  2. define a getter method for grade
  3. define a Comparator inside Student
  4. make Student implement Comparable
  5. use reflection (in my opinion this is not a solution, it is a workaround/hack)

Example for solution 3:

public class Student {
    private int grade;

    public static Comparator<Student> byGrade = Comparator.comparing(s -> s.grade);
}

and use it like this:

List<Student> students = Arrays.asList(student2, student3, student1);
students.sort(Student.byGrade);
System.out.println(students);

This is my favorite solution because:

  • You can easily define several Comparators
  • It is not much code
  • Your field stays private and encapsulated

Example of solution 4:

public class Student implements Comparable {
    private int grade;

    @Override
    public int compareTo(Object other) {
        if (other instanceof Student) {
            return Integer.compare(this.grade, ((Student) other).grade);
        }
        return -1;
    }
}

You can sort everywhere like this:

List<Student> students = Arrays.asList(student2, student3, student1);
Collections.sort(students);
System.out.println(students);

Aspects of this solution:

  • This defines, that sorting by grade represents the natural order of students
  • Some preexisting methods will automatically sort (like TreeMap)



回答2:


In general, if you need a behaviour that depends on student's grade, than this information must be accessible - add a method or a property that allows other code to access it.

A simplest fix would be thus:

public class Student implements IStudent {

    ...
    private int grade;
    ...
    // other fields and methods

    public int getGrade() {
        return grade;
    }
}

You should probably extend the interface IStudent as well :)

However, if you need this only for sorting you can go with an idea already suggested in other answers: implement Comparable interface. This way you can keep the grade hidden, and use it inside the int compareTo method.




回答3:


An option provided by JDK 1.8 is using stream library sorted()method which does not require Comparable interface to be implemented. You need to implement accessor (getter) method for field grade

public class Student {

private int grade;

public int getGrade() {
    return grade;
}

public Student setGrade(int grade) {
    this.grade = grade;
    return this;
}}

Then given having the unsortedStudentList you are able to sort it like the below code:

List<Student> sortedStudentList = unsortedStudentList
             .stream()
             .sorted(Comparator.comparing(Student::getGrade))
             .collect(Collectors.toList());

Also, sorted() method enables you to sort students based on other fields (if you have) as well. For instance, consider a field name for student and in this case you'd like to sort studentList based on both grade and name. So Student class would be like this:

public class Student {

private int grade;
private String name;

public int getGrade() {
    return grade;
}

public Student setGrade(int grade) {
    this.grade = grade;
    return this;
}

public String getName() {
    return name;
}

public Student setName(String name) {
    this.name = name;
    return this;
}} 

To sort based on both fields:

 List<Student> sortedStudentList = unsortedStudentList
              .stream()
              .sorted(Comparator.comparing(Student::getGrade)
              .thenComparing(Comparator.comparing(Student::getName)))
              .collect(Collectors.toList());

Second comparator comes into play when the first one is comparing two equal objects.




回答4:


Implement Comparable interface for the Student class and implement the method int compareTo(T o). This way you can keep the grade property private.




回答5:


Your class could implement the Comparable interface. Then, you can easily sort the list:

public class Student implements IStudent, Comparable<Student>
{
  ...

  private int grade;
  ...

  @Override
  public int compareTo(Student other)
  {
    return (grade - other.grade);
  }

}

public class Section
{
  private List<IStudent> studentsList;

  ...

  public void sortStudents()
  {
    studentsList.sort(null);
  }

}



回答6:


If you really need to sort by field you don't have access to you can use reflection:

private static int extractGrade(Student student) {
    try {
        Field field = Student.class.getDeclaredField("grade");
        field.setAccessible(true);
        return field.getInt(student);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

public static void main(String[] args) {
    Comparator<Student> studentComparator = Comparator.comparingInt(DemoApplication::extractGrade);
    List<Student> students = Arrays.asList(new Student(1), new Student(10), new Student(5));
    students.sort(studentComparator);
}

But I have to say that this method is kinda unsafe.

Don't use it unless absolutely necessary. It is better to give access to given field using getter method for example.

Also you might get issues if you're running this code on modulepath against Java 9+ (you can get InaccessibleObjectException thrown).

About implementing Comparable

From Comparable docs:

This interface imposes a total ordering on the objects of each class that implements it. This ordering is referred to as the class's natural ordering, and the class's {@code compareTo} method is referred to as its natural comparison method.

But what might be a natural ordering for Student? First name? Last name? Their combination?

It's easy to answer this question for numbers, but not for classes like Student.

So I don't think that Students should be Comparable, they are humans and not dates or numbers. And you can't say who is greater, who is equal and who is lesser.




回答7:


Another option that was mentioned before but not shown as an example is implementing a special Comparator for a comparison by grade.

This example consists of a class Student implementing an interface IStudent, a StudentGradeComparator and a little class Main that uses sample data.

Further explanations are given as code comments, please read them

/**
 * A class that compares students by their grades.
 */
public class StudentGradeComparator implements Comparator<IStudent> {

    @Override
    public int compare(IStudent studentOne, IStudent studentTwo) {
        int result;
        int studentOneGrade = studentOne.getGrade();
        int studentTwoGrade = studentTwo.getGrade();

        /* The comparison just decides if studentOne will be placed
         * in front of studentTwo in the sorted order or behind
         * or if they have the same comparison value and are considered equal
         */
        if (studentOneGrade > studentTwoGrade) {
            /* larger grade is considered "worse", 
             * thus, the comparison puts studentOne behind studentTwo
             */
            result = 1;
        } else if (studentOneGrade < studentTwoGrade) {
            /* smaller grade is considered "better"
             * thus, the comparison puts studentOne in front of studentTwo
             */
            result = -1;
        } else {
            /* the students have equal grades,
             * thus, there will be no swap 
             */
            result = 0;
        }

        return result;
    }
}

You can apply this class in the sort(Comparator<? super IStudent> comparator) method of a List:

/**
 * The main class for trying out the sorting by Comparator
 */
public class Main {

    public static void main(String[] args) {
        // a test list for students
        List<IStudent> students = new ArrayList<IStudent>();

        // create some example students
        IStudent beverly = new Student("Beverly", 3);
        IStudent miles = new Student("Miles", 2);
        IStudent william = new Student("William", 4);
        IStudent deanna = new Student("Deanna", 1);
        IStudent jeanLuc = new Student("Jean-Luc", 1);
        IStudent geordi = new Student("Geordi", 5);

        // add the example students to the list
        students.add(beverly);
        students.add(miles);
        students.add(william);
        students.add(deanna);
        students.add(jeanLuc);
        students.add(geordi);

        // print the list in an unordered state first
        System.out.println("———— BEFORE SORTING ————");
        students.forEach((IStudent student) -> {
            System.out.println(student.getName() + ": " + student.getGrade());
        });

        /*---------------------------------------*
         * THIS IS HOW YOU APPLY THE COMPARATOR  *
         *---------------------------------------*/
        students.sort(new StudentGradeComparator());

        // print the list ordered by grade
        System.out.println("———— AFTER SORTING ————");
        students.forEach((IStudent student) -> {
            System.out.println(student.getName() + ": " + student.getGrade());
        });
    }
}

Just for completeness, here are the interface IStudent and its implementing class Student:

public interface IStudent {

    String getName();
    int getGrade();

}


/**
 * A class representing a student
 */
public class Student implements IStudent {

    private String name;
    private int grade;

    public Student(String name, int grade) {
        this.name = name;
        this.grade = grade;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getGrade() {
        return grade;
    }

    public void setGrade(int grade) {
        this.grade = grade;
    }

}



回答8:


Getters are not a bad practice, they are exactly made for your problem: Accessing private fields to read them.
Add a getter and you can do:

studentsList.stream().sorted((s1, s2) -> s1.getGrade()compareTo(s2.getGrade)).collect(Collectors.toList())  

Update: If you really want to keep the grade private, you need to implement Comparable and override the compare-method.




回答9:


You can do it this way when u want to keep grade private:

students = students.stream().sorted((s1, s2) -> {
        try {
            Field f = s1.getClass().getDeclaredField("grade");
            f.setAccessible(true);
            int i = ((Integer)f.getInt(s1)).compareTo((Integer) f.get(s2));
            f.setAccessible(false);
            return i;
        } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
            e.printStackTrace();
        }
        return 0;
    }).collect(Collectors.toList());



回答10:


I belive the best option here is to create a Comparator where the sorted list is needed, because you may need to sort by other fields in other places and that would inflate your domain class:

List<Student> sorted = list.stream()
    .sorted(Comparator.comparingInt(o -> o.grade))
    .collect(Collectors.toList());


来源:https://stackoverflow.com/questions/52149721/how-to-sort-a-list-by-a-private-field

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!