% Activate the following line by filling in the right side. If for example the name of the root file is Main.tex, write
% "...root = Main.tex" if the chapter file is in the same directory, and "...root = ../Main.tex" if the chapter is in a subdirectory.
 
%!TEX root =  ../notas_java.tex

\chapter[Uso de Collections y Generics]{Uso de Collections y Generics}

\section[Uso de == ]{Uso de == y del método equal, y sobreescritura del método hashCode}

Utilizar el símbolo == para comparar objetos resulta inoperante dado que hace una comparación de los apuntadores de cada uno de los objetos. En lugar de esto utilizamos un método especial del que disponen todos los objetos denominado equals. Equals se basa en una definición de igualdad que ha de estar establecida. Por ejemplo, podríamos definir que dos objetos son iguales si contienen el mismo número de bolígrafos, de la misma marca, modelo y color. Para algunos tipos de objetos, como los String, la definición de igualdad que viene incorporada en el API de Java nos va a resultar suficiente. 

 El siguiente es un ejemplo de como se debe implementar el uso de este método
 
 \begin{verbatim}
public class Compara_Personas {
    private String nombre;
    private int clave;
    
    public Compara_Personas(String unNombre, int unaClave){
        nombre = unNombre;
        clave = unaClave;
    }
    
    public String imprime(){
        String aux ;
        
        aux = nombre + " " + clave;
        
        return aux;
    }    
  
    @Override
    public boolean equals(Object a) {
        if(a instanceof Compara_Personas && this instanceof Compara_Personas) {       
            if(this.nombre.equals(((Compara_Personas)a).nombre )) return true;
            else return false;            
        }
        else return false;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 97 * hash + (this.nombre != null ? this.nombre.hashCode() : 0);
        return hash;
    }
    
    static public void main(String args[]) {
        Compara_Personas a = new Compara_Personas("Juan", 123);
        Compara_Personas b = new Compara_Personas("Juan", 123);
        
        System.out.println(a.equals(b));
    }
}
 \end{verbatim}

\section[Colecciones]{Clases de la API Collections (Set, List, Map, etc)}


Para trabajar con colecciones en Java podemos hacer uso del framework Collections. Las clases e interfaces que componen este framework se encuentran en los paquetes java.util y java.util.concurrent. Todas hacen uso del polimorfismo paramétrico que proporciona generics; concepto que tratamos ampliamente en la entrada Generics en Java.

La raíz de la jerarquía de colecciones es la interfaz Collection$<$E$>$. De entre las interfaces que extienden Collection$<$E$>$ las más interesantes son List$<$E$>$, Set$<$E$>$ y Queue$<$E$>$ que definen, respectivamente, listas, conjuntos y colas.

La lista es una de las colecciones más básicas, se trata de una estructura de datos secuencial, en la que cada elemento tiene una posición o índice, y que permite elementos duplicados. Set$<$E$>$ es la interfaz utilizada para modelar los conjuntos matemáticos; como en estos, los elementos no tienen un orden, y no se permiten elementos duplicados. Una cola o Queue$<$E$>$, por último, es una estructura de datos de tipo FIFO (First in first out) lo que significa que el primer elemento en introducirse en la cola será el elemento que se devolverá al extraer por primera vez de la cola, y así sucesivamente (en realidad existen implementaciones de Queue$<$E$>$ que no son FIFO, pero eso queda fuera del enfoque de esta entrada).

Otra estructura de datos que forma parte del framework, aunque no deriva de Collection$<$E$>$, dado que no se trata de una colección per se sino de un mapeo, es la que define la interfaz Map$<$K,V$>$, el conocido diccionario o matriz asociativa, en el que cada valor tiene asociado una clave que usaremos para recuperar el elemento, en lugar de un índice como el caso de las listas. Por ejemplo podríamos tener un mapeo en el que las claves fueran los días de la semana y los valores, el número de líneas de código que escribimos.

Para cada una de las interfaces que definen los tipos de colección disponibles tenemos una clase abstracta que contiene la funcionalidad común a varias implementaciones de la interfaz. Estas son AbstractList$<$E$>$, AbstractSet$<$E$>$, AbstractQueue$<$E$>$ y AbstractMap$<$K,V$>$.

En esta entrada trataremos las listas y los mapeos, por ser las estructuras de datos más utilizadas.

\subsection{List}

Hay varias clases que implementan la interfaz List $<$E $>$. Las más utilizadas habitualmente son ArrayList$<$E$>$ y la vieja conocida Vector$<$E$>$, que forma parte del framework Collections desde Java 1.2. Ambas extienden de AbstractList$<$E$>$ y tienen una interfaz muy parecida.

