sábado, 31 de diciembre de 2011

Colecciones

votar
   Cuando en un lenguaje de programación es necesario tratar con un conjunto de datos, como por ejemplo el número de ventas realizadas, o el stock de un almacén, se emplean colecciones. En Java, las colecciones utilizan el marco de trabajo Java Collections Framework. Dentro de este marco, existen una serie de interfaces y clases diseñadas para tratar específicamente con conjuntos de datos. Si se te escapa el concepto de interfaz, puedes consultar la entrada correspondiente.

   Este marco esta ordenado de forma jerárquica. Primero está la interfaz Collection (no confundir con la clase Collections, en plural) a la que extienden las interfaces Set, List y Queue. A su vez, de estas interfaces parten otras interfaces y clases que tratan los datos de forma diferente según las necesidades de los mismos.
   Por otro lado tenemos la interfaz Map. Map forma parte del Java Collection Framework pero no tiene nada que ver con Collection. Más tarde volveremos con esta interfaz.

   Vayamos por ahora con Collection. Una colección es simplemente un grupo de objetos a los que llamamos elementos. Hay que tener en cuenta que las colecciones usan Genéricos, es decir, hay que declarar qué tipo de elementos los componen. Así, para declarar la interfaz Collection haríamos lo siguiente:
public interface Collection<E>
siendo E el nombre de los objetos de que está formada la colección. Por ejemplo, Collection<String>, o Collection<Integer>.

   Collection se usa cuando no hay que ser muy específico sobre cómo tratar los datos, y nos llega con los métodos que tiene esta interfaz. Cuando esto no es suficiente, usamos las interfaces que extienden Collection:
  • Set: es una colección que no puede contener elementos duplicados, como entradas de cine o nombres de clientes. Estos elementos pueden estar ordenados o no. La extiende la interfaz SortedSet, que tiene sus elementos ordenados, y la implementan tres clases
                 -HashSet: los elementos no están ordenados.
                 -TreeSet: los elementos están ordenados según un orden natural (de menor a mayor, lexicográficamente...) o según las reglas de comparación que provea el programador.
                  -LinkedHashSet: los elementos están colocados por el orden en que son insertados.
  • List: es una colección ordenada y que permite duplicados, por ejemplo, los libros vendidos en una semana. ArrayList, Vector y LinkedList son las tres clases que implementan List. Las tres tienen sus elementos ordenados según su índice.
  • Queue: es una colección que sirve para mantener los elementos antes de procesarlos. Normalmente, pero no siempre, los elementos están ordenados según el sistema FIFO (first-in-first-out), es decir, el primero en entrar es el primero en salir. PriorityQueue es una clase que implementa Queue y que ordena los elementos según su orden de prioridad.
   Volvamos ahora con la interfaz Map. Map es utilizado cuando tenemos un conjunto de valores y a cada uno de ellos le asignamos una clave. Por ejemplo, el nombre y el DNI, o el nombre de usuario y la contraseña. Un Map no puede contener claves duplicadas y cada clave referencia a un sólo valor (existen los multimaps, cuyas claves pueden referenciar a varios, pero ése es otro tema). Hashtable, TreeMap y HashMap son tres clases que implementan Map. De ellas, sólo TreeMap está ordenada según un orden natural o por las reglas de comparación dadas. Map también tiene una interfaz que la extiende: SortedMap (en realidad, TreeMap implementa NavigableMap, que extiende SortedMap, que extiende Map).

   Hay por último dos clases, Arrays y Collections, que extienden la clase Object, y que contienen un grupo de métodos útiles para usar con las colecciones. Puedes encontrar todos los métodos usados en estas clases e interfaces en los docs de Oracle.

  


sábado, 24 de diciembre de 2011

Novedades en Java 7

