Ejemplo de Genericidad

Disponemos de una función que ordena listas de strings (no es necesario entender como funciona):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package paradigmas3c;

import java.util.*;

public class Cosas {
    
    public static void ordenar(List<String> lis) {
        // Ordenación burbuja
        int n = lis.size();
        for(int i = 0; i < n-1; i++) {
            for(int j = n-1; j > i; j--) {
                String eant = lis.get(j-1);
                String eact = lis.get(j);
                if(eant.compareTo(eact) > 0) {
                    // lis[j-1] > lis[j] -> Intercambiamos
                    lis.set(j-1, eact);
                    lis.set(j, eant);
                }
            }
        }                    
    }
}

Queremos generalizarla para que pueda ordenar listas de personas y alumnos con distintos criterios de ordenació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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package paradigmas3c;

import java.util.*;

public class Paradigmas3c {
    
    public class Persona {
        public String DNI;
        public String Apellidos;
        public String Nombre;
    
        public Persona(String DNI, String Apellidos, String Nombre) {
            this.DNI = DNI;
            this.Apellidos = Apellidos;
            this.Nombre = Nombre;
        }
    
        @Override
        public String toString() {
            return String.format("%s | %s, %s", DNI, Apellidos, Nombre);
        }        
    }
    
    public class Alumno extends Persona {    
        public double Nota;
    
        public Alumno(String DNI, String Apellidos, String Nombre, double Nota) {
            super(DNI, Apellidos, Nombre);
            this.Nota = Nota;
        }
    
        @Override
        public String toString() {
            return String.format("%s | %s, %s: %.2f", DNI, Apellidos, Nombre, Nota);
        }
    }
    
    public void Procesar() {
        Persona p1 = new Persona("01234567A", "Perez Gomez", "Ana");
        Persona p2 = new Persona("76543210B", "Alvarez Zamora", "Pedro");
        Persona p3 = new Persona("66666666X", "Alvarez Alvarez", "Maria");
        Persona p4 = new Persona("42424242Z", "Sainz Jimenez", "Javier");
        Alumno a1 = new Alumno("01234567A", "Perez Gomez", "Ana", 8.7);
        Alumno a2 = new Alumno("76543210B", "Alvarez Zamora", "Pedro", 3.5);
        Alumno a3 = new Alumno("66666666X", "Alvarez Alvarez", "Maria", 7.5);
        Alumno a4 = new Alumno("43434343Z", "Sainz Ximenez", "Xavier", 8.2);
        
        List<Persona> lisper = Arrays.asList(p1, p2, p3, p4);        
        List<Alumno> lisalu = Arrays.asList(a1, a2, a3, a4);        
        
        Cosas.ordenar(lisper);
        System.out.println("LISTA DE PERSONAS");
        lisper.forEach(System.out::println);        
                
        Cosas.ordenar(lisalu);
        System.out.println("\nLISTA DE ALUMNOS");
        lisalu.forEach(System.out::println);           
    }
       
    public static void main(String[] args) {
        (new Paradigmas3c()).Procesar();
    }
}

Se muestran en fondo rojo las zonas donde el compilador indica un error ya que una lista de personas o de alumnos no es compatible con una lista de strings

Solución #1: Genericidad usando la interfaz Comparable

El parametrizar directamente no es suficiente, ya que es necesario que los elementos de la lista a ordenar (clase genérica T) dispongan de la operación compareTo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package paradigmas3c;

import java.util.*;

public class Cosas {
    
    public static <T> void ordenar(List<T> lis) {
        // Ordenación burbuja
        int n = lis.size();
        for(int i = 0; i < n-1; i++) {
            for(int j = n-1; j > i; j--) {
                T eant = lis.get(j-1);
                T eact = lis.get(j);
                if(eant.compareTo(eact) > 0) {
                    // lis[j-1] > lis[j] -> Intercambiamos
                    lis.set(j-1, eact);
                    lis.set(j, eant);
                }
            }
        }                    
    }
}

La interfaz Comparable de la librería estandar de Java obliga a que cualquier clase que la implemente incluya 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
22
package paradigmas3c;

import java.util.*;

public class Cosas {
    
    public static <T extends Comparable> void ordenar(List<T> lis) {
        // Ordenación burbuja
        int n = lis.size();
        for(int i = 0; i < n-1; i++) {
            for(int j = n-1; j > i; j--) {
                T eant = lis.get(j-1);
                T eact = lis.get(j);
                if(eant.compareTo(eact) > 0) {
                    // lis[j-1] > lis[j] -> Intercambiamos
                    lis.set(j-1, eact);
                    lis.set(j, eant);
                }
            }
        }                    
    }
}

Es necesario hacer que la clases Persona y Alumno implementen la 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
    ...
    public class Persona implements Comparable {
        ...
        public Persona(String DNI, String Apellidos, String Nombre) { ... }
    
        @Override
        public String toString() { ... }
 
        @Override
        public int compareTo(Object o) {
            Persona otra = (Persona) o;
            return Apellidos.compareTo(otra.Apellidos);
        }
    }
    
    public class Alumno extends Persona {
        ...
        public Alumno(String DNI, String Apellidos, String Nombre, double Nota) { ... }
    
        @Override
        public String toString() { ... }

        @Override
        public int compareTo(Object o) {
            Alumno otro = (Alumno) o;
            if(Nota < otro.Nota) return -1;
            if(Nota > otro.Nota) return 1;
            return 0;
        }
    }
    ...