Como en el caso de StringBuffer y StringBuilder la principal diferencia entre ambas clases es que Vector$<$E$>$ es sincronizada y ArrayList$<$E$>$ no sincronizada. Debido a esto no tendremos que preocuparnos de problemas de sincronización a la hora utilizar varios hilos con Vector$<$E$>$ (hasta cierto punto). Pero a cambio, y por la misma razón, el rendimiento de Vector$<$E$>$ es mucho peor que el de ArrayList$<$E$>$. Ergo, si no necesitamos sincronización o no estamos trabajando con más de un hilo de ejecución, lo adecuado es utilizar ArrayList$<$E$>$.

En todo caso, si trabajamos con varios hilos, también existe la posibilidad de sincronizar ArrayList$<$E$>$ de forma externa o utilizar el método Collections.synchronizedList(List$<$T$>$ list) para crear un wrapper que añade sincronización a ArrayList$<$E$>$. Vector$<$E$>$, no obstante, es ligeramente más rápido que un ArrayList$<$E$>$ sincronizado con synchronizedList.

Tanto ArrayList$<$E$>$ como Vector$<$E$>$ utilizan un objeto Array$<$E$>$ para almacenar los elementos internamente. Las colecciones de tipo Array$<$E$>$, sin embargo, tienen un tamaño fijo que se determina de antemano al llamar al constructor. Esto implica que cuando sobrepasamos dicho tamaño hay que crear un nuevo Array$<$E$>$ y copiar el antiguo, lo que puede ser muy costoso computacionalmente. Por otro lado, si la capacidad máxima inicial del array fuera demasiado grande, estaríamos desperdiciando espacio.

Por defecto ArrayList$<$E$>$ y Vector$<$E$>$ utilizan arrays con capacidad para 10 elementos. Cuando el número de elementos sobrepasa la capacidad disponible Vector$<$E$>$ dobla el tamaño del array interno, mientras que ArrayList$<$E$>$ utiliza la fórmula (capacidad * 3) / 2 + 1.

Para indicar valores que se ajusten lo máximo posible al uso que vamos a hacer de nuestra lista tanto Vector$<$E$>$ como ArrayList$<$E$>$ cuentan, además de con un constructor vacío y un constructor al que pasar una colección con los elementos con los que inicializar el vector o la lista, con un constructor al que pasar un entero con la capacidad inicial de la colección.

Vector$<$E$>$ cuenta también con un constructor extra mediante el que indicar el incremento en capacidad a utilizar cuando el número de elementos rebase la capacidad del vector, lo cuál puede ser muy interesante.

En cualquier caso, independientemente del constructor que utilicemos, al crear cualquier colección es una buena práctica utilizar el tipo más genérico posible para referirnos al nuevo objeto. Por ejemplo, sería preferible utilizar:
view plaincopy to clipboardprint?

    List$<$String$>$ lista = new ArrayList$<$String$>$();  

a
view plaincopy to clipboardprint?

    ArrayList$<$String$>$ lista = new ArrayList$<$String$>$();  

ya que al utilizar la primera versión, si es necesario en un futuro, podremos migrar fácilmente a una implementación distinta de List$<$E$>$ gracias al polimorfismo.

Otra implementación para listas muy utilizada es LinkedList$<$E$>$, diseñada para obtener un mejor rendimiento cuando se insertan o eliminan muchos elementos de la mitad de la colección. Este tipo de lista, como su nombre indica, utiliza una lista doblemente enlazada internamente, en lugar de un array. En una lista enlazada tendremos un nodo por cada elemento introducido y cada uno de estos nodos tendrá el valor asociado a esa posición y una referencia al siguiente nodo. De esta forma, al insertar o eliminar un elemento cualquiera, basta con actualizar la referencia al siguiente nodo, en lugar de mover cada elemento a una posición superior o inferior como ocurriría con una implementación basada en Array$<$E$>$ como Vector$<$E$>$ y ArrayList$<$E$>$.

Sin embargo en las listas enlazadas, a diferencia de en los arrays, que se implementan directamente en hardware, el acceso a los elementos se realiza de forma secuencial, siguiendo las referencias de cada uno de los nodos, por lo que el acceso a una posición aleatoria de la colección es mucho más lento. Esta clase de colección, por tanto, tan solo debería utilizarse en casos en los que vayamos a insertar o eliminar muchos elementos de la mitad de la colección, pero no vayamos a realizar muchos accesos aleatorios.

Como Vector$<$E$>$ y ArrayList$<$E$>$, y de hecho como todos los tipos de colección concretos, LinkedList$<$E$>$ cuenta con un constructor vacío y un constructor que recibe una colección con los valores iniciales para la lista. De esta forma es sencillo copiar valores de una colección a otra o convertir un tipo de colección en otra distinta.

La interfaz que nos proporciona es muy parecida a la de Vector$<$E$>$ y ArrayList$<$E$>$. De hecho esta clase extiende de la clase AbstractSequentialList$<$E$>$, que a su vez es hija de AbstractList$<$E$>$, la clase padre de Vector$<$E$>$ y ArrayList$<$E$>$.