votar
   Acabo de leer un panfleto escrito por Madhusudhan Konda y publicado por O´Reilly sobre algunas de las novedades en Java 7 y he pensado que estaría bien hacer un resumen para aquellos que no lo conozcáis. Todos los ejemplos que aparecen a continuación son parte del escrito de Konda. Obviamente, no se tratan aquí todas las novedades que hay en Java 7, pero sí algunas de las que serán más útiles a los desarrolladores.

   Konda empieza tratando de las mejoras del lenguaje que se solicitaron a través del Project Coin. Una de las mejoras que se reclamaban en este proyecto era sobre el uso de Genéricos. Un ejemplo del uso de Genéricos hasta Java 7 sería como sigue:
   Map<String, List<Trade>>  trades = new TreeMap<String, List<Trade>>( );
   Esto resulta un poco lioso, porque hay que declarar los tipos a ambos lados. Observad ahora como quedaría usando Java 7:
   Map<String, List<Trade>> trades = new TreeMap<>( );
   Mucho mejor, ¿verdad?. Ya no hay que declarar los tipos a la derecha, porque el compilador infiere de qué tipos se trata viendo los que hay a la izquierda. Incluso sería legal omitir el operador de Genéricos (diamond operator), así:
   trades = new TreeMap( );
pero el compilador lanzaría advertencias de seguridad.

   Otra de las peticiones que se hicieron en el proyecto Coin fue el poder usar strings en sentencias Switch. Hasta ahora, las sentencias Switch usaban o bien tipos primitivos o bien tipos enumerados. Con Java 7 se pueden usar también strings, que antes tendrían que ir en una serie de if-else como estos:
   private void processTrade(Trade t){
         String status = t.getStatus( );
            if(status.equalsIgnoreCase(NEW)){
              newTrade(t);
             }else if(status.equalsIgnoreCase(EXECUTE)){
              executeTrade(t);
             }else if(status.equalsIgnoreCase(PENDING)){
              pendingTrade(t);
             }
           }
   Comparadlo ahora en como queda al poder usar una sentencia Switch:
      public void processTrade(Trade t){
         String status = t.getStatus( );
         switch (status){
            case NEW : newTrade(t);
                  break;
             case EXECUTE : executeTrade(t);
                  break;
             case PENDING : pendingTrade(t);
                  break; 
             default : break;
           }
         }

   Konda habla a continuación del manejo automático de recursos tales como Files, Input/OutputStreams, etc., que los desarrolladores tienen que cerrar manualmente, pero que ahora Java 7 puede realizar de forma automática. Hasta ahora el proceso era el siguiente:
   public void oldTry( ){
        try{
               fos = new FileOutputStream("movies.txt");
              dos = new DataOutputStream(fos);
              dos.writeUTF("Java 7 Block Buster");
       }catch (IOException e){
               e.printStackTrace( );
       }finally{
            try{
                   fos.close( );
                  dos.close( );
            }catch (IOException e){
                     //maneja la excepción
           }
        }
     }
   Y así es como se haría con Java 7:
   public void newTry( ){
            try(FileOutputStream fos = new FileOutputStream("movies.txt");
                 DataOutputStream dos = new DataOuputStream(fos))
                {
                    dos.writeUTF("Java 7 Block Buster");
                 }catch (IOException e){
                        //maneja la excepción
                }
       }
   Eso sí, para que esto funcione, los recursos que queremos que se cierren automáticamente tienen que implementar la interfaz java.lang.AutoCloseable. 

   Otra de las novedades introducidas en Java 7 es el uso de la barra baja para separar números literales y que sea más fácil leerlos. Por ejemplo, si hasta ahora un millón se escribía así:
   1000000
