sábado, 26 de noviembre de 2011

Manejo de Excepciones en Java

votar
Una excepción se produce cuando una condición excepcional interrumpe o altera el curso normal de un programa. Por ejemplo, un método que debe abrir un archivo se encuentra con que tal archivo no existe, o se intenta realizar una división siendo el divisor cero.

El manejo de excepciones consiste en escribir el código necesario para capturar una excepción y resolverla o al menos informar al usuario de lo que ocurre y que no se asuste. No es lo mismo ver una ventanita que nos informa amablemente de que no se encuentra el archivo o que inserte un disco a que la aplicación se quede muerta o ver un galimatías de números y letras (recordemos que el usuario final no suele ser un programador).

Como en Java todo se reduce a objetos, cuando ocurre una excepción el método que la provoca crea un objeto con información sobre el error y el sistema busca algo en el programa para manejarlo. Estos objetos, por supuesto, tienen que pertenecer a una clase. En Java, las excepciones derivan todas de la superclase Throwable, que está en el paquete java.lang. Throwable significa "arrojable" o "lanzable" en inglés, porque en Java las excepciones se "lanzan" o "arrojan" y hay que "pillarlas" o manejarlas con un gestor de excepciones (exception handler). De Throwable derivan las clases Error y Exception, y de esta última deriva la clase RuntimeException, cuyos objetos son errores que se producen en tiempo de ejecución.

Las excepciones pueden producirse por fallos de programación o por fallos imprevisibles del sistema. Estos últimos son los que forman la clase Error, de la que el sistema no se espera que se recupere y por eso no se requiere que se manejen. Vamos, que si te has quedado sin memoria, se va a ir todo a la porra hagas lo que hagas.

Entre las subclases de Error tenemos IOError, AWTError, CoderMalfunctioError, AssertionError, VirtualMachineError o mi favorita: ThreadDeath, que parece el título de una película. Pero hay muchas más.

Una de las grandes controversias en Java es que obligatoriamente hay una serie de excepciones que hay que manejar, quieras o no. Unos lo ven innecesario, otros imprescindible. Nosotros no vamos a mojarnos, sólo a explicar en qué consiste eso.

Las checked exceptions, o excepciones revisadas son las que derivan de la clase Exception, menos RuntimeException. Son las que se supone que hay que prever y de las que la aplicación se puede recuperar. Si no las manejas o las declaras, no podrás compilar tu programa.

Esto se hace de la siguiente manera: un método que pueda lanzar una excepción, bien lo hayas escrito tú, bien esté en el API, debe declararlo así
      public void miMétodoArriesgado() throws NombreDeLaExcepción;

Después, al utilizar ese método en el programa, hay que hacerlo dentro de un bloque try-catch (prueba y captura), que básicamente viene a decir: prueba a usar este método, y si produce una excepción, captúrala y haz lo que debas con ella. 
      try{
         public void miMétodoArriesgado(); //método que puede producir una excepción
      }catch (NombreDeLaExcepción e){
            //código que indica qué hacer si se produce
      }

Por supuesto, si la excepción no se produce, el código que escribimos dentro de catch no se ejecutará, si no que el programa seguirá normalmente. Además, si queremos que algo se ejecute independientemente de que surja o no una excepción, escribimos finally después de catch. Todo lo que va dentro de finally se ejecuta siempre, así que es muy útil para atar cabos sueltos, como cerrar archivos, etc.

Si no queremos usar un bloque try-catch para manejar la excepción, siempre nos queda la opción de esquivarla. Esto es como si alguien te lanza una pelota y tú te agachas con la esperanza de que otra persona la coja. Porque en alguna parte del programa, "alguien", léase un método, tiene necesariamente que pillar la excepción. El sistema va de método en método, hasta llegar a main() si hace falta, buscando un bloque de código que se ocupe de manejarla. Personalmente, esquivar la excepción no me gusta demasiado. Creo que es mejor lidiar con ella cuanto antes y listo. Pero a veces, por necesidades de programación, es mejor dejarla pasar y que otro método se ocupe. En ese caso hay que declarar el método como vimos, especificando la excepción que lanza.

Como sublcases de Exception tenemos IOExcepton, SQLException, ApplicationException, AWTException, BadLocationException, unas cuantas (muchas) más que hay en la librería Java y todas las que se te puedan ocurrir para tus métodos arriesgados. Porque por supuesto tú puedes crear tus propias excepciones.

Por último, están las RuntimeException y sus subclases, que como ya comentamos, no son excepciones revisadas, sino unchecked, es decir, no se revisan en el momento de la compilación. Estas excepciones tienen en común con las de clase Error que no se pueden anticipar ni normalmente recuperar de ellas, pero al contrario que estas, no se producen por fallos de sistema, sino casi siempre por fallos de programación, como errores de lógica. No es necesario declarar ni manejar estas excepciones, pero puedes hacerlo si quieres.