Por último, otra clase interesante dentro de la jerarquía de List$<$E$>$ es CopyOnWriteArrayList$<$E$>$, perteneciente al paquete java.util.concurrent, y que, a diferencia de ArrayList$<$E$>$ y LinkedList$<$E$>$ es thread-safe, como Vector$<$E$>$, y ofrece un muy buen rendimiento a la hora de leer de la colección. Su desventaja es que el array que utiliza por debajo es inmutable, lo que hace que tengamos que crear un nuevo array cada vez que la colección se modifica. Puede ser una buena alternativa si utilizamos varios hilos de ejecución y no vamos a modificar mucho la lista.

De entre los métodos comunes a las clases ArrayList$<$E$>$, Vector$<$E$>$, LinkedList$<$E$>$ y CopyOnWriteArrayList$<$E$>$ los más interesantes son los siguientes:

\begin{itemize}
\item boolean add(E o): Añade un nuevo elemento al final de la colección.  
\item boolean add(int index, E element): Añade un nuevo elemento en la posición especificada.
\item boolean addAll(Collection$<$? extends E$>$ c): Añade todos los elementos de la colección especificada a esta colección.
\item void clear(): Elimina todos los elementos de la colección.
\item boolean contains(Object o): Comprueba si el elemento especificado es parte de la colección.
\item E get(int index): Recupera el elemento que se encuentra en la posición espeficicada.
\item int indexOf(Object o): Devuelve la primera posición en la que se encuentra el elemento especificado en la colección, o -1 si no se encuentra.
\item int lastIndexOf(Object o): Devuelve la última posición en la que se encuentra el elemento especificado en la colección, o -1 si no se encuentra.
\item E remove(int index): Elimina el elemento de la posición indicada.
\item boolean remove(Object o): Elimina la primera ocurrencia del elemento indicado. Si se encontró y se borró el elemento, devuelve true, en caso contrario, false.
\item E set(int index, E element): Reemplaza el elemento que se encuentra en la posición indicada por el elemento pasado como parámetro. Devuelve el elemento que se encontraba en dicha posición anteriormente.
\item int size(): Devuelve el número de elementos que se encuentran actualmente en la colección.
\end{itemize}

\subsubsection{Ejemplo 1} 

Crear una lista de valores enteros probar algunas de las funciones validas para una Lista

\begin{verbatim}
import java.util.ArrayList;
import java.util.List;

public class ejemplo_lista {
    
    static public void prueba() {
        List <Integer> lista = new ArrayList<Integer>();
        List <Integer> aux = new ArrayList <Integer> ();
        
        // agrega un elemento
        
        lista.add(new Integer(1));
        
        // agrega un elemento en la posicion indicada
        
        lista.add(0, new Integer(2));
        
        // agrega todos los elementos a otra lista
        
        aux.addAll(lista);        
        
        for(Integer b: lista)
            System.out.println(b);
        
        // elimina todos los elementos de la lista
        
        aux.clear();
        
        for(Integer b: aux)
            System.out.println(b);           
    }
    
    static public void main(String args[]){
        prueba();
    }
}
\end{verbatim}

\subsubsection{Ejemplo 2} 

Crear un conjunto con valores enteros probar algunas de las funciones validas para un Conjunto

\begin{verbatim}
import java.util.HashSet;
import java.util.Set;

public class ejemplo_set {
    static public void prueba() {
        Set <String> A = new HashSet <String> ();
        A.add("c");
        A.add("a");
        A.add("a");
        A.add("b");
        
        // Funcion para preguntar si contiene
        
        System.out.println(A.contains("a"));
        
        for(String b: A)
            System.out.println(b);             
    }
    static public void main(String args[]){
        prueba();
    }
}
\end{verbatim}

\subsection{Map}

La interfaz Map$<$K,V$>$ y las clases que la implementan vienen a reemplazar la antigua clase Dictionary.

HashMap$<$K,V$>$ es el tipo de mapeo más sencillo y probablemente el más usado. Es la clase a utilizar si queremos asociar pares de claves y valores sin orden, sin más. Internamente, como su nombre indica, utiliza un hash para almacenar la clave. No permite claves duplicadas, pero si utilizar null como clave.

Hashtable$<$K,V$>$ es una vieja conocida del JDK 1.0, que, como Vector$<$E$>$ pasó a formar parte del framework de colecciones en Java 1.2. Esta clase es muy similar a HashMap$<$K,V$>$, con la excepción de que es sincronizada y que no acepta utilizar el valor null como clave. Como en el caso de Vector$<$E$>$ contra ArrayList$<$E$>$, nos interesará utilizar HashMap$<$K,V$>$ siempre que no estemos utilizando varios hilos de ejecución. En el caso de que necesitemos sincronización, otra opción es utilizar el método Collections.synchronizedMap sobre un HashMap, que funciona de forma similar a Collections.synchronizedList.

LinkedHashMap$<$K,V$>$ es una clase introducida con el J2SE 1.4 que extiende HashMap$<$K,V$>$ y utiliza una lista doblemente enlazada para poder recorrer los elementos de la colección en el orden en el que se añadieron. Esta clase es ligeramente más rápida que HashMap$<$K,V$>$ a la hora de acceder a los elementos, pero es algo más lenta al añadirlos.

