如何按私有字段对列表进行排序?

How to sort a list by a private field?

我的实体 class 如下所示:

public class Student {

   private int grade;

   // other fields and methods
 }

我是这样使用它的:

List<Student> students = ...;

如何根据 gradestudents 进行排序,考虑到它是私有字段?

一般来说,如果您需要一种取决于学生成绩的行为,则必须可以访问此信息 - 添加允许其他代码访问它的方法或 属性。

最简单的修复方法是:

public class Student implements IStudent {

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

    public int getGrade() {
        return grade;
    }
}

您或许还应该扩展接口 IStudent :)

但是,如果您只需要它来进行排序,您可以采用其他答案中已经提出的想法:实现 Comparable 接口。这样你就可以隐藏 grade,并在 int compareTo 方法中使用它。

Getters 不是一个坏习惯,它们正是为您的问题而设计的:访问私有字段以读取它们。
添加一个 getter 你可以这样做:

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

更新:如果你真的想将成绩保密,你需要实施Comparable并覆盖比较方法。

您有以下选择:

  1. 使 grade 可见
  2. grade
  3. 定义一个getter方法
  4. 定义一个Comparator里面Student
  5. 制作Student实施Comparable
  6. 使用反射(在我看来这不是解决方案,而是解决方法/破解)

解决方案示例 3

public class Student {
    private int grade;

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

并像这样使用它:

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

这是我最喜欢的解决方案,因为:

  • 您可以轻松定义多个 Comparators
  • 代码不多
  • 您的字段保持私有和封装

解决方案示例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;
    }
}

您可以像这样到处排序:

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

此解决方案的方面:

  • 这定义了,按 grade 排序代表 学生的自然顺序
  • 一些预先存在的方法会自动排序(如TreeMap

为学生 class 实施 Comparable interface 并实施方法 int compareTo(T o)。这样您就可以将成绩 属性 保密。

您的 class 可以实现 Comparable 接口。然后,您可以轻松地对列表进行排序:

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);
  }

}

之前提到但未作为示例显示的另一个选项是实施特殊 Comparator 以按等级进行比较。

这个例子包括一个 class Student 实现一个接口 IStudent,一个 StudentGradeComparator 和一个使用 class Main示例数据。

进一步解释为代码注释,请阅读

/**
 * 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;
    }
}

您可以在 List:

sort(Comparator<? super IStudent> comparator) 方法中应用此 class
/**
 * 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());
        });
    }
}

为了完整起见,这里是接口 IStudent 及其实现 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;
    }

}

JDK 1.8 提供的一个选项正在使用 streamsorted() 方法,不需要实现 Comparable 接口。 您需要为字段 grade

实现访问器 (getter) 方法
public class Student {

private int grade;

public int getGrade() {
    return grade;
}

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

然后给定 unsortedStudentList,您可以像下面的代码一样对其进行排序:

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

此外,sorted() 方法还允许您根据其他字段(如果有)对学生进行排序。例如,考虑一个用于学生的字段 name,在这种情况下,您希望根据年级和姓名对 studentList 进行排序。所以 Student class 会像这样:

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;
}} 

根据两个字段排序:

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

当第一个比较器比较两个相等的对象时,第二个比较器开始起作用。

如果您确实需要按您无权访问的字段排序,您可以使用 反射:

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);
}

但是不得不说这种方法有点不安全

除非绝对必要,否则不要使用它。例如,最好使用 getter 方法访问给定字段。

如果您 运行 模块路径上的此代码针对 Java 9+,您也可能会遇到问题(您可能会抛出 InaccessibleObjectException)。

关于实施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.

但是 Student 自然排序 可能是什么? 名?姓?他们的组合?

这个问题对于数字很容易回答,但对于 类 就不行了,比如 Student

所以我认为Student不应该是Comparable,他们是人而不是日期或数字。而且不能说谁大谁小谁平等

我认为这里最好的选择是在需要排序列表的地方创建一个 Comparator,因为您可能需要在其他地方按其他字段排序,这会使您的域膨胀 class :

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

如果你想将成绩保密,你可以这样做:

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());