Solución #2: Genericidad usando un Comparator

El método usado en la sección anterior solo permite definir un criterio de ordenación por cada clase. Si por ejemplo quisieramos ordenar por apellidos y en otro punto del programa ordenar por dni, no podríamos hacerlo. Un enfoque más general es proporcionar un parámetro extra a la función ordenar que sea algo que indique la forma de comparar dos elementos.

En Java existe la interfaz genérica Comparator<T> que se usa para crear clases que incluyan el método compare(T a, T b) el cual define el código para comparar dos elementos de clase T

La función ordenar se modificaría de ésta forma:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package paradigmas3c;

import java.util.*;

public class Cosas {
    
    public static <T> void ordenar(List<T> lis, Comparator<T> comp) {
        // Ordenación burbuja
        int n = lis.size();
        for(int i = 0; i < n-1; i++) {
            for(int j = n-1; j > i; j--) {
                T eant = lis.get(j-1);
                T eact = lis.get(j);
                if(comp.compare(eant, eact) > 0) {
                    // lis[j-1] > lis[j] -> Intercambiamos
                    lis.set(j-1, eact);
                    lis.set(j, eant);
                }
            }
        }                    
    }
}

En el programa principal definimos las clases necesarias para indicar los criterios de ordenació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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package paradigmas3c;

import java.util.*;

public class Paradigmas3c {
    
    public class Persona { ... }
    
    public class Alumno extends Persona { ... }

    class CompDNI implements Comparator<Persona> {
        @Override
        public int compare(Persona o1, Persona o2) {
            return o1.DNI.compareTo(o2.DNI);
        }        
    }

    class CompApellidos implements Comparator<Persona> {
        @Override
        public int compare(Persona o1, Persona o2) {
            return o1.Apellidos.compareTo(o2.Apellidos);
        }        
    }

    class CompNota implements Comparator<Alumno> {
        @Override
        public int compare(Alumno o1, Alumno o2) {
            if(o1.Nota > o2.Nota) return -1;
            if(o1.Nota < o2.Nota) return 1;
            return 0;
        }        
    }          
    
    public void Procesar() {
        Persona p1 = new Persona("01234567A", "Perez Gomez", "Ana");
        Persona p2 = new Persona("76543210B", "Alvarez Zamora", "Pedro");
        Persona p3 = new Persona("66666666X", "Alvarez Alvarez", "Maria");
        Persona p4 = new Persona("42424242Z", "Sainz Jimenez", "Javier");
        Alumno a1 = new Alumno("01234567A", "Perez Gomez", "Ana", 8.7);
        Alumno a2 = new Alumno("76543210B", "Alvarez Zamora", "Pedro", 3.5);
        Alumno a3 = new Alumno("66666666X", "Alvarez Alvarez", "Maria", 7.5);
        Alumno a4 = new Alumno("43434343Z", "Sainz Ximenez", "Xavier", 8.2);
        
        List<Persona> lisper = Arrays.asList(p1, p2, p3, p4);        
        List<Alumno> lisalu = Arrays.asList(a1, a2, a3, a4);        
        
        Cosas.ordenar(lisper, new CompApellidos());
        System.out.println("LISTA DE PERSONAS");
        lisper.forEach(System.out::println);        
                
        Cosas.ordenar(lisalu, new CompNota());
        System.out.println("\nLISTA DE ALUMNOS");
        lisalu.forEach(System.out::println);           
    }
       
    public static void main(String[] args) {
        (new Paradigmas3c()).Procesar();
    }

}

Aparentemente todo va bien, pero si quisieramos ordenar la lista de alumnos por apellidos:

 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
package paradigmas3c;

import java.util.*;

public class Paradigmas3c {
    
    public class Persona { ... }
    
    public class Alumno extends Persona { ... }

    class CompDNI implements Comparator<Persona> { ... }

    class CompApellidos implements Comparator<Persona> { ... }

    class CompNota implements Comparator<Alumno> { ... }
    
    public void Procesar() {
        ...        
        Cosas.ordenar(lisper, new CompApellidos());
        System.out.println("LISTA DE PERSONAS");
        lisper.forEach(System.out::println);        
                
        Cosas.ordenar(lisalu, new CompApellidos());
        System.out.println("\nLISTA DE ALUMNOS");
        lisalu.forEach(System.out::println);           
    }
       
    ...
}

El compilador nos da un error porque al llamar a ordenar con una lista de alumnos, se tiene que T = Alumno. El comparador que se le proporciona es de clase Comparator<Persona>, que no es compatible con lo que esperaba, un Comparator<Alumno>.

La solución es modificar ligeramente la cabecera de ordenar para indicar que el comparator no tiene que tener como parámetro exactamente la clase T sino que es válida cualquier clase padre de T:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package paradigmas3c;

import java.util.*;

public class Cosas {
    
    public static <T> void ordenar(List<T> lis, Comparator<? super T> comp) {
        // Ordenación burbuja
        int n = lis.size();
        for(int i = 0; i < n-1; i++) {
            for(int j = n-1; j > i; j--) {
                T eant = lis.get(j-1);
                T eact = lis.get(j);
                if(comp.compare(eant, eact) > 0) {
                    // lis[j-1] > lis[j] -> Intercambiamos
                    lis.set(j-1, eact);
                    lis.set(j, eant);
                }
            }
        }                    
    }
}