Por último tenemos TreeMap$<$K,V$>$, en el que los pares clave-valor se almacenan en un árbol ordenado según los valores de las claves. Como es de esperar es la clase más lenta a la hora de añadir nuevos elementos, pero también a la hora de accederlos.

De entre los métodos comunes a las cuatro clases los más utilizados son:

\begin{itemize}
\item  void clear(): Elimina todos los elementos de la colección.
\item boolean containsKey(Object key): Comprueba si la clave especificada se encuentra en la colección.
\item boolean containsValue(Object value): Comprueba si el valor especificado se encuentra en la colección.
\item V get(Object key): Devuelve el valor asociado a la clave especificada o null si no se encontró.
\item boolean isEmpty(): Comprueba si la colección está vacía.
\item Set keySet(): Devuelve un conjunto con las claves contenidas en la colección.
\item V put(K key, V value): Añade un nuevo par clave-valor a la colección
\item V remove(Object key): Elimina el par clave-valor correspondiente a la clave pasada como parámetro.
\item int size(): Devuelve el número de pares clave-valor que contiene la colección.
\item Collection values(): Devuelve una colección con los valores que contiene la colección.
\end{itemize}

\subsubsection{Ejemplo}

El siguiente ejemplo muestra el uso de Map para asociar a los jugadores de Futbol con un número de camiseta

\begin{verbatim}
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class ejemplo_Map {
    static public void prueba() {
        Map <Integer, String> map = new HashMap <Integer, String>();
        map.put(1, "Casillas");		
        map.put(15, "Ramos");
        map.put(3, "Pique");		
        map.put(5, "Puyol");
        map.put(11, "Capdevila");	
        map.put(14, "Xabi Alonso");
        map.put(16, "Busquets");	
        map.put(8, "Xavi Hernandez");
        map.put(18, "Pedrito");		
        map.put(6, "Iniesta");
        map.put(7, "Villa");    
        
        Iterator it = map.keySet().iterator();
        
        while(it.hasNext()){
            Integer key = (Integer) it.next();
            System.out.println("Clave: " + key + " -> Valor: " + map.get(key));
        }
    }
    
    static public void main(String args[]) {
        prueba();
    }
}
\end{verbatim}

\subsection{Collections}

Collections es una clase perteneciente al paquete java.util que proporciona una serie de métodos estáticos para manejar colecciones que pueden ser de mucha utilidad. A esta clase pertenecen los métodos synchronizedList y synchronizedMap que ya comentamos anteriormente. Otros métodos interesantes son:

\begin{itemize}
\item int binarySearch(List list, Object key): Busca un elemento en una lista ordenada utilizando un algoritmo de búsqueda binaria. El método devuelve un entero indicando la posición en la que se encuentra el elemento, o bien un número negativo si no se encontró. Este número negativo indica la posición la que se encontraría el elemento de haber estado en la colección.
\item int frequency(Collection c, Object o): Devuelve el número de veces que se repite el elemento especificado en la colección.
\item Object max(Collection coll): Devuelve el mayor elemento de la colección.
\item Object min(Collection coll): Devuelve el menor elemento de la colección.
\item boolean replaceAll(List list, Object oldVal, Object newVal): Reemplaza todas las ocurrencias en la lista de un cierto elemento por otro objeto.
\item void reverse(List list): Invierte la lista.
\item void shuffle(List list): Modifica la posición de distintos elementos de la lista de forma aleatoria.
\item void sort(List list): Ordena una lista utilizando un algoritmo merge sort.
\end{itemize}


\subsubsection{Ejemplo}

Escribir un pequeño código que permita probar algunas de las funciones para la utilidades en la clase colecciones.

\begin{verbatim}
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ejemplo_Coleccion {
    static public void prueba() {
        List <String> A = new ArrayList <String> ();
        A.add(new String("c"));
        A.add(new String("a"));
        A.add(new String("b"));
                     
        Collections.sort(A);

        for(String b: A)
            System.out.println(b); 
        
        System.out.println("Esta la letra b " + Collections.binarySearch(A, "b"));   
        System.out.println("El minimo es "  + Collections.min(A));
        System.out.println("El maximo es "  + Collections.max(A));
              
        Collections.reverse(A);

        for(String b: A)
            System.out.println(b);     
     }
     
    static public void main(String args[]){
        prueba();
    }
}
\end{verbatim}

 \section{Generics}
 
Antes de Java 5 cuando introducíamos objetos en una colección estos se guardaban como objetos de tipo Object, aprovechando el polimorfismo para poder introducir cualquier tipo de objeto en la colección. Esto nos obligaba a hacer un casting al tipo original al obtener los elementos de la colección.