ahora lo escribiremos de esta forma:
   1_000_000
   También se introducen los literales binarios, con lo que ya no hay que convertirlos a hexadecimales y nos ahorramos un trabajo.

   Se ha mejorado también el manejo de excepciones múltiples, que ahora se pueden atrapar usando un único bloque. Hasta ahora, si tenías un método con, por ejemplo, tres excepciones, tenías que atraparlas de forma individual. Con Java 7 se puede hacer lo siguiente:
   public void newMultiCatch( ){
      try{
         methodThatThrowsThreeExceptions( );
      }catch(ExceptionOne | ExceptionTwo | ExceptionThree e){
             //maneja las excepciones
      }
    }
   Sin embargo, si tienes excepciones que pertenecen a distintos tipos que deben manejarse de distinta manera, se puede hacer lo siguiente: 
   public void newMultiMultiCatch( ){
       try{
          methodThatThrowsThreeExceptions( );
       }catch (ExceptionOne e){
              //maneja ExceptionOne
       }catch (ExceptionTwo | ExceptionThree e){
             //maneja ExceptionTwo y ExceptionThree
       }
   }

   Java 7 tiene también una nueva API, NIO 2.0, que proporciona mejoras a la hora de trabajar con Java IO. El nuevo paquete java.nio.file viene con nuevas clases e interfaces como Path, Paths, FileSystem, FileSystems y otras. Path, por ejemplo, es el equivalente mejorado de java.io.File. Lo mejor para comprobar como funciona NIO 2.0 es ir a los docs de Oracle y ver allí cómo funcionan estas nuevas clases y métodos.

   Otras de las mejoras está relacionada con la multitarea, para lo que se han creado las nuevas clases ForkJoinPool y ForkJoinTask.

   Por último, hay que mencionar la creación de otro nuevo paquete, java.lang.invoke, que permite que  la JVM soporte mucho mejor la utilización de lenguajes dinámicos tales como Phyton, Ruby y Clojure. Algunas de las nuevas clases que incorpora este paquete son MethodHandle y CallSite.

   Os recuerdo que este trabajo y los ejemplos aquí expuestos son obra de Madhusudhan Konda, que lo ha publicado en O´Reilly. Si queréis leerlo completo, yo me he descargado una versión gratuita a través de Amazon, y supongo que O´Reilly también ofrecerá esta posibilidad. ; )
   





   
   

sábado, 10 de diciembre de 2011

Interfaces

votar
   Cuando se comienza a estudiar Java, se aprenden normalmente primero los conceptos de Clase, Objeto, Métodos, Tipos, Variables, etc. Y de repente un día comienzan a hablarte de interfaces. ¿Interfaces?, pero, ¿qué es eso?, se pregunta el novato nervioso. ¿Es una clase?, ¿es un método?, ¿cómo se usa?, ¿es lo mismo que la interfaz de usuario? (vale, este último no estuvo muy atento en ninguna de las clases). Tranquilos todos, que aquí os vamos a explicar las interfaces como si fueseis niños.

   Una interfaz en Java, en realidad no es más que un cajón donde guardar uno o varios métodos de forma que todas las clases que lo necesiten puedan cogerlos y emplearlos. La cuestión es que esos métodos de las interfaces no tienen cuerpo, están vacíos; vamos, que no son más que el nombre, como si fuesen el método de una clase abstracta, así que cuando una clase va a utilizarlos tiene que implementarlos o desarrollarlos.

   Y por qué, te preguntarás, no puede una clase escribir directamente sus propios métodos, sin necesidad de implementar los de una interfaz. Bueno, aquí es donde viene el ejemplo explicativo.

   Supongamos que tienes una clase llamada Vehículos que tiene varias subclases: Coche, Moto, Camión, Autobús y Bicicleta. Esta clase Vehículos tiene una serie de métodos que por supuesto todas sus subclases heredan, como arrancar( ), frenar( ) o velocidad( ). Pero resulta que Coche, Camión y Autobús necesitan un método extra abrirPuertas( ). ¿Dónde lo pondrías?. Si lo colocas en Vehículos lo heredarían también Moto y Bicicleta, que no tienen ninguna puerta que abrir. Si estuviésemos en otro lenguaje, como C++ que permite la multiherencia podríamos hacer que las clases con puerta fuesen además de subclases de Vehículos, subclases de otra clase distinta con el método abrirPuertas( ), pero esto es Java. Claro, podemos escribir tres métodos distintos para cada una de las clases que lo necesiten, pero hay una forma mucho más económica de hacerlo: ir al cajón-interfaz que tenga este método y cogerlo.

   Así, no sólo estas clases pueden utilizar este método y adaptarlo a sus necesidades, sino que en realidad, cualquier otra clase no relacionada con ellas puede hacerlo también. Por ejemplo, si tenemos la clase Vivienda, también ella y todas sus subclases pueden utilizar el método abrirPuertas( ) sin tener ninguna relación con Vehículos ni sus subclases.

   La palabra mágica a utilizar es implements:
      class Coche extends Vehículos implements Abrible //Abrible es el nombre de la interfaz

   Una clase sólo puede extender otra (es una familia monoparental) pero puede implementar todas las interfaces que necesite. Es más, una interfaz también puede extender (que no implementar) otras interfaces. Esto es útil sobre todo cuando tú ya has escrito una interfaz y más adelante quieres añadirle otro método. Si lo haces en la vieja interfaz, todos los programas que estén usándola dejarán de funcionar porque ellos no implementan ese nuevo método. Así que puedes crear una nueva interfaz que extienda la antigua y que utilicen sólo los que necesiten ese nuevo método.

  ¿Cómo se escribe una interfaz?. Es muy similar a una clase:
   public interface Interface extends Interface2, Interface3{
             void abrirPuertas( );
   }

   Por supuesto, extends sólo se usa si la interfaz extiende alguna otra. El modificador public es para que cualquier clase pueda utilizar la interfaz. Si omites este modificador sólo las clases dentro del mismo paquete de la interfaz podrán utilizarla.
   Todos los métodos de una interfaz son implícitamente públicos y abstractos, así que no hay necesidad de añadirles esos modificadores.
   Las interfaces, además de métodos pueden tener constantes, que son siempre públicas, estáticas y finales, por lo que tampoco hay que añadirles esos modificadores. Por ejemplo:
      double Pi = 3.14;

   Cuando una clase implementa una interfaz, debe implementar todos los métodos de esa interfaz, y seguir todas las reglas de sobrescritura (override) de esos métodos.

   Además, una interfaz puede ser usada como un tipo de referencia en cualquier clase que la implemente, igual que si fuese un tipo primitivo o un objeto:
      Abrible ob1 = (Abrible)ob1;
   De esta forma, ob1 puede utilizar el método abrirPuertas( ) y es considerado no sólo como tipo de su clase, sino también como tipo de la interfaz.

   Si a pesar de la sencillez que hemos intentado usar en la explicación tienes alguna dificultad en seguirla, mándanos un mensaje o entra en la web JavaParaNulos donde encontrarás un Tutorial para principiantes.
   