Dentro de RuntimeException podemos encontrar ArithmeticException,CannotRedoException, CannotUndoException,NullPointerException,NumberFormatException,ClassCastException,IndexOutOfBoundsException o BufferOverflowException. Pero claro, hay muchísimas más.

Si quieres dejar algún comentario o pregunta, ya sabes como ;)

  

   

sábado, 19 de noviembre de 2011

Overload vs. Override en Java

votar
   Overload y override, al ser dos términos que empiezan igual y al ser ambos utilizados con métodos, son muy dados a confusiones.

   He visto traducidos estos verbos al castellano como sobrecargar y sobrepasar, que utilizaré también aquí, pero a los que estéis pensando estudiar programación os recomiendo encarecidamente que, si no aprendéis a hablar inglés, al menos aprendáis a leerlo. Pensad que la mayoría de las novedades y libros de consulta sobre programación se escriben en ese idioma, y que, o bien no se traducen nunca al castellano, o cuando lo hacen, a veces ya han quedado desfasados.

   Volviendo con el tema, un método overloaded o sobrecargado no es más que la reutilización del nombre de un método. En la entrada anterior veíamos que esto también lo hacían los constructores.

   Supongamos que tienes el método suma( ) que tiene como argumentos a y b de tipo int y que utilizas ese método para sumar los argumentos

      int suma(int a, int b){
         //código que sume a+b
      }


pero después te encuentras con un par de decimales que también quieres sumar. Bastará con que hagas esto:

      double suma(double c, double d){
         //código que sume c+d
     }

   Como habéis visto, nos hemos limitado a cambiar los argumentos. El tipo de retorno es también diferente, pero eso no es necesario para sobrecargar un método. Podríamos haber escrito en los dos métodos void suma() y seguiría siendo overload.

   Lo importante es que los argumentos sean distintos, para que el compilador sepa qué método usar en cada caso. Eso sí, es mucho más lógico usar métodos con el mismo nombre que hagan lo mismo, porque sería un poco tonto llamar suma a un método que suma enteros y a otro que reste, aunque sea a decimales.

   Otros puntos a tener en cuenta son que al ser diferentes métodos pueden tener distintos modificadores de acceso (uno puede ser public y otro private, por ejemplo) y pueden lanzar diferentes excepciones. Los métodos de las subclases pueden sobrecargar a los métodos de las superclases, y aquí es donde a mayor confusión se presta con override.

   Override o sobrepasar un método es implementar eses método de una manera diferente. Por supuesto, eso es lo que hace una subclase con los métodos abstractos de su superclase, pero no es necesario que un método sea abstracto para que sea sobrepasado.

   Imaginemos un juego que tiene una clase Vehículos que a su vez tiene un método correr(). Esta clase tiene dos subclases, Coches y Motos que heredan el método correr() pero cada una lo implementa de forma distinta dependiendo de las diferentes características de las dos clases de vehículos. Eso es sobrepasar un método:

      public class TestVehículos{
         public static void main(String[] args){
            Vehículos v = new Vehículos();
            Coches c = new Coches();
            Motos m = new Motos();
            v.correr(); //ejecuta la versión correr() de Vehículos
            c.correr(); //ejecuta la versión correr() de Coches
            m.correr(); //ejecuta la versión correr() de Motos
         }
      }

   Ahora bien, sabemos que, aplicando el polimorfismo (otro día hablaremos de esto), podemos hacer algo así:

      Vehículos c = new Coches();
      Vehículos m = new Motos();
      c.correr();
      m.correr();

   ¿Qué versión de correr() se ejecutaría en estos casos? En tiempo de ejecución se aplica el tipo del Objeto (Coches y Motos) no el tipo de la variable de referencia (Vehículos), así que se ejecutará el método correr() de Coches y Motos.
 
   Pero cuidado, supongamos que la clase Coches tiene el método abrirPuertas(). Lo siguiente no sería legal:

      Vehículos c = new Coches();
      c.abrirPuertas();

   Porque el compilador no nos dejará que apliquemos un método de la clase Coches a una referencia de la clase Vehículos que no tiene ese método.

   Un método que sobrepase a otro no puede tener un modificador de acceso más restrictivo que el método sobrepasado, pero si más amplio. Por ejemplo, si correr() en Vehículos es protected, en Motos podría ser public pero no private.


   Tampoco puede lanzar excepciones nuevas o más amplias. El tipo de retorno tiene que ser el mismo o bien el de una subclase (esto último desde Java 5).

   Pero sobre todo, lo que nos ayuda a distinguir un overriding de un overloading es que si en este último la lista de argumentos tenía que ser distinta, en el primero debe ser exactamente igual.

   Espero que esta entrada os haya ayudado a aclarar conceptos, si no, ya sabéis que podéis enviar vuestras preguntas y comentarios.