\begin{verbatim}
    import java.util.ArrayList;  
    import java.util.List;  
      
    public class Ejemplo {  
      public static void main(String[] args) {  
        List lista = new ArrayList();  
        lista.add("Hola mundo");  
      
        String cadena = (String) lista.get(0);  
        System.out.println(cadena);  
      }  
    }  
\end{verbatim}

Esta forma de trabajar no solo nos ocasiona tener que escribir más código innecesariamente, sino que es propenso a errores porque carecemos de un sistema de comprobación de tipos. Si introdujéramos un objeto de tipo incorrecto el programa compilaría pero lanzaría una excepción en tiempo de ejecución al intentar convertir el objeto en String:

\begin{verbatim}
    import java.util.ArrayList;  
    import java.util.List;  
      
    public class Ejemplo {  
      public static void main(String[] args) {  
        List lista = new ArrayList();  
        lista.add(22);  
      
        String cadena = (String) lista.get(0);  
        System.out.println(cadena);  
      }  
    } 
\end{verbatim} 

Podríamos utilizar el operador instanceof para comprobar el tipo del objeto antes de hacer el casting o antes de introducirlo en la colección, pero es poco elegante, añade aún más código a escribir, y no nos evitaría tener que hacer el casting.

Desde Java 5 contamos con una característica llamada generics que puede solventar esta clase de problemas. Los generics son una mejora al sistema de tipos que nos permite programar abstrayéndonos de los tipos de datos, de forma parecida a los templates o plantillas de C++ (pero mejor).

Gracias a los generics podemos especificar el tipo de objeto que introduciremos en la colección, de forma que el compilador conozca el tipo de objeto que vamos a utilizar, evitándonos así el casting. Además, gracias a esta información, el compilador podrá comprobar el tipo de los objetos que introducimos, y lanzar un error en tiempo de compilación si se intenta introducir un objeto de un tipo incompatible, en lugar de que se produzca una excepción en tiempo de ejecución.

Para utilizar generics con nuestras colecciones tan solo tenemos que indicar el tipo entre $<$ y$ >$ a la hora de crearla. A estas clases a las que podemos pasar un tipo como “parámetro” se les llama clases parametrizadas, clases genéricas, definiciones genéricas o simplemente genéricas (generics). Veamos un ejemplo con la colección List.

\begin{verbatim}
    import java.util.ArrayList;  
    import java.util.List;  
      
    public class Ejemplo {  
      public static void main(String[] args) {  
        List<String> lista = new ArrayList<String>();  
        lista.add("Hola mundo");  
      
        String cadena = lista.get(0);  
        System.out.println(cadena);  
      }  
    }  
\end{verbatim}

Este código, sin embargo, no compilaría, dado que hemos indicado que nuestra colección contendrá objetos de tipo String, y no Integer:

\begin{verbatim}
    import java.util.ArrayList;  
    import java.util.List;  
      
    public class Ejemplo {  
      public static void main(String[] args) {  
        List<String> lista = new ArrayList<String>();  
        lista.add(new Integer(22));  
      
        Integer numero = lista.get(0);  
        System.out.println(numero);  
      }  
    }  
\end{verbatim}

Algo a tener en cuenta es que el tipo parámetro debe ser una clase; no podemos utilizar tipos primitivos. Esto, sin embargo, no es ningún problema gracias a las características de autoboxing y unboxing de Java 5. En el código siguiente, por ejemplo, se crea un objeto Integer intermedio de forma transparente, que es el objeto que se introducirá en realidad en la colección, y no un int de valor 22, como podría parecer. Posteriormente el objeto Integer se vuelve a convertir en int automáticamente.

\begin{verbatim}
    import java.util.ArrayList;  
    import java.util.List;  
      
    public class Ejemplo {  
      public static void main(String[] args) {  
        List<Integer> lista = new ArrayList<Integer>();  
        lista.add(22);  
      
        int numero = lista.get(0);  
        System.out.println(numero);  
      }  
    }  
\end{verbatim}

Ahora, ¿cómo podemos crear nuestras propias clases parametrizadas? ¿cómo funciona a partir de Java 5 una colección como List? Sencillo. Solo tenemos que añadir el tipo parámetro después del nombre de la clase, y utilizar el nombre de este tipo genérico donde usaríamos un tipo concreto (con algunas excepciones). Por convención se suele utilizar una sola letra mayúscula para el tipo genérico.

Como ejemplo vamos a crear una pequeña clase que almacene un objeto, con su getter y setter, y que permita imprimir la cadena que lo representa, o bien imprimir la cadena al revés:

\begin{verbatim}
    public class Imprimidor<T> {  
      private T objeto;  
      
      public Imprimidor(T objeto) {  
        this.objeto = objeto;  
      }  
      
      public void setObjeto(T objeto) {  
        this.objeto = objeto;  
      }  
      
      public T getObjeto() {  
        return objeto;  
      }  
      
      public void imprimir() {  
        System.out.println(objeto.toString());  
      }  
      
      public void imprimir_reves() {  
        StringBuffer sb = new StringBuffer(objeto.toString());  
        System.out.println(sb.reverse());  
      }  
    }  
