Ordenación de datos
Para ordenar Colecciones se suele sobrescribir el método compareTo
requerido por la interfaz Comparable
que recibe como parámetro el objeto con el que se compara el objeto this
. Si el objeto this
viene antes que el objeto recibido como parámetro en términos de orden de clasificación, el método debería devolver un número negativo. Si, por el contrario, el objeto this
viene después del objeto recibido como parámetro, el método debería devolver un número positivo. De lo contrario, se devuelve 0. La ordenación resultante del método compareTo
se denomina ordenación natural.
Echemos un vistazo a esto con la ayuda de una clase de Member
que representa a un niño o joven que pertenece a un club. Cada miembro del club tiene un nombre y una altura. Los miembros del club deben ir a comer en orden de altura, por lo que la clase Member
debe implementar la interfaz Comparable
. La interfaz Comparable
toma como parámetro de tipo la clase que es objeto de la comparación.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Member implements Comparable<Member> {
private String name;
private int height;
public Member(String name, int height) {
this.name = name;
this.height = height;
}
public String getName() {
return this.name;
}
public int getHeight() {
return this.height;
}
@Override
public String toString() {
return this.getName() + " (" + this.getHeight() + ")";
}
@Override
public int compareTo(Member member) {
if (this.height == member.getHeight()) {
return 0;
} else if (this.height > member.getHeight()) {
return 1;
} else {
return -1;
}
}
}
El método compareTo
requerido por la interfaz devuelve un número entero que nos informa el orden de comparación.
Como devolver un número negativo de compareTo()
es suficiente si este objeto es más pequeño que el objeto de parámetro, y devolver cero es suficiente cuando las longitudes son las mismas, el método compareTo
descrito anteriormente también se puede implementar de la siguiente manera.
1
2
3
4
@Override
public int compareTo(Member member) {
return this.height - member.getHeight();
}
Dado que la clase Member
implementa la interfaz Comparable
, es posible ordenar la lista utilizando el método ordenado. De hecho, los objetos de cualquier clase que implementen la interfaz Comparable se pueden ordenar mediante el método de ordenación. Ten en cuenta, sin embargo, que una transmisión no ordena la lista original; solo se ordenan los elementos de la transmisión.
Si un programador quiere organizar la lista original, se debe usar el método de clasificación de la clase Collections
. Esto, por supuesto, asume que los objetos en la lista implementan la interfaz Comparable
.
Clasificar a los miembros del club es sencillo ahora.
1
2
3
4
5
6
7
8
9
10
11
12
13
List<Member> members = new ArrayList<>();
members.add(new Member("mikael", 182));
members.add(new Member("matti", 187));
members.add(new Member("ada", 184));
members.stream().forEach(System.out::println);
System.out.println();
// sorting the stream that is to be printed using the sorted method
members.stream().sorted().forEach(System.out::println);
members.stream().forEach(System.out::println);
// sorting a list with the sort-method of the Collections class
Collections.sort(members);
members.stream().forEach(System.out::println);
Cuya salida es:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mikael (182)
matti (187)
ada (184)
mikael (182)
ada (184)
matti (187)
mikael (182)
matti (187)
ada (184)
mikael (182)
ada (184)
matti (187)
Ejercicio (F)
(ra2.d, ra2.h, ra3.b, ra3.f, ra5.c, ra6.b, ra6.e, ra6.c, ra6.d)
Se te proporciona la clase
Human
. Un ser humano tiene un nombre e información sobre salario. Implementa la interfazComparable
de tal manera que el métodocompareTo
clasifique a los humanos según el salario del más grande al más pequeño.
Método de ordenación como una expresión lambda
Supongamos que tenemos la siguiente clase:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Person {
private String name;
private int birthYear;
public Person(String name, int birthYear) {
this.name = name;
this.birthYear = birthYear;
}
public String getName() {
return name;
}
public int getBirthYear() {
return birthYear;
}
}
Y una lista de objetos Person
1
2
3
4
5
ArrayList<Person> persons = new ArrayList<>();
persons.add(new Person("Ada Lovelace", 1815));
persons.add(new Person("Irma Wyman", 1928));
persons.add(new Person("Grace Hopper", 1906));
persons.add(new Person("Mary Coombs", 1929));
Queremos ordenar la lista sin tener que implementar la interfaz Comparable
.
Tanto el método sort
de la clase Collections
como el método sorted
del stream
aceptan una expresión lambda como parámetro que define los criterios de clasificación. Más específicamente, ambos métodos se pueden proporcionar con un objeto que implementa la interfaz Comparator
, que define el orden deseado: la expresión lambda se usa para crear este objeto.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ArrayList<Person> persons = new ArrayList<>();
persons.add(new Person("Ada Lovelace", 1815));
persons.add(new Person("Irma Wyman", 1928));
persons.add(new Person("Grace Hopper", 1906));
persons.add(new Person("Mary Coombs", 1929));
persons.stream().sorted((p1, p2) -> {
return p1.getBirthYear() - p2.getBirthYear();
}).forEach(p -> System.out.println(p.getName()));
System.out.println();
persons.stream().forEach(p -> System.out.println(p.getName()));
System.out.println();
Collections.sort(persons, (p1, p2) -> p1.getBirthYear() - p2.getBirthYear());
persons.stream().forEach(p -> System.out.println(p.getName()));
Cuya salida es:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Ada Lovelace
Grace Hopper
Irma Wyman
Mary Coombs
Ada Lovelace
Irma Wyman
Grace Hopper
Mary Coombs
Ada Lovelace
Grace Hopper
Irma Wyman
Mary Coombs
Al comparar cadenas, podemos usar el método compareTo
proporcionado por la clase String
. El método devuelve un número entero que describe el orden de la cadena que se le da como parámetro y la cadena que lo llama.
1
2
3
4
5
6
7
8
9
ArrayList<Person> persons = new ArrayList<>();
persons.add(new Person("Ada Lovelace", 1815));
persons.add(new Person("Irma Wyman", 1928));
persons.add(new Person("Grace Hopper", 1906));
persons.add(new Person("Mary Coombs", 1929));
persons.stream().sorted((p1, p2) -> {
return p1.getName().compareTo(p2.getName());
}).forEach(p -> System.out.println(p.getName()));
1
2
3
4
Ada Lovelace
Grace Hopper
Irma Wyman
Mary Coombs
Ejercicio (M)
(ra2.a, ra2.d, ra2.h, ra3.b, ra3.f, ra4.a, ra4.b, ra4.d, ra4.e, ra5.c, ra5.d, ra6.b, ra6.e, ra6.c, ra6.d)
El siguiente archivo muestra la población analfabeta mundial en el formato:
1 2 "ILLPOP_AG15T24","Youth illiterate population, 15-24 years, both sexes (number)","40550","Small Island Developing States","2016","2016",1192378,,Se trata de realizar un programa que lea este archivo y que luego muestre la información en pantalla por orden ascendente del total de personas analfabetas (en el ejemplo, 1192378)
Clase Comparator
Otra forma de realizar la ordenación es mediante el uso de la clase Comparator
.
Vamos a usar la siguiente clase:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Film {
private String name;
private int releaseYear;
public Film(String name, int releaseYear) {
this.name = name;
this.releaseYear = releaseYear;
}
public String getName() {
return this.name;
}
public int getReleaseYear() {
return this.releaseYear;
}
public String toString() {
return this.name + " (" + this.releaseYear + ")";
}
}
La clase Comparator
proporciona dos métodos esenciales para ordenar: comparing
y thenComparing
. Al método de comparación se le pasa primero el valor que se va a comparar, y thenComparing
se le pasa el siguiente valor que se va a comparar. El método thenComparing
se puede utilizar muchas veces mediante métodos de encadenamiento, lo que permite utilizar valores prácticamente ilimitados para la comparación.
En el siguiente ejemplo, imprimimos películas por año y título en orden.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
List<Film> films = new ArrayList<>();
films.add(new Film("A", 2000));
films.add(new Film("B", 1999));
films.add(new Film("C", 2001));
films.add(new Film("D", 2000));
for (Film e: films) {
System.out.println(e);
}
Comparator<Film> comparator = Comparator
.comparing(Film::getReleaseYear)
.thenComparing(Film::getName);
Collections.sort(films, comparator);
for (Film e: films) {
System.out.println(e);
}
Cuya salida es:
1
2
3
4
5
6
7
8
9
A (2000)
B (1999)
C (2001)
D (2000)
B (1999)
A (2000)
D (2000)
C (2001)
Cómo resolver los ejercicios
Creación de la clase POJO
Una clase POJO es una clase que representa a una entidad como una Persona
, un Animal
, una Impresora
. Estas clases tienen campos
para almacenar datos, un constructor para inicializarla y varios setters
y getters
Por ejemplo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package comparable;
public class Member{
private String name;
private int height;
public Member(String name, int height) {
this.name = name;
this.height = height;
}
public String getName() {
return this.name;
}
public int getHeight() {
return this.height;
}
@Override
public String toString() {
return this.getName() + " (" + this.getHeight() + ")";
}
}
Creamos ahora un main
para crear una lista con varios Member
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.ArrayList;
import java.util.List;
public class MainMember {
public static void main(String[] args) {
List<Member> members = new ArrayList<>();
Member m = new Member("Juan", 180);
members.add(m);
m = new Member("María", 168);
members.add(m);
m = new Member("Andres", 190);
members.add(m);
}
}
Si ahora intentamos ordenarla va a saltar una Exception
porque java no sabe cómo ordenarla lista (por name
?, por height
? )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.ArrayList;
import java.util.List;
public class MainMember {
public static void main(String[] args) {
List<Member> members = new ArrayList<>();
Member m = new Member("Juan", 180);
members.add(m);
m = new Member("María", 168);
members.add(m);
m = new Member("Andres", 190);
members.add(m);
members.stream()
.sorted()
.forEach(System.out::println);
}
}
Y esta es la Exception
1
2
Exception in thread "main" java.lang.ClassCastException: class Member cannot be cast to class java.lang.Comparable (comparable.Member is in unnamed module of loader 'app'; java.lang.Comparable is in module java.base of loader 'bootstrap')
at java.base/java.util.Comparators$NaturalOrderComparator.compare(Comparators.java:47)
Que nos viene a decir que la clase Member
no implementa la interfaz Comparable
Interfaz Comparable
Por tanto, vamos a hacer que la clase implemente dicha Interfaz.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package comparable;
public class Member implements Comparable<Member> {
private String name;
private int height;
public Member(String name, int height) {
this.name = name;
this.height = height;
}
public String getName() {
return this.name;
}
public int getHeight() {
return this.height;
}
@Override
public String toString() {
return this.getName() + " (" + this.getHeight() + ")";
}
@Override
public int compareTo(Member other) {
//Si mi altura es igual a la del otro, devuelve 0. Por tanto, no se toca
if (this.height == other.getHeight()) {
return 0;
} else if (this.height > other.getHeight()) {
//Si mi altura es mayor, pasa adelante
return 1;
} else {
//Si mi altura es menor, pasa atrás
return -1;
}
//También se puede hacer de forma resumida
//return this.height - other.getHeigh();
//Y si queremos ordenar de mayor a menor (fíjate cómo cambia el orden
//de this y other)
//return other.getHeight() - this.height;
// Otra forma de hacerlo sería
//return Integer.compare(this.getHeight(), other.getHeight());
}
}
Ahora, la clase debe implementar el método compareTo
porque así lo indicamos en la clase:
1
public class Member implements Comparable<Member> {
Y hemos implementado el método compareTo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Override
public int compareTo(Member other) {
//Este método debe devolver un número menor que 0 si this es menor que other
//0 si son iguales y un número mayor si this is mayor que other
//Si mi altura es igual a la del otro, devuelve 0. Por tanto, no se toca
if (this.height == other.getHeight()) {
return 0;
} else if (this.height > other.getHeight()) {
//Si mi altura es mayor, pasa adelante
return 1;
} else {
//Si mi altura es menor, pasa atrás
return -1;
}
//También se puede hacer de forma resumida
//return this.height - other.getHeigh();
//Y si queremos ordenar de mayor a menor
//return other.getHeight() - this.height;
// Otro forma de hacerlo sería
//return Integer.compare(this.height(), other.getHeight());
}
La forma más fácil de hacer es simplemente hacer la resta:
1
return this.height - other.getHeigh(); //Si queremos de menor a mayor
O de esta forma:
1
return other.height - this.getHeigh(); //De mayor a menor
Para otro tipo de datos como Double
lo más fácil es usar el método:
1
Double.compare(this.getHeight(), other.getHeight());//Suponiendo que height sea Double
¿Y qué pasa si deseo ordenar por más campos? Vamos a ordenarlos por name
y en el caso que sea el mismo, ordenarlo por height
de menor a mayor.
1
2
3
4
5
6
7
if (this.name.equals(other.getName())){
return Integer.compare(this.height, other.getHeigh());
//O
//return this.height, other.getHeigh();
}else{
return this.name.compareTo(other.getName());
}
Ejercicios
(ra2.a, ra2.d, ra2.h, ra3.b, ra3.f, ra4.a, ra4.b, ra4.d, ra4.e, ra5.c, ra5.d, ra6.b, ra6.e, ra6.c, ra6.d)
Los niños buenos (F)
Adaptado del siguiente material