sábado, 12 de noviembre de 2011

Constructores en Java

votar
   Parece una tontería y sin embargo, los constructores son de las cosas que más problemas causan a los programadores noveles.
   Los constructores sirven para crear nuevos objetos. Por ejemplo:
class Gata{
     public Gata( ){}
    Gata g = new Gata( );
}
   Bueno, en este ejemplo, no parece que un constructor parezca ser algo muy útil, y en muchas ocasiones, el programador no se molesta en escribirlo. Pero eso no significa que no esté ahí, porque si tú no lo escribes, el compilador siempre, siempre, siempre, pondrá el constructor por defecto, ya que si no, no sería posible crear ningún objeto. Así que, si escribimos esto:
class Gata{
    Gata g = new Gata( );
 }
el compilador interpretará esto:
class Gata{
    public Gata( ){
           super( );
    }
    Gata g = new Gata( );
}
   ¿Qué es eso de super( ); te estarás preguntando?. Bueno, todos los objetos en Java son parte de un enorme árbol genealógico. Todas las clases son descendientes, o subclases, de otra, aunque sólo sea de una, la madre de todas las clases: la clase Objeto. Cuando se ejecuta el constructor de una clase, ese constructor llama al constructor de su superclase, que también se ejecuta y a su vez llama al constructor de su superclase...y así hasta llegar a la clase Objeto. Ese super( ); es la llamada explícita al constructor de la superclase.
   ¿Y por qué hacer una llamada explícita si el compilador la hace por ti?. Supongamos este ejemplo:
class Mascota{
    private String nombre;
    public Mascota(String n){
        nombre = n;
    }
    public String getNonbre( ){
        return nombre;
    }
class Gata extends Mascota{
    public Gata( ){ }//escribes esto
       // super( ); pero es esto otro lo que hay en realidad    
}//Oh, oh, error de compilación.
   Efectivamente, el compilador hace una llamada al constructor por defecto de la superclase. El problema, es que la superclase y la clase no tienen el mismo constructor. El constructor por defecto no tiene argumentos, pero en este caso vemos que el constructor de Mascota tiene uno: String n.
   ¿Pero Mascota no tiene un constructor por defecto?. No, en el momento en que se escribe un constructor, el compilador no ve necesario añadir el constructor por defecto.
   Por supuesto, pueden coexistir varios constructores a la vez, así que si el programador escribe:
class Mascota{
    private String nombre;
    public Mascota(String n){
        nombre = n;
    }
    public Mascota( ){ }
    }
class Gata extends Mascota{
    public Gata( ){ }
}
el compilador no protestará porque puede llamar al constructor sin argumentos de la superclase. Veamos con   un ejemplo cómo funciona esta llamada a super( );
class Mascota{
    private String nombre;
    public String getNombre( ){
        return nombre;
    }
    public Mascota(String n){
        nombre = n;
        System.out.println("Esta es mi mascota");
}
class Gata extends Mascota{
    public Gata(String n){
        super(n);
    }
    public static void main(String[ ] args){
        Gata g = new Gata("Linda");
        System.out.println("El nombre de mi gata es " + g.getNombre();
    }
}
El resultado al ejecutar la clase Gata sería:
Esta es mi mascota
El nombre de mi gata es Linda
   Vemos que primero se ejecuta el constructor de la superclase y luego el de la clase.

   Además un constructor puede llamar a otro constructor de la misma clase usando this( ) Por ejemplo:
class Gata{
    String nombre;
    Gata(String n){
        nombre = n;
    }
    Gata( ){
        this("Bonita");
    }
    public static void main(String[ ] args){
        Gata g1 = new Gata("Linda");
        Gata g2 = new Gata( );
        System.out.println(g1);
        System.out.println(g2);
    }
}
   El constructor sin argumentos hace una llamada al primer constructor, que tiene un argumento de tipo String, y el resultado sería:
Linda
Bonita
   Tanto super( ) como this( ) deben usarse en la primera línea del constructor, pero no pueden usarse a la vez.

   Por último, no hay que confundir los constructores con los métodos. Los constructores siempre tienen el mismo nombre que la clase (y ningún programador que se precie escribiría un método con el nombre de la clase, aunque es factible), pero además, los constructores no tienen tipo de retorno, ni siquiera void, y los únicos modificadores que pueden usar son los de acceso.

   En fin, espero que este post os haya servido para clarificar algunas cosas respecto a los constructores.