\end{verbatim}

Ahora podríamos utilizar nuestra nueva clase de la misma forma que hacíamos con List:

\begin{verbatim}
    public class Ejemplo {  
      public static void main(String[] args) {  
        Imprimidor<String> impStr = new Imprimidor<String>("Hola mundo");  
        impStr.imprimir();  
      
        Imprimidor<Integer> impInt = new Imprimidor<Integer>(143);  
        impInt.imprimir_reves();  
      }  
    }  
\end{verbatim}

En ocasiones también puede interesarnos limitar los tipos con los que se puede parametrizar nuestra clase. Por ejemplo, podríamos querer crear una clase con un método que imprimiera el resultado de dividir un número entre 2. No tendría sentido permitir que se pudiera parametrizar la clase con el tipo String; nos interesaría pues encontrar alguna forma de indicar que el tipo utilizado debe ser un subtipo de la clase Number. Nuestro código podría tener un aspecto similar al siguiente:

\begin{verbatim}
public class Divisor<T extends Number> {  
    private T numero;  

    public Divisor(T numero) {  
        this.numero = numero;  
    }  

    public void dividir() {  
        System.out.println(numero.doubleValue() / 2);  
    }
    
    static public void main(String args[]) {
        Divisor <Integer> a = new Divisor (10);
        //Divisor <String> a = new Divisor (10);
        a.dividir();
    }
} 
\end{verbatim}

Ahora, en Java no existe la herencia múltiple, pero lo que si podemos hacer es implementar varias interfaces distintas. Si quisieramos obligar que el tipo implemente varias interfaces distintas, o que extienda una clase e implemente una o varias interfaces, tendríamos que separar estos tipos con el carácter \&:

\begin{verbatim}
    public class Divisor<T extends A & B> {  
      ...  
    }  
\end{verbatim}

Otra cosa que podemos hacer es utilizar más de un parámetro de tipo, separando los tipos con comas. Supongamos por ejemplo que quisiéramos crear una clase que imprimiera la suma de dos números. Escribiríamos algo como esto:

\begin{verbatim}
public class Sumador<T1 extends Number, T2 extends Number> {  
    private T1 numero1;  
    private T2 numero2;  

    public Sumador(T1 numero1, T2 numero2) {  
        this.numero1 = numero1;  
        this.numero2 = numero2;  
    }  

    public void sumar() {  
       System.out.println(numero1.doubleValue() + numero2.doubleValue());  
    } 
    
    static public void main(String args[]) {
        Sumador <Float, Float> a = new Sumador(1,2);
        //Sumador <char, Float> b = new Sumador(1,2);
        a.sumar();
    }
} 
\end{verbatim}

Por supuesto también podemos crear clases abstractas e interfaces de forma similar a las clases genéricas:

\begin{verbatim}
    public interface MiColeccion<T> {  
      public void anyadir(T objeto);  
      public T obtener();  
      public void ordenar();  
    }  
\end{verbatim}

E incluso métodos, en cuyo caso se indica el nombre a utilizar para el tipo genérico antes del valor de retorno del método. Sustituyamos como ejemplo la clase que imprimía las cadenas al revés por un método estático:

\begin{verbatim}
     public class EjemploGenerics {  
      public static <T> void imprimir_reves(T objeto) {  
        StringBuffer sb = new StringBuffer(objeto.toString());  
        System.out.println(sb.reverse());  
      }  
    }  
\end{verbatim}

A la hora de llamar al método podemos especificar el tipo de la misma forma que hacíamos con las clases genéricas, aunque en este caso es menos útil:

\begin{verbatim}
    public class EjemploGenerics {  
      public static <T> void imprimir_reves(T objeto) {  
        StringBuffer sb = new StringBuffer(objeto.toString());  
        System.out.println(sb.reverse());  
      }  
      
      public static void main(String args[]) {  
        String cadena = new String("Hola");  
      
        EjemploGenerics ej = new EjemploGenerics();  
        ej.<string>imprimir_reves(cadena);  
      }  
    }</string>  
\end{verbatim}

También podemos omitirlo, por supuesto:

\begin{verbatim}
    public class EjemploGenerics {  
      public static <T> void imprimir_reves(T objeto) {  
        StringBuffer sb = new StringBuffer(objeto.toString());  
        System.out.println(sb.reverse());  
      }  
      
      public static void main(String args[]) {  
        String cadena = new String("Hola");  
        Integer entero = new Integer(12);  
        Float flotante = new Float(13.6);  
        Object objeto = new Object();  
      
        imprimir_reves(cadena);  
        imprimir_reves(entero);  
        imprimir_reves(flotante);  
        imprimir_reves(objeto);  
      }  
    }  
\end{verbatim}
 
 \section[Uso de comparadores]{Uso de java.util.Comparator and java.lang.Comparable  así como el paquete java.util para escribir código que ordene y realice búsquedas}