sábado, 3 de diciembre de 2011

HashCode( ) y equals( )

votar
   Hoy vamos a hablar de dos métodos que están muy relacionados: equals( ) y hashCode( ). Estos dos métodos están en la clase Object, y como esta clase es la madre de todas clases, todas ellas heredan esto métodos. Por supuesto, como ya vimos en otro post, estos métodos pueden ser overriden (sobreescritos o sobrepasados).

   Estos métodos son muy utilizados al usar colecciones (ya hablaremos de ellas en otra entrada) pero hay que utilizarlos bien y saber cómo sobreescribirlos para no meter la pata.

   Expliquemos en qué consisten. El método equals( ) compara dos objetos y devuelve true si son iguales. Utiliza el operador (= =). ¡No confundir nunca con (=) que asigna un valor!. Cuando compara dos tipos primitivos es sencillo: 

   2300 = = 2300;
   2´575 = = 2´575;

   pero cuando comparamos objetos la cosa cambia, porque lo que equals( ) compara son las referencias a los objetos y no los mismos objetos, y si las referencias no son iguales considera que los objetos tampoco, aunque sean significativamente equivalentes. Lo entenderemos mejor explicando este ejemplo que podéis encontrar en el tutorial de Oracle.

   Supongamos que hay dos ejemplares de un libro:

   Libro primerEjemplar = new Libro( );
   Libro segundoEjemplar = new Libro( );

   Aunque sean dos ejemplares del mismo libro, y, por lo tanto, equivalentes para nosotros, el método equals( ) los tratará como distitntos a no ser que lo sobreescribamos o sobrepasemos. Ahora bien, los libros tienen un número de identificación, ISBN, que es igual para todos los ejemplares del mismo libro. Usando este número como referencia, podemos sobreescribir equals( )

   public class Libro{
      public boolean equals(Object o){
         if(o instanceof Libro) //nos aseguramos de que el objeto en cuestión es un libro
            return ISBN.equals((Libro)o.getISBN( )); //hacemos un casting para poder usar el nuevo método     equals()
              else
                return false;
      }
   }

   Veamos ahora cómo quedaría:
   
   Libro primerEjemplar = new Libro("0201914670"); //el mismo número que el ejemplo del tutorial
   Libro segundoEjemplar = new Libro("0201914670");
      if(primerEjemplar.equals(segundoEjemplar)){
         System.out.println("Los dos objetos son iguales");
      }else{
         System.out.println("Los objetos no son iguales");
      }
   
   Ahora, los dos ejemplares del mismo libro son considerados iguales porque lo que se compara es su ISBN.

   El método equals( ) tiene un contrato o normas que hay que cumplir al utilizarlo. Son las siguientes, y no, no estás en clase de matemáticas:

  • Es reflexivo. Para cualquier valor x, x.equals(x) debe devolver true.
  • Es simétrico. Para cualesquiera valores x e y, x.equals(y) es cierto si y sólo si y.equals(x) devuelve true.
  • Es transitivo. Para cualesquiera valores x, y y z, si x.equals(y) devuelve true e y.equals(z) devuelve true, entonces x.equals(z) debe devolver true.
  • Es consistente. Para cualesquiera valores x e y, x.equals(y) debe devolver consistentemente true o false mientras no se modifique ningún dato utilizado en la comparación.
  • Para cualquier valor x no vacío, x.equals(null) debe devolver falso.


   Además, si sobreescribes el método equals( ), también debes sobreescribir el método hashCode( ) ¿Por qué?.
   
   El valor que devuelve el método hashCode( ) es la alocación en la memoria del objeto, expresada en un número hexadecimal. (Excepto los supergeekies, este método normalmente se sobreescribe para que devuelva algo más comprensible al común de los mortales). Esto significa que, antes de sobreescribir el método equals( ), dos referencias a un objeto son iguales cuando el objeto es el mismo y por tanto ocupa el mismo lugar en la memoria. Pero al sobreescribir el método equals( ), vimos antes que estamos asignando la misma referencia a dos ejemplares distintos de un mismo libro. Así que el método hashCode( ) ya no es válido a no ser que lo sobreescribamos también, porque la regla básica es que si dos objetos son iguales según equals( ), su hashCode debe ser también igual.

   El hashCode viene siendo, pues, como el número de identidad de un objeto. Pero varios objetos pueden tener el mismo número de identidad. Incluso objetos distintos según equals( ).

   Imaginemos una cajonera donde en un cajón guardamos sólo bolígrafos, en otro sólo lápices, en otro sólo papel, etc. Cuando queremos un bolígrafo, sabemos perfectamente a cuál cajón ir. Ése es su hashCode. Pero resulta que no queremos un bolígrafo cualquiera, si no uno con tinta verde. Ahí es donde usamos el método equals( ). Vemos que todos los bolígrafos tienen el mismo hashCode, pero no todos son iguales. Eso sí, si dos objetos son bolígrafos, se guardan en el mismo cajón.

   HashCode( ) también tiene un contrato, que es como sigue:
  • Cuando durante la ejecución de una aplicación se invoque más de una vez el método hashCode( ), éste debe devolver consistentemente el mismo número, si no hay ningún cambio en equals( ). En distintas ejecuciones de la misma aplicación, el número puede variar.
  • Si dos objetos son iguales según equals( ), su hashCode es el mismo.
  • Si dos objetos son distintos según equals( ), su hashCode puede ser distinto o no.

   Claro, es mucho más eficiente a la hora de encontrar el bolígrafo deseado si estuvieran clasificados en distintos cajones según el color, pero a veces no tenemos tantos cajones, y otras veces nos da igual el color...como ya veremos al hablar de colecciones.

   Los que os sintáis un poco superados por este tema, podéis leer el libro de Kathy Sierra y Bert Bates "SCJP Sun Certified Programmer for Java 6". Los que queráis ampliarlo tenéis "Effective Java", de J. Bloch.
Y por supuesto, no olvidéis visitar Java Para Nulos ; )