java - sort - ¿Cómo ordenar una lista por un campo privado?
ordenar una lista de numeros en java (10)
Creo que la mejor opción aquí es crear un Comparator
donde se necesita la lista ordenada, ya que es posible que deba ordenar por otros campos en otros lugares y eso inflaría su clase de dominio:
List<Student> sorted = list.stream()
.sorted(Comparator.comparingInt(o -> o.grade))
.collect(Collectors.toList());
Mi clase de entidad se ve así:
public class Student {
private int grade;
// other fields and methods
}
y lo uso así:
List<Student> students = ...;
¿Cómo puedo ordenar a los students
por grade
, teniendo en cuenta que es un campo privado?
En general, si necesita un comportamiento que depende de la calificación del estudiante, entonces esta información debe ser accesible: agregue un método o una propiedad que permita que otro código acceda a él.
Una solución más simple sería así:
public class Student implements IStudent {
...
private int grade;
...
// other fields and methods
public int getGrade() {
return grade;
}
}
Probablemente debería extender la interfaz también a los IStudent
:)
Sin embargo, si necesita esto solo para ordenar, puede ir con una idea ya sugerida en otras respuestas: implementar la interfaz Comparable
. De esta manera, puede mantener el grade
oculto y usarlo dentro del int compareTo
.
Implementar la Comparable para la clase del alumno e implementar el método int compareTo(T o)
. De esta manera usted puede mantener la propiedad de grado privado.
Los usuarios no son una mala práctica, están hechos exactamente para su problema: acceso a campos privados para leerlos.
Añade un getter y podrás hacer:
studentsList.stream().sorted((s1, s2) -> s1.getGrade()compareTo(s2.getGrade)).collect(Collectors.toList())
Actualización: si realmente desea mantener el grado privado, debe implementar Comparable
y anular el método de comparación.
Otra opción que se mencionó anteriormente pero que no se muestra como ejemplo es la implementación de un Comparator
especial para una comparación por grado.
Este ejemplo consiste en una clase Student
implementa una interfaz IStudent
, IStudent
y una pequeña clase Main
que usa datos de muestra.
Más explicaciones se dan como comentarios de código, por favor léalos.
/**
* 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;
}
}
Puede aplicar esta clase en el método de sort(Comparator<? super IStudent> comparator)
de una 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());
});
}
}
Solo para completar, aquí están la interfaz de IStudent
y su clase de implementación 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;
}
}
Puedes hacerlo de esta manera cuando quieras mantener el grado privado:
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());
Si realmente necesita ordenar por campo al que no tiene acceso, puede usar la reflexión :
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);
}
Pero tengo que decir que este método es un poco inseguro.
No lo uses a menos que sea absolutamente necesario. Es mejor dar acceso a un campo dado utilizando el método getter, por ejemplo.
También es posible que tengas problemas si ejecutas este código en modulepath contra Java 9+ (puedes lanzar InaccessibleObjectException
).
Acerca de la implementación de Comparable
De docs comparables:
Esta interfaz impone un orden total en los objetos de cada clase que lo implementa. Este ordenamiento se conoce como ordenamiento natural de la clase, y el método {@code compareTo} de la clase se conoce como método de comparación natural .
Pero, ¿qué podría ser un orden natural para el Student
? ¿Nombre de pila? ¿Apellido? ¿Su combinación?
Es fácil responder a esta pregunta para los números, pero no para clases como Student
.
Así que no creo que los Student
deban ser Comparable
, son seres humanos y no fechas ni números. Y no puedes decir quién es mayor, quién es igual y quién es menor.
Tienes estas opciones:
- hacer visible el
grade
- definir un método getter para el
grade
- definir un
Comparator
dentro deStudent
- hacer que el
Student
implementaComparable
-
usar la reflexión(en mi opinión, esto no es una solución , es una solución / hack )
Ejemplo para la solución 3
:
public class Student {
private int grade;
public static Comparator<Student> byGrade = Comparator.comparing(s -> s.grade);
}
y úsalo así:
List<Student> students = Arrays.asList(student2, student3, student1);
students.sort(Student.byGrade);
System.out.println(students);
Esta es mi solución favorita porque:
- Usted puede definir fácilmente varios
Comparator
s - No es mucho codigo
- Tu campo se mantiene privado y encapsulado.
Ejemplo de solución 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;
}
}
Puedes ordenar en todas partes así:
List<Student> students = Arrays.asList(student2, student3, student1);
Collections.sort(students);
System.out.println(students);
Aspectos de esta solución:
- Esto define, que la clasificación por
grade
representa el orden natural de los estudiantes. - Algunos métodos preexistentes se ordenarán automáticamente (como
TreeMap
)
Tu clase podría implementar la interfaz Comparable
. Entonces, puede ordenar fácilmente la lista:
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);
}
}
Una opción provista por JDK 1.8 es usar el método sorted()
biblioteca de stream
que no requiere la implementación de una interfaz Comparable
. Es necesario implementar el método de acceso (getter) para el grade
campo
public class Student {
private int grade;
public int getGrade() {
return grade;
}
public Student setGrade(int grade) {
this.grade = grade;
return this;
}}
Luego, teniendo en cuenta la lista de estudiantes sin clasificar, puede clasificarlo como el siguiente código:
List<Student> sortedStudentList = unsortedStudentList
.stream()
.sorted(Comparator.comparing(Student::getGrade))
.collect(Collectors.toList());
Además, el método sorted()
permite clasificar a los estudiantes según otros campos (si tiene). Por ejemplo, considere un name
campo para el alumno y, en este caso, le gustaría clasificar la lista de alumnos según el grado y el nombre. Así que Student
clase de Student
sería así:
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;
}}
Para ordenar en base a ambos campos:
List<Student> sortedStudentList = unsortedStudentList
.stream()
.sorted(Comparator.comparing(Student::getGrade)
.thenComparing(Comparator.comparing(Student::getName)))
.collect(Collectors.toList());
El segundo comparador entra en juego cuando el primero compara dos objetos iguales.