El uso de objetos que puedan compararse permite realizar algunas tareas en Java de manera muy eficiente. La clase Comparable de Java permite hacer que un objeto pueda ser comparado con otro con el propósito de utilizar métodos de ordenamiento y búsqueda. Para llevar a cabo el ordenamiento de una lista haremos uso del método estático de la clase Colections denominado sort. Dicho método tiene la siguiente sintaxis

\begin{verbatim}
Collections.sort(List)
\end{verbatim}

Para el caso de búsqueda podemos realizar una búsqueda iterativa o hacer uso de una búsqueda binaria definida como

\begin{verbatim}
Collections.binarySearch((List)miLista, Objet a)
\end{verbatim}

En ambos casos es necesario hacer un objeto que implemente la interface Comparable de Java y por lo tanto el método compareTo(Object a). 

El siguiente es un ejemplo de como hacer la implementación de estos métodos para buscar una persona ya sea por nombre, apellido o edad.

 \begin{verbatim}
 import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Uso_Comparadores implements Comparable {

    private String Nombre;
    private String Apellido;
    private int Edad;
    
    public Uso_Comparadores(String unNombre, String unApellido, int unaEdad) {
        Nombre   = unNombre;
        Apellido = unApellido;
        Edad     = unaEdad;
    }

    public int compareTo(Object arg0) {       
        Uso_Comparadores c = (Uso_Comparadores) arg0;
        // ordenar por Apellido
        //return this.Apellido.compareTo(c.Apellido);
        // ordenar por Nombre
        //return this.Nombre.compareTo(c.Nombre);

        return Integer.toString(this.Edad).compareTo(Integer.toString(c.Edad));
    }
    
    public String Datos() {
        return Nombre + " " + Apellido + " " + Edad;
    }
    
    static public void main(String args[]) {
        List <Uso_Comparadores> miLista = new ArrayList();
        
        miLista.add(new Uso_Comparadores("Juan", "Reyes", 21 ));
        miLista.add(new Uso_Comparadores("Luis", "Alba", 31 ));
        miLista.add(new Uso_Comparadores("Maria Luisa", "Jaimes", 23 ));
        miLista.add(new Uso_Comparadores("Adan", "Zavala", 18 ));
        
        System.out.println("Datos sin ordenar");
        for(Uso_Comparadores i : miLista) 
            System.out.println(i.Datos());
        
        System.out.println("Datos ordenados");        
        Collections.sort(miLista);

        for(Uso_Comparadores i : miLista) 
            System.out.println(i.Datos());
        
        System.out.println("Resultado de la Busqueda");
        
        Object a = new Uso_Comparadores("algo", "algo", 18);
        int indice = Collections.binarySearch((List)miLista, a);
        System.out.println(miLista.get(indice).Datos());
    }
}
 \end{verbatim}
 
 \subsection{Ejemplo de una lista de Empleados}
 
 El siguiente es un ejemplo de como hacer el ordenamiento de Empleados en un TreeMap
 
\begin{verbatim}
import java.util.Comparator;
import java.util.Set;
import java.util.TreeMap;


public class MapaOrdenado {
 
    public static void main(String a[]){
        
        //Comparacion por Nombre (String comparison)
    
        TreeMap<Empleado,String> tm = 
           new TreeMap<Empleado, String>(new ComparaNombre());
        tm.put(new Empleado("Ramom",3000), "RAM");
        tm.put(new Empleado("Juan",6000), "JOHN");
        tm.put(new Empleado("Critina",2000), "CRISH");
        tm.put(new Empleado("Tomas",2400), "TOM");
        Set<Empleado> keys = tm.keySet();
        for(Empleado key:keys){
            System.out.println(key+" ==> "+tm.get(key));
        }
        System.out.println("===================================");
        
        //Comparacion por salario
        
        TreeMap<Empleado,String> trmap = 
           new TreeMap<Empleado, String>(new ComparaSalario());
        trmap.put(new Empleado("Ramon",3000), "RAM");
        trmap.put(new Empleado("Juan",6000), "JOHN");
        trmap.put(new Empleado("Cristina",2000), "CRISH");
        trmap.put(new Empleado("Tomas",2400), "TOM");
        Set<Empleado> ks = trmap.keySet();
        for(Empleado key:ks){
            System.out.println(key+" ==> "+trmap.get(key));
        }
    }
}
 
class ComparaNombre implements Comparator<Empleado>{
 
    @Override
    public int compare(Empleado e1, Empleado e2) {
        return e1.getNombre().compareTo(e2.getNombre());
    }
}
 
class ComparaSalario implements Comparator<Empleado>{
 
    @Override
    public int compare(Empleado e1, Empleado e2) {
        if(e1.getSalario() > e2.getSalario()){
            return 1;
        } else {
            return -1;
        }
    }
}
 
class Empleado{
    private String nombre;
    private float salario;
     
