Как отсортировать список по частному полю?
Мой класс сущности выглядит следующим образом:
public class Student {
private int grade;
// other fields and methods
}
и я использую его так:
List<Student> students = ...;
Как я могу сортировать students
по grade
, принимая во внимание, что это личное поле?
Ответы
Ответ 1
У вас есть следующие варианты:
- сделать
grade
видимым - определить метод геттера для
grade
- определить
Comparator
внутри Student
- сделать
Student
реализацию Comparable
-
использовать отражение (на мой взгляд это не решение, это обходной путь/хак)
Пример для решения 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());