Как отсортировать список по частному полю?

Мой класс сущности выглядит следующим образом:

public class Student {

   private int grade;

   // other fields and methods
 }

и я использую его так:

List<Student> students = ...;

Как я могу сортировать students по grade, принимая во внимание, что это личное поле?

Ответ 1

У вас есть следующие варианты:

  1. сделать grade видимым
  2. определить метод геттера для grade
  3. определить Comparator внутри Student
  4. сделать Student реализацию Comparable
  5. использовать отражение (на мой взгляд это не решение, это обходной путь/хак)

Пример для решения 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);

Это мое любимое решение, потому что:

  • Вы можете легко определить несколько Comparator s
  • Это не много кода
  • Ваше поле остается закрытым и инкапсулированным

Пример решения 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)

Ответ 2

В общем, если вам нужно поведение, зависящее от уровня ученика, эта информация должна быть доступна - добавьте метод или свойство, которое позволяет другому коду получить к нему доступ.

Простейшим решением будет:

public class Student implements IStudent {

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

    public int getGrade() {
        return grade;
    }
}

Вероятно, вы должны расширить интерфейс IStudent :)

Однако, если вам это нужно только для сортировки, вы можете пойти с идеей, уже предложенной в других ответах: реализовать интерфейс Comparable. Таким образом, вы можете сохранить grade скрытым и использовать его внутри метода int compareTo.

Ответ 3

Параметр, предоставляемый JDK 1.8, использует метод sorted() библиотеки stream который не требует реализации интерфейса Comparable. Вам необходимо реализовать метод доступа (getter) для grade поля

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 класс будет выглядеть так:

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

Второй компаратор вступает в игру, когда первый сравнивает два равных объекта.

Ответ 4

Внедрите интерфейс Comparable для класса Student и реализуйте метод int compareTo(T o). Таким образом, вы можете сохранить свойство класса приватным.

Ответ 5

Ваш класс может реализовать интерфейс 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);
  }

}

Ответ 6

Если вам действительно нужно сортировать по полю, к которому у вас нет доступа, вы можете использовать отражение:

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

Но я должен сказать, что этот метод небезопасен.

Не используйте его, если это абсолютно необходимо. Лучше дать доступ к заданному полю, например, с помощью метода геттера.

Также вы можете получить проблемы, если вы используете этот код на модульном пути против Java 9+ (вы можете получить бросок InaccessibleObjectException).

О внедрении Comparable

Из сопоставимых документов:

Этот интерфейс накладывает полный порядок на объекты каждого класса, который его реализует. Это упорядочение называется естественным упорядочением класса, а метод класса {@code compareTo} называется его естественным методом сравнения.

Но что может быть естественным заказом для Student? Имя? Фамилия? Их комбинация?

Легко ответить на этот вопрос для чисел, но не для таких классов, как Student.

Поэтому я не думаю, что Student должен быть Comparable, это люди, а не даты или цифры. И вы не можете сказать, кто больше, кто равен и кто меньше.

Ответ 7

Другой вариант, который упоминался ранее, но не показан в качестве примера, представляет собой специальный Comparator для сравнения по классам.

Этот пример состоит из класса Student реализующего интерфейс IStudent, StudentGradeComparator и небольшой класс 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;
    }
}

Вы можете применить этот класс к методу sort(Comparator<? super IStudent> comparator) 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());
        });
    }
}

Для полноты, вот интерфейс IStudent и его реализующий класс 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 - не плохая практика, они точно созданы для вашей проблемы: доступ к закрытым полям для их чтения.
Добавьте геттер, и вы можете сделать:

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

Обновление. Если вы действительно хотите сохранить класс приватным, вам необходимо реализовать Comparable и переопределить метод сравнения.

Ответ 9

Вы можете сделать это таким образом, когда вы хотите сохранить класс закрытым:

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

Я верю, что лучший вариант здесь - создать Comparator где нужен отсортированный список, потому что вам может понадобиться сортировать по другим полям в других местах и раздуть ваш класс домена:

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