    public Empleado(String n, int s){
        this.nombre = n;
        this.salario = s;
    }
    public String getNombre() {
        return nombre;
    }
    public float getSalario() {
        return salario;
    }   
    @Override
    public String toString(){
        return "Nombre: "+this.nombre+"-- Salario: "+this.salario;
    }
}
 \end{verbatim}
 
 \subsection{Ejemplo de Una agenda}
 
Considere el caso de una agenda personal donde deseamos guardar información correspondiente a personas. Cada una de las personas podrá tener uno o más teléfonos y estos quedarán identificados por el lugar donde lo tienen. Así por ejemplo Juan Pérez tendrá un teléfono de casa, un teléfono del trabajo y un teléfono movil.

Para comenzar creamos la clase Persona

\begin{verbatim}
package Agenda2;

import java.util.Map;
import java.util.TreeMap;

public class Persona {
    private String Nombre, Paterno, Materno;
    int Edad;
    private Map <String, String> telefonos;
    
    public Persona(String unNombre, String unPaterno, String unMaterno, 
            int unaEdad) {
        Nombre = unNombre;
        Paterno = unPaterno;
        Materno = unMaterno;
        Edad = unaEdad;
        
        telefonos = new TreeMap <String, String>();
    }
    
    public void agregaTelefono(String unaDescripcion, String unTelefono){
        telefonos.put(unaDescripcion, unTelefono);
    }
    
    public String getNombre() {
        return Nombre;
    } 
    public String getPaterno() {
        return Paterno;
    } 
    public String getMaterno() {
        return Materno;
    } 
    public int getEdad() {
        return Edad;
    } 
    
    void imprime() {
        System.out.println(Nombre + " " + Paterno + " " + Materno +
                " " + Edad);
        
        if(telefonos.keySet().isEmpty()) return;
        
        for(String a: telefonos.keySet()){
            System.out.println("\t" + a + " : " + telefonos.get(a) );
        }
    }
      
    @Override
    public String toString() {
        return Nombre + " " + Paterno + " " + Materno + " " +Edad;
    }
}
\end{verbatim}

\subsubsection{Clase Compara}

Esta clase es necesario implementar ya que es la responsable de decidir sobre el objeto persona como se ordenaran los datos. Esta implementación queda como:

\begin{verbatim}
package Agenda2;

import java.util.Comparator;

class Compara implements Comparator <Persona>{

    public Compara() {
    }

    @Override
    public int compare(Persona t, Persona t1) {
    
        // Ordena por apellido Paterno
    
        //return t.getPaterno().compareTo(t1.getPaterno());
        
        // Ordena por Edad
        
        return (t.getEdad()-t1.getEdad());

    }   
}
\end{verbatim}

\subsubsection{Implementación con Listas}

La implementación con Listas y utilizando Collections para ordenar queda

\begin{verbatim}
package Agenda2;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class MiAgenda_Listas {
    static public void main(String args[]) {
        List <Persona> ls = new ArrayList <Persona>();
        
        Persona p = new Persona("Jorge", "Pintor", "Leon", 19);
        p.agregaTelefono("Movil", "1231231");
        p.agregaTelefono("Casa", "912931239");
        ls.add(p);
        
        p = new Persona("Roberto", "Garcia", "Palomares", 25);
        ls.add(p);
        
        p = new Persona("Felipe", "Rodriguez", "Gonzalez", 24);
        p.agregaTelefono("Tralpu", "'2340923'4");
        p.agregaTelefono("Morelia", "1231");
        p.agregaTelefono("Nextel", "3423");
        p.agregaTelefono("Movil", "234");
        ls.add(p);
        
        for(Persona i: ls) 
            System.out.println(i.toString());
        
        Collections.sort(ls, new Compara());
        
        System.out.println("Despues de Ordenar");
        
        for(Persona i: ls) {
            i.imprime();
        }   
    }   
}
\end{verbatim}

\subsubsection{Implementación con TreeMaps}

La implementación con TreeMaps queda como

\begin{verbatim}
package Agenda2;

import java.util.Set;
import java.util.TreeMap;

public class MiAgenda_TreeMap {
    
    static public void main(String args[]) {
        TreeMap<Persona, String> tm = new TreeMap <Persona, String> (new Compara());
        Persona p = new Persona("Jorge", "Pintor", "Leon", 19);
        p.agregaTelefono("Movil", "1231231");
        p.agregaTelefono("Casa", "912931239");
        
        tm.put(p, "asd");
        
        p = new Persona("Roberto", "Garcia", "Palomares", 25);
        tm.put(p, "lkjdf");
        
        p = new Persona("Felipe", "Rodriguez", "Gonzalez", 24);
        p.agregaTelefono("Tralpu", "'2340923'4");
        p.agregaTelefono("Morelia", "1231");
        p.agregaTelefono("Nextel", "3423");
        p.agregaTelefono("Movil", "234");
        tm.put(p, "ñsldf");    
        
        Set <Persona> ks = tm.keySet();
        for(Persona k:ks) 
            k.imprime();
    }   
}
\end{verbatim}

