问题
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:
- make
grade
visible - define a getter method for
grade
- define a
Comparator
insideStudent
- make
Student
implementComparable
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
Comparator
s - 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 Student
s 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