miércoles, 29 de abril de 2009

La Clase Systemstrong>
Hay ocasiones en que se necesita acceder a recursos del sistema , como son los dispositivos de entrada/salida, el reloj del sistema, etc. Java dispone de la clase System, que proporciona acceso a estos recursos, independientemente de la plataforma. Es decir, que si se ejecuta un programa en una plataforma diferente a la que se ha desarrollado, no es necesaria ninguna modificación para tener en cuenta las peculiaridades de la nueva plataforma.

La clase System es miembro del paquete java.lang y en ella se definen los dispositivos estándar de entrada/salida

static PrintStream err;
static InputStream in;
static PrintStream out;
y dispone de varios métodos, algunos de los cuales ya se han utilizado en secciones anteriores, sin saber muy bien lo que se estaba haciendo, cosa que se intentará remediar ahora.

static void arraycopy( Object,int,Object,int,int )
static long currentTimeMillis()
static void exit( int )
static void gc()
static Properties getProperties()
static String getPropertie( String )
static String getPropertie( String,String )
static SecurityManager getSecurityManager()
static native int identityHashCode( Object )
static void load( String )
static void loadLibrary( String )
static void runFinalization()
static void runFinalizersOnExit( boolean )
static void setErr( PrintStream )
static void setIn( InputStream )
static void setOut( PrintStream )
static void setProperties( Properties )
static void setSecurityManager( SecurityManager )
No se puede instanciar ningún objeto de la clase System, porque es una clase final y todos sus contenidos son privados; por ellos es por lo que no hay una lista de constructores en la enumeración de métodos. Es decir, la clase System siempre está ahí disponible para que se pueda invocar cualquiera de sus métodos utilizando la sintaxis de punto (.) ya conocida

System.out.println( "Hola Java" );

Entrada/Salida estándar

En otra sección del Tutorial se trata a fondo este tema de la entrada y salida, aquí solamente interesa su relación con la clase System. Los dispositivos de entrada/salida probablemente sean uno de los aspectos más utilizado de esta clase.

La clase System proporciona automáticamente cuando comienza la ejecución de un programa, un stream para leer del dispositivo estándar de entrada (normalmente, el teclado), un stream para presentar información en el dispositivo estándar de salida (normalmente, la pantalla) y otro stream donde presentar mensajes de error, que es el dispositivo estándar de error (normalmente, la pantalla).

Los tres streams de entrada/salida están controlados por esta clase y se referencian como:

System.in entrada estándar
System.out salida estándar
System.err salida de error estándar

Las variables internas de la clase System out y err son de tipo PrintStream, es decir, que tienen acceso a los métodos de la clase PrintStream. La clase PrintStream proporciona tres métodos para poder visualizar información: print(), println() y write().

Los dos primeros ya se han utilizado en el Tutorial ampliamente, con lo que no resultan extrañas sentencias como:

System.out.print( … );
System.out.println( … );
System.out.write( … );
Los métodos print() y println() son semejante, la única diferencia es que println() coloca automáticamente un carácter nueva línea en el stream, tras la lista de argumentos que se le pase.

El método write() se utiliza para escribir bytes en el stream, es decir, para escribir datos que no pueden interpretarse como texto, como pueden ser los datos que componen un gráfico.

Los métodos print() y println() aceptan un argumento de cualquiera de los siguientes tipos: Object, String, char[], int, long, float, double o boolean. En cada caso, el sistema convierte el dato a un conjunto de caracteres que transfiere al dispositivo estándar de salida. Si se invoca al método println() sin argumentos, simplemente se inserta un carácter nueva línea en el stream.

Además, hay versiones sobrecargadas de estos métodos para visualizar adecuadamente objetos de varias clases estándar. Por ejemplo, las siguientes sentencias:

Thread obj = new Thread;
System.out.println( obj );
Producirían la siguiente salida en pantalla:

Thread[Thread-4,5,main]
Cuando se utilizan print() y println() sobre un objeto, la salida dependerá de ese objeto; por ejemplo, si se imprime un objeto String, visualizaremos el contenido de la cadena y si se imprime un objeto Thread obtenemos una salida en formato:

claseThread[nombre,prioridad,grupo]
como en el ejemplo anterior.

Propiedades del Sistema

Ya se ha indicado al tratar la clase Properties que las Propiedades son pares de clave/valor que los programas Java pueden utilizar para establecer y mantener diversos atributos o parámetros, que estarían disponibles en todas sus invocaciones.

El sistema Java también mantiene un conjunto de Propiedades del Sistema que contienen información acerca del entorno en que se está ejecutando como: el usuario actual, la versión actual del ejecutable de Java, etc. Estas Propiedades se fijan cuando arranca el Sistema.

En la fecha de hoy, invierno del 97, los applets pueden acceder a algunas de las Propiedades del sistema, pero no a todas. Además, los applets no pueden escribir las Propiedades del sistema. Supongo que en adelante esto cambiará.

La clase System dispone de varios métodos para leer y escribir las Propiedades del sistema. A estas Propiedades se puede acceder a través de la clave o se puede leer el conjunto completo de Propiedades.

Los dos métodos que proporciona la clase System para leer las propiedades del sistema son getProperty() y getProperties(). De la primera hay dos versiones sobrecargadas. La primera de ellas es:

static String getProperty( String key );
Solamente tiene un argumento y devuelve un objeto de tipo String. Si no es capaz de encontrar la propiedad indicada en la clave, devolverá una referencia nula. La segunda versión sobrecargada de getProperty()

static String getProperty( String clave,String def );
necesita dos argumentos. El primero es la propiedad clave que se quiere consultar y el segundo argumento es el valor por defecto que devolverá el método si la propiedad clave no se encuentra entre las propiedades, o si esa propiedad clave sí se encuentra pero no tiene asignado ningún valor.

El tercer método para acceder a las Propiedades del sistema es getProperties(), que devuelve un objeto Properties conteniendo el conjunto completo de pares clave/valor del sistema. Una vez obtenido este objeto, se puede usar cualquiera de sus métodos para obtener la información que se necesite.

Las Propiedades del sistema también se pueden modificar a través del método setProperties() de esta clase System. Este método coge un objeto Properties que haya sido inicializado con el adecuado conjunto de pares clave/valor que se desee y reemplaza el conjunto completo de Propiedades del sistema por los nuevos valores representados por el objeto.

En general, hay que tener cuidado de no sobreescribir las Propiedades del sistema. El método setProperties() cambia las Propiedades del sistema solamente para la ejecución actual en que se está utilizando. El sistema Java reinicializa las Propiedades cada vez que se arranca, luego los cambios realizados por este método no son persistentes. Si se quiere que lo sean, hay que salvarlos en un fichero antes de concluir la aplicación y cargarlos de nuevo al arrancar. La clase Properties proporciona métodos para realizar ambas tareas.

Finalización

Todos los objetos en Java son instanciados dinámicamente, en tiempo de ejecución, en la pila. Cuando ya no exista variable alguna que referencie al objeto, éste será marcado para su reciclado.

El reciclador de memoria, o cualquier nombre que quiera dársele, se ejecuta asíncronamente en segundo plano, recogiendo los objetos que ya no estén referenciados y haciendo que la memoria que ocupaban quede libre y sea devuelta al sistema para su reutilización.

El método finalize() de un objeto siempre se ejecuta antes de que la memoria ocupada por ese objeto sea liberada. Este método se puede sobreescribir para las clases que se desee, de forma que se ejecuten un conjunto de setencias determinado antes de liberar la memoria.

Se puede forzar una ejecución del reciclador de memoria invocando al método gc(). Además, se puede forzar a que el sistema ejecute la finalización de los objetos utilizando la llamada al método runFinalization(), que invocará a todos los métodos finalize() de los objetos que estén marcados para ser reciclados.

Copia de arrays

Para copiar eficientemente datos desde un array a otro se puede utilizar el método arraycopy(). Este método requiere cinco argumentos, de forma que se indiquen los arrays de origen y destino y el número de elementos que se van a copiar.

static void arraycopy( Object src,int src_position,Object dst,
int dst_position,int length );
El array destino debe estar localizado en memoria antes de llamarlo y debe ser lo suficientemente largo para contener los datos que se le van a pasar.

Salida del Sistema

Se puede abandonar el intérprete Java llamando al método exit() y pasándole un int como código de salida. Sin embargo, la invocación de este método está sujeta a restricciones de seguridad, Así, dependiendo del navegador sobre el que se esté ejecutando un applet, una llamada a exit() desde dentro del applet puede originar una excepción de seguridad, SecurityException.

Seguridad

El controlador de seguridad es un objeto que asegura una cierta política de seguridad a la aplicación Java. Se puede fijar el controlador de seguridad para las aplicaciones utilizando el método setSecurityManager() y se puede recuperar el que esté actualmente definido utilizando el método getSecurityManager().

El controlador de seguridad para una aplicación solamente se puede fijar una vez. Normalmente, un navegador fija su controlador de seguridad al arrancar, con lo cual, en acciones posteriores los applets no pueden fijarlo de nuevo, o se originará una excepción de seguridad si el applet lo intenta.

Recursos dependientes del Sistema

Deberíamos ser capaces de satisfacer todas nuestras necesidades de programación utilizando la interfaz independiente de plataforma que proporciona la clase System. Pero también es posible saltarse este interfaz y acceder directamente a los recursos del sistema en que se esté ejecutando el programa Java.

El sistema Java proporciona un objeto Runtime que representa el entorno en que se está corriendo, y que se puede utilizar para acceder directamente a los recursos de la plataforma.

Al hacer esto, probablemente se esté perdiendo parte de la portabilidad que caracteriza a Java, pero se está ganando velocidad de ejecución, o la obtención de información propia del sistema en que se está ejecutando la aplicación.

Colecciones
Java tiene matrices para almacenar grupos de datos de tipo similar, que son muy útiles para modelos simples de acceso a datos. Sin embargo, las Colecciones o enumeraciones ofrecen una manera más completa y orientada a objetos para almacenar conjuntos de datos de tipo similar. Las Colecciones tienen su propia asignación de memoria y posibilidad de una nueva asignación para ampliarlas. Tienen interfaces de método para su iteración y recorrido. Se pueden indexar mediante algo más complejo y útil que los simples enteros. Hasta el JDK 1.1, las Colecciones parecían estar incorporadas a Java por necesidad de aportar alguna herramienta para el almacenamiento de datos. Pero en el JDK 1.2, se han revisado completamente las Colecciones y ahora el programador dispone de toda la potencia de estas nuevas estructuras de datos, a las cuales se ha hecho referencia en secciones anteriores, a las cuales debe remitirse el lector, aquí solamente se citarán por estar incluidas dentro de las clases base de Java, y muy por encima.

Enumeraciones

Enumeration es un interfaz simple que permite enumerar todos los elementos de cualquier conjunto de objetos. Especifica dos métodos:

Bboolean hasMoreElements()
Object nextElement()
El primer método devuelve true cuando todavía quedan más elementos que extraer y false cuando se han enumerado todos los elementos del conjunto. El segundo método, devuelve una referencia a objeto genérica, cuyo tipo hay que convertir al tipo de clase de la cual el objeto es una instancia.

En el programa de ejemplo, java816.java, se utiliza una clase llamada Coleccion para implementar una enumeración de objetos de tipo Integer, y la clase principal que crea una instancia de Coleccion, itera sobre sus valores e imprime cada uno de ellos. Coleccion no contiene realmente ningún dato; simplemente devuelve la secuencia de enteros que ha construido.

Vector

Un Vector es una matriz ampliable de referencia a objeto. Internamente, un Vector implementa una estrategia de crecimiento para minimizar la reasignación y el espacio desperdiciado. Los objetos se pueden almacenar al final de un Vector utilizando el método addElement() o en un índice dado mediante el método insertElement(). Se puede almacenar una matriz de objetos en un Vector utilizando el método copyInto(). Una vez se ha almacenado un conjunto de objetos en un Vector, se puede utilizar para buscar un elemento en concreto utilizando los métodos contains(), indexOf() o lastIndexOf(). También se puede extraer un objeto de una posición específica de un Vector utilizando los métodos elementAt(), firstElement() y lastElement().

Un Stack, pila, es una subclase de Vector que implementa una pila simple del tipo FIFO (primero en entrar, primero en salir. Además de los métodos estándar de la clase padre, Stack implementa el método push(), que coloca objetos en la parte superior de la pila y el método pop() que retira y devuelve el objeto superior de la pila. También dispone del método peek() para obtener el objeto superior de la pila, pro no retirarlo. El método empty() devolverá true si no hay nada en la pila. El método search() comprobará si existe un objeto en la pila y devolverá el número de llamadas al método pop() que se necesitarán realizar para que dicho objeto se quede en la parte superior de la pila, o –1 si el objeto pasado como parámetro no se encuentra.

Diccionario

Un Dictionary es una clase abstracta que representa un depósito para almacenar claves/valores. Una clave es un nombre que se utiliza para recuperar un valor más adelante. Dada una clave y un valor, se puede almacenar el valor en un Dictionary con el método put( clave,valor ). Después se puede utilizar get( clave ) para recuperar el valor. Se pueden devolver las claves y valores como una Enumeration utilizando los métodos keys() y elements(). El método size() devuelve el número de parejas clave/valor almacenadas en un diccionario y el método isEmpty() devuelve true cuando no queda ninguna pareja. Se puede utilizar el método remove( clave ) para eliminar una pareja clave/valor.

Una Hashtable, tabla hash, es una implementación concreta de un Dictionary. Se puede utilizar una instancia de Hashtable para almacenar objetos arbitrarios que están indexados por cualquier otro objeto arbitrario. La utilización más habitual de una Hashtable es utilizar un String como clave para almacenar objetos como valores. El ejemplo java818.java, crea una tabla hash para almacenar información acerca del Tutorial.

import java.util.Dictionary;
import java.util.Hashtable;

class java818 {
public static void main( String args[] ) {
Hashtable ht = new Hashtable();

ht.put( "titulo","Tutorial de Java" );
ht.put( "autor","Agustin Froufe" );
ht.put( "email","froufe@arrakis.es" );
ht.put( "patria","Spain" );
ht.put( "edad",new Integer( 31 ) );
show( ht );
}

static void show( Dictionary d ) {
System.out.println( "Titulo: " + d.get( "titulo" ) );
System.out.println( "Autor: " + d.get( "autor" ) );
System.out.println( "E-mail: " + d.get( "email" ) );
System.out.println( "Pais: " + d.get( "patria" ) );
System.out.println( "Edad: " + d.get( "edad" ) );
}
}
La salida del programa, muestra cómo el método show(), que toma un Dictionary abstracto como parámetro, es capaz de recuperar todos los valores que se han almacenado en el método main().

C:\> java java818
Titulo: Tutorial de Java
Autor: Agustin Froufe
E-mail: froufe@arrakis.es
Pais: Spain
Edad: 31

EXEPCION DE JAVA:

Una excepción es un evento que ocurre durante la ejecución de un programa y detiene el flujo normal de la secuencia de instrucciones de ese programa; en otras palabras, una excepción es una condición anormal que surge en una secuencia de código durante su ejecución.
Las excepciones en Java están destinadas, al igual que en el resto de los lenguajes que las soportan, para la detección y corrección de errores. Si hay un error, la aplicación no debería morirse y generar un core (o un crash en caso del DOS). Se debería lanzar (throw) una excepción que a su vez debería capturar (catch) y resolver la situación de error, o poder ser tratada finalmente (finally) por un gestor por defecto u omisión. Java sigue el mismo modelo de excepciones que se utiliza en C++. Utilizadas en forma adecuada, las excepciones aumentan en gran medida la robustez de las aplicaciones.
En este Tutorial de Java no se pretende entrar en detalle en las ventajas que representa la utilización de excepciones, para ello debe el lector recurrir al Java Tutorial de Sun, en donde hay una completa discusión acerca de este aspecto de Java.
La gestión de excepciones en Java proporciona un mecanismo excepcionalmente poderoso para controlar programas que tengan muchas características dinámicas durante su ejecución. Las excepciones son formas muy limpias de manejar errores y problemas inesperados en la lógica del programa, y no deberían considerarse como un mecanismo general de ramificaciones o un tipo de sentencias de salto. Los lugares más indicados para utilizar excepciones son aquellos en los que se usan valores como 0 o 1, en C/C++, para indicar algún fallo funcional. Por ejemplo:#include
int fd;
fd = open( "leeme.txt" );
if( fd == -1 && errno == EEXIT )
fd = open( "defecto.txt" );
}
En este programa C, si falla la primera sentencia open() por cualquiera de las 19 razones distintas de EEXIT por las que puede fallar, entonces el programa se continuaría ejecutando y moriría por alguna razón misteriosa más adelante, dejando atrás un problema de depuración complicado y frustrante.
La versión Java del código anterior, tal como se muestra a continuación: FilterReader fr;
try {
Fr = new FilterReader( "leeme.txt" );
} catch( FileNotFoundException e ) {
fr = new FilterReader( "defecto.txt" );
}
proporciona una oportunidad para capturar una excepción más genérica y tratar la situación con elegancia o, en el peor de los casos, imprimiría el estado de la pila de memoria.
Por ello, que la utilización adecuada de las excepciones proporcionará un refinamiento profesional al código que cualquier usuario futuro de las aplicaciones que salgan de la mano del programador que las utilice agradecerá con toda seguridad.
Manejo de excepciones
A continuación se muestra cómo se utilizan las excepciones, reconvirtiendo en primer lugar el applet de saludo a partir de la versión iterativa de HolaIte.java:import java.awt.*;
import java.applet.Applet;
public class HolaIte extends Applet {
private int i = 0;
private String Saludos[] = {
"Hola Mundo!",
"HOLA Mundo!",
"HOLA MUNDO!!"
};
public void paint( Graphics g ) {
g.drawString( Saludos[i],25,25 );
i++;
}
}
Normalmente, un programa termina con un mensaje de error cuando se lanza una excepción. Sin embargo, Java tiene mecanismos para excepciones que permiten ver qué excepción se ha producido e intentar recuperarse de ella.
Vamos a reescribir el método paint() de esa versión iterativa del saludo:public void paint( Graphics g ) {
try {
g.drawString( Saludos[i],25,25 );
} catch( ArrayIndexOutOfBoundsException e ) {
g.drawString( "Saludos desbordado",25,25 );
} catch( Exception e ) {
// Cualquier otra excepción
System.out.println( e.toString() );
} finally {
System.out.println( "Esto se imprime siempre!" );
}
i++;
}
La palabra clave finally define un bloque de código que se quiere que sea ejecutado siempre, de acuerdo a si se capturó la excepción o no. En el ejemplo anterior, la salida en la consola, con i=4 sería:C:\>java HolaIte
Saludos desbordado
¡Esto se imprime siempre!

Cuando se produce una condición excepcional en el transcurso de la ejecución de un programa, se debería generar, o lanzar, una excepción. Esta excepción es un objeto derivado directa, o indirectamente, de la clase Throwable. Tanto el intérprete Java como muchos métodos de las múltiples clases de Java pueden lanzar excepciones y errores.
La clase Throwable tiene dos subclases: Error y Exception. Un Error indica que se ha producido un fallo no recuperable, del que no se puede recuperar la ejecución normal del programa, por lo tanto, en este caso no hay nada que hacer. Los errores, normalmente, hacen que el intérprete Java presente un mensaje en el dispositivo estándar de salida y concluya la ejecución del programa. El único caso en que esto no es así, es cuando se produce la muerte de un thread, en cuyo caso se genera el error ThreadDead, que lo que hace es concluir la ejecución de ese hilo, pero ni presenta mensajes en pantalla ni afecto a otros hilos que se estén ejecutando.
Una Exception indicará una condición anormal que puede ser subsanada para evitar la terminación de la ejecución del programa. Hay nueve subclases de la clase Exception ya predefinidas, y cada una de ellas, a su vez, tiene numerosas subclases.
Para que un método en Java, pueda lanzar excepciones, hay que indicarlo expresamente.void MetodoAsesino() throws NullPointerException,CaidaException
Se pueden definir excepciones propias, no hay por qué limitarse a las nueve predefinidas y a sus subclases; bastará con extender la clase Exception y proporcionar la funcionalidad extra que requiera el tratamiento de esa excepción.
También pueden producirse excepciones no de forma explícita como en el caso anterior, sino de forma implícita cuando se realiza alguna acción ilegal o no válida.
Las excepciones, pues, pueden originarse de dos modos: el programa hace algo ilegal (caso normal), o el programa explícitamente genera una excepción ejecutando la sentencia throw (caso menos normal). La sentencia throw tiene la siguiente forma: throw ObtejoExcepction;
El objeto ObjetoException es un objeto de una clase que extiende la clase Exception.
Origina una excepción de división por cero:class java901 {
public static void main( String[] a ) {
int i=0, j=0, k;
k = i/j; // Origina un error de division-by-zero
}
}
Si compilamos y ejecutamos esta aplicación Java, obtendremos la siguiente salida por pantalla:% javac java901.java
% java java901
java.lang.ArithmeticException: / by zero
at java901.main(java901.java:25)
Las excepciones predefinidas, como ArithmeticException, se conocen como excepciones runtime. Actualmente, como todas las excepciones son eventos runtime, sería mejor llamarlas excepciones irrecuperables. Esto contrasta con las excepciones que se generan explícitamente, a petición del programador, que suelen ser mucho menos severas y en la mayoría de los casos no resulta complicado recuperarse de ellas. Por ejemplo, si un fichero no puede abrirse, se puede preguntar al usuario que indique otro fichero; o si una estructura de datos se encuentra completa, siempre se podrá sobreescribir algún elemento que ya no se necesite.

Todas las excepciones deben llevar un mensaje asociado a ellas al que se puede acceder utilizando el método getMessage(), que presentará un mensaj describiendo el error o la excepción que se ha producido.

Si se desea, se pueden invocar otros métodos de la clase Throwable que presentan un traceado de la pila en donde se ha producido la excepción, o también se pueden invocar para convertir el objeto Exception en una cadena, que siempre es más intelegible y agradable a la vista.

Los nombres de las excepciones indican la condición de error que representan. Las siguientes son las excepciones predefinidas más frecuentes que se pueden encontrar:

ArithmeticException
Las excepciones aritméticas son típicamente el resultado de división por 0:int i = 12 / 0;
NullPointerException
Se produce cuando se intenta acceder a una variable o método antes de ser definido:class Hola extends Applet {
Image img;
paint( Graphics g ) {
g.drawImage( img,25,25,this );
}
}
IncompatibleClassChangeException
El intento de cambiar una clase afectada por referencias en otros objetos, específicamente cuando esos objetos todavía no han sido recompilados.
ClassCastException
El intento de convertir un objeto a otra clase que no es válida.y = (Prueba)x; // donde x no es de tipo Prueba
NegativeArraySizeException
Puede ocurrir si hay un error aritmético al cambiar el tamaño de un array.
OutOfMemoryException
¡No debería producirse nunca! El intento de crear un objeto con el operador new ha fallado por falta de memoria. Y siempre tendría que haber memoria suficiente porque el garbage collector se encarga de proporcionarla al ir liberando objetos que no se usan y devolviendo memoria al sistema.
NoClassDefFoundException
Se referenció una clase que el sistema es incapaz de encontrar.
ArrayIndexOutOfBoundsException <
Es la excepción que más frecuentemente se produce. Se genera al intentar acceder a un elemento de un array más allá de los límites definidos inicialmente para ese array.
UnsatisfiedLinkException
Se hizo el intento de acceder a un método nativo que no existe. Aquí no existe un método a.kk()class A {
native void kk();
}
y se llama a a.kk(), cuando debería llamar a A.kk().
InternalException
Este error se reserva para eventos que no deberían ocurrir. Por definición, el usuario nunca debería ver este error y esta excepción no debería lanzarse.
El compilador Java obliga al programador a proporcionar el código de manejo o control de algunas de las excepciones predefinidas por el lenguaje. Por ejemplo, el siguiente programa java902.java, no compilará porque no se captura la excepción InterruptedException que puede lanzar el método sleep().import java.lang.Thread;
class java902 {
public static void main( String args[] ) {
java902 obj = new java902();
obj.miMetodo();
}

void miMetodo() {
// Aqui se produce el error de compilacion, porque no se esta
// declarando la excepcion que genera este metodo
Thread.currentThread().sleep( 1000 ); // currentThread() genera
// una excepcion
}
}
Este es un programa muy simple, que al intentar compilar, producirá el siguiente error de compilación que se visualizará en la pantalla tal como se reproduce a continuación:% javac java902.java
java902.java:41: Exception java.lang.InterruptedException must be caught,
or it must be declared in the throws clause of this method.
Thread.currentThread().sleep( 1000 ); // currentThread() genera
^
Como no se ha previsto la captura de la excepción, el programa no compila. El error identifica la llamada al método sleep() como origen del problema. Así que, la siguiente versión del programa, soluciona el problema generado por esta llamada.import java.lang.Thread;
class java903 {
public static void main( String args[] ) {
// Se instancia un objeto
java903 obj = new java903();
// Se crea la secuencia try/catch que llamara al metodo que
// lanza la excepcion
try {
// Llamada al metodo que genera la excepcion
obj.miMetodo();
}catch(InterruptedException e){} // Procesa la excepcion
}

// Este es el metodo que va a lanzar la excepcion
void miMetodo() throws InterruptedException {
Thread.currentThread().sleep( 1000 ); // currentThread() genera
// una excepcion
}
}
Lo único que se ha hecho es indicar al compilador que el método miMetodo() puede lanzar excepciones de tipo InterruptedException. Con ello conseguimos propagar las excepción que genera el método sleep() al nivel siguiente de la jerarquía de clases. Es decir, en realidad no se resuelve el problema sino que se está pasando a otro método para que lo resuelva él.

En el método main() se proporciona la estructura que resuelve el problema de compilación, aunque no haga nada, por el momento. Esta estructura consta de un bloque try y un bloque catch, que se puede interpretar como que intentará ejecutar el código del bloque try y si hubiese una nueva excepción del tipo que indica el bloque catch, se ejecutaría el código de este bloque, si ejecutar nada del try.

La transferencia de control al bloque catch no es una llamada a un método, es una transferencia incondicional, es decir, no hay un retorno de un bloque catch.
Crear Excepciones Propias
También el programador puede lanzar sus propias excepciones, extendiendo la clase System.exception. Por ejemplo, considérese un programa cliente/servidor. El código cliente se intenta conectar al servidor, y durante 5 segundos se espera a que conteste el servidor. Si el servidor no responde, el servidor lanzaría la excepción de time-out:class ServerTimeOutException extends Exception {}
public void conectame( String nombreServidor ) throws Exception {
int exito;
int puerto = 80;
exito = open( nombreServidor,puerto );
if( exito == -1 )
throw ServerTimeOutException;
}
Si se quieren capturar las propias excepciones, se deberá utilizar la sentencia try:public void encuentraServidor() {
...
try {
conectame( servidorDefecto );
catch( ServerTimeOutException e ) {
g.drawString(
"Time-out del Servidor, intentando alternativa",5,5 );
conectame( servidorAlterno );
}
...
}
Cualquier método que lance una excepción también debe capturarla, o declararla como parte del interfaz del método. Cabe preguntarse entonces, el porqué de lanzar una excepción si hay que capturarla en el mismo método. La respuesta es que las excepciones no simplifican el trabajo del control de errores. Tienen la ventaja de que se puede tener muy localizado el control de errores y no hay que controlar millones de valores de retorno, pero no van más allá.

Y todavía se puede plantear una pregunta más, al respecto de cuándo crear excepciones propias y no utilizar las múltiples que ya proporciona Java. Como guía, se pueden plantear las siguientes cuestiones, y si la respuesta es afirmativa, lo más adecuado será implementar una clase Exception nueva y, en caso contrario, utilizar una del sistema.

¿Se necesita un tipo de excepción no representado en las que proporciona el entorno de desarrollo Java?
¿Ayudaría a los usuarios si pudiesen diferenciar las excepciones propias de las que lanzan las clases de otros desarrolladores?
¿Si se lanzan las excepciones propias, los usuarios tendrán acceso a esas excepciones?
¿El package propio debe ser independiente y auto-contenido?

CAPTURA DE EXEPCIONES

Las excepciones lanzadas por un método que pueda hacerlo deben recoger en bloque try/catch o try/finally.int valor;
try {
for( x=0,valor = 100; x < name="Cap9_3_1">try
Es el bloque de código donde se prevé que se genere una excepción. Es como si dijésemos "intenta estas sentencias y mira a ver si se produce una excepción". El bloque try tiene que ir seguido, al menos, por una cláusula catch o una cláusula finally.
La sintaxis general del bloque try consiste en la palabra clave try y una o más sentencias entre llaves.try {
// Sentencias Java
}
Puede haber más de una sentencia que genere excepciones, en cuyo caso habría que proporcionar un bloque try para cada una de ellas. Algunas sentencias, en especial aquellas que invocan a otros métodos, pueden lanzar, potencialmente, muchos tipos diferentes de excepciones, por lo que un bloque try consistente en una sola sentencia requeriría varios controladores de excepciones.
También se puede dar el caso contrario, en que todas las sentencias, o varias de ellas, que puedan lanzar excepciones se encuentren en un único bloque try, con lo que habría que asociar múltiples controladores a ese bloque. Aquí la experiencia del programador es la que cuenta y es el propio programador el que debe decidir qué opción tomar en cada caso.
Los controladores de excepciones deben colocarse inmediatamente después del bloque try. Si se produce una excepción dentro del bloque try, esa excepción será manejada por el controlador que esté asociado con el bloque try.

catch
Es el código que se ejecuta cuando se produce la excepción. Es como si dijésemos "controlo cualquier excepción que coincida con mi argumento". No hay código alguno entre un bloque try y un bloque catch, ni entre bloques catch. La sintaxis general de la sentencia catch en Java es la siguiente:catch( UnTipoTrhowable nombreVariable ) {
// sentencias Java
}

El argumento de la sentencia declara el tipo de excepción que el controlador, el bloque catch, va a manejar.
En este bloque tendremos que asegurarnos de colocar código que no genere excepciones. Se pueden colocar sentencias catch sucesivas, cada una controlando una excepción diferente. No debería intentarse capturar todas las excepciones con una sola cláusula, como esta: catch( Excepcion e ) { ...
Esto representaría un uso demasiado general, podrían llegar muchas más excepciones de las esperadas. En este caso es mejor dejar que la excepción se propague hacia arriba y dar un mensaje de error al usuario.
Se pueden controlar grupos de excepciones, es decir, que se pueden controlar, a través del argumento, excepciones semejantes. Por ejemplo:class Limites extends Exception {}
class demasiadoCalor extends Limites {}
class demasiadoFrio extends Limites {}
class demasiadoRapido extends Limites {}
class demasiadoCansado extends Limites {}
.
.
.
try {
if( temp > 40 )
throw( new demasiadoCalor() );
if( dormir < name="Cap9_3_3">finally
Es el bloque de código que se ejecuta siempre, haya o no excepción. Hay una cierta controversia entre su utilidad, pero, por ejemplo, podría servir para hacer un log o un seguimiento de lo que está pasando, porque como se ejecuta siempre puede dejar grabado si se producen excepciones y si el programa se ha recuperado de ellas o no.

Este bloque finally puede ser útil cuando no hay ninguna excepción. Es un trozo de código que se ejecuta independientemente de lo que se haga en el bloque try.

A la hora de tratar una excepción, se plantea el problema de qué acciones se van a tomar. En la mayoría de los casos, bastará con presentar una indicación de error al usuario y un mensaje avisándolo de que se ha producido un error y que decida si quiere o no continuar con la ejecución del programa.
Por ejemplo, se podría disponer de un diálogo como el que se presenta en el código siguiente:public class DialogoError extends Dialog {
DialogoError( Frame padre ) {
super( padre,true );
setLayout( new BorderLayout() );
// Presentamos un panel con continuar o salir
Panel p = new Panel();
p.add( new Button( "¿Continuar?" ) );
p.add( new Button( "Salir" ) );
add( "Center",new Label(
"Se ha producido un error. ¿Continuar?" ) )
add( "South",p );
}
public boolean action( Event evt,Object obj ) {
if( "Salir".equals( obj ) ) {
dispose();
System.exit( 1 );
}
return( false );
}
}
Y la invocación, desde algún lugar en que se suponga que se generarán errores, podría ser como sigue:try {
// Código peligroso
}
catch( AlgunaExcepcion e ) {
VentanaError = new DialogoError( this );
VentanaError.show();
}
Lo cierto es que hay autores que indican la inutilidad del bloque finally, mientras que desde el Java Tutorial de Sun se justifica plenamente su existencia. El lector deberá revisar todo el material que esté a su alcance y crearse su propia opinión al respecto.
En el programa java904.java, se intenta demostrar el poder del bloque finally. En él, un controlador de excepciones intenta terminar la ejecución del programa ejecutando una sentencia return. Antes de que la sentencia se ejecute, el control se pasa al bloque finally y se ejecutan todas las sentencias de este bloque. Luego el programa termina. Es decir, quedaría demostrado que el bloque finally no tiene la última palabra palabra.
El programa redefine el método getMessage() de la clase Throwable, porque este método devuelve null si no es adecuadamente redefinido por la nueva clase excepción.
throw
La sentencia throw se utiliza para lanzar explícitamente una excepción. En primer lugar se debe obtener un descriptor de un objeto Throwable, bien mediante un parámetro en una cláusula catch o, se puede crear utilizando el operador new. La forma general de la sentencia throw es:throw ObjetoThrowable;
El flujo de la ejecución se detiene inmediatamente después de la sentencia throw, y nunca se llega a la sentencia siguiente. Se inspecciona el bloque try que la engloba más cercano, para ver si tiene la cláusula catch cuyo tipo coincide con el del objeto o instancia Thorwable. Si se encuentra, el control se transfiere a ese sentencia. Si no, se inspecciona el siguiente bloque try que la engloba, y así sucesivamente, hasta que el gestor de excepciones más externo detiene el programa y saca por pantalla el trazado de lo que hay en la pila hasta que se alcanzó la sentencia throw. En el programa siguiente, java905.java, se demuestra como se hace el lanzamiento de una nueva instancia de una excepción, y también cómo dentro del gestor se vuelve a lanzar la misma excepción al gestor más externo.class java905 {
static void demoproc() {
try {
throw new NullPointerException( "demo" );
} catch( NullPointerException e ) {
System.out.println( "Capturada la excepcion en demoproc" );
throw e;
}
}
public static void main( String args[] ) {
try {
demoproc();
} catch( NullPointerException e ) {
System.out.println( "Capturada de nuevo: " + e );
}
}
}
Este ejemplo dispone de dos oportunidades para tratar el mismo error. Primero, main() establece un contexto de excepción y después se llama al método demoproc(), que establece otro contexto de gestión de excepciones y lanza inmediatamente una nueva instancia de la excepción. Esta excepción se captura en la línea siguiente. La salida que se obtiene tras la ejecución de esta aplicación es la que se reproduce:% java java905
Capturada la excepcion en demoproc
Capturada de nuevo: java.lang.NullPointerException: demo
throws
Si un método es capaz de provocar una excepción que no maneja él mismo, debería especificar este comportamiento, para que todos los métodos que lo llamen puedan colocar protecciones frente a esa excepción. La palabra clave throws se utiliza para identificar la lista posible de excepciones que un método puede lanzar. Para la mayoría de las subclase de la clase Exception, el compilador Java obliga a declarar qué tipos podrá lanzar un método. Si el tipo de excepción es Error o RuntimeException, o cualquiera de sus subclases, no se aplica esta regla, dado que no se espera que se produzcan como resultado del funcionamiento normal del programa. Si un método lanza explícitamente una instancia de Exception o de sus subclases, a excepción de la excepción de runtime, se debe declarar su tipo con la sentencia throws. La declaración del método sigue ahora la sintaxis siguiente:type NombreMetodo( argumentos ) throws excepciones { }
En el ejemplo siguiente, java906.java, el programa intenta lanzar una excepción sin tener código para capturarla, y tampoco utiliza throws para declarar que se lanza esta excepción. Por tanto, el código no será posible compilarlo.class java906 {
static void demoproc() {
System.out.println( "Capturada la excepcion en demoproc" );
throw new IllegalAccessException( "demo" );
}
}
public static void main( String args[] ) {
demoproc();
}
El error de compilación que se produce es lo suficientemente explícito:% javac java906.java
java906.java:30: Exception java.lang.IllegalAccessException must be caught, or
it must be declared in the throws clause of this method.
throw new IllegalAccessException( "demo" );
^
Para hacer que este código compile, se convierte en el ejemplo siguiente, java907.java, en donde se declara que el método puede lanzar una excepción de acceso ilegal, con lo que el problema asciende un nivel más en la jerarquía de llamadas. Ahora main() llama a demoproc(), que se ha declarado que lanza una IllegalAccessException, por lo tanto colocamos un bloque try que pueda capturar esa excepción.class java907 {
static void demoproc() throws IllegalAccessException {
System.out.println( "Dentro de demoproc" );
throw new IllegalAccessException( "demo" );
}
public static void main( String args[] ) {
try {
demoproc();
} catch( IllegalAccessException e ) {
System.out.println( "Capturada de nuevo: " + e );
}
}
}

La cláusula catch comprueba los argumentos en el mismo orden en que aparezcan en el programa. Si hay alguno que coincida, se ejecuta el bloque y sigue el flujo de control por el bloque finally (si lo hay) y concluye el control de la excepción.

Si ninguna de las cláusulas catch coincide con la excepción que se ha producido, entonces se ejecutará el código de la cláusula finally (en caso de que la haya). Lo que ocurre en este caso, es exactamente lo mismo que si la sentencia que lanza la excepción no se encontrase encerrada en el bloque try.

El flujo de control abandona este método y retorna prematuramente al método que lo llamó. Si la llamada estaba dentro del ámbito de una sentencia try, entonces se vuelve a intentar el control de la excepción, y así continuamente.

Cuando una excepción no es tratada en la rutina en donde se produce, lo que sucede es lo siguiente. El sistema Java busca un bloque try..catch más allá de la llamada, pero dentro del método que lo trajo aquí. Si la excepción se propaga de todas formas hasta lo alto de la pila de llamadas sin encontrar un controlador específico para la excepción, entonces la ejecución se detendrá dando un mensaje. Es decir, podemos suponer que Java nos está proporcionando un bloque catch por defecto, que imprime un mensaje de error, indica las últimas entradas en la pila de llamadas y sale.

No hay ninguna sobrecarga en el sistema por incorporar sentencias try al código. La sobrecarga se produce cuando se genera la excepción.

Se ha indicado ya que un método debe capturar las excepciones que genera, o en todo caso, declararlas como parte de su llamada, indicando a todo el mundo que es capaz de generar excepciones. Esto debe ser así para que cualquiera que escriba una llamada a ese método esté avisado de que le puede llegar una excepción, en lugar del valor de retorno normal. Esto permite al programador que llama a ese método, elegir entre controlar la excepción o propagarla hacia arriba en la pila de llamadas. La siguiente línea de código muestra la forma general en que un método declara excepciones que se pueden propagar fuera de él, tal como se ha visto a la hora de tratar la sentencia throws:tipo_de_retorno( parametros ) throws e1,e2,e3 { }
Los nombres e1,e2,... deben ser nombres de excepciones, es decir, cualquier tipo que sea asignable al tipo predefinido Throwable. Observar que, como en la llamada al método se especifica el tipo de retorno, se está especificando el tipo de excepción que puede generar (en lugar de un objeto Exception).

He aquí un ejemplo, tomado del sistema Java de entrada/salida:byte readByte() throws IOException;
short readShort() throws IOException;
char readChar() throws IOException;
void writeByte( int v ) throws IOException;
void writeShort( int v ) throws IOException;
void writeChar( int v ) throws IOException;

Lo más interesante aquí es que la rutina que lee un char, puede devolver un char; no el entero que se requiere en C. C necesita que se devuelva un int, para poder pasar cualquier valor a un char, y además un valor extra (-1) para indicar que se ha alcanzado el final del fichero. Algunas de las rutinas Java lanzan una excepción cuando se alcanza el fin del fichero.

Cuando se crea una nueva excepción, derivando de una clase Exception ya existente, se puede cambiar el mensaje que lleva asociado. La cadena de texto puede ser recuperada a través de un método. Normalmente, el texto del mensaje proporcionará información para resolver el problema o sugerirá una acción alternativa. Por ejemplo:class SinGasolina extends Exception {
SinGasolina( String s ) { // constructor
super( s );
}
....
// Cuando se use, aparecerá algo como esto
try {
if( j < 1 )
throw new SinGasolina( "Usando deposito de reserva" );
} catch( SinGasolina e ) {
System.out.println( o.getMessage() );
}
Esto, en tiempo de ejecución originaría la siguiente salida por pantalla:> Usando deposito de reserva
Otro método que es heredado de la superclase Throwable es printStackTrace(). Invocando a este método sobre una excepción se volcará a pantalla todas las llamadas hasta el momento en donde se generó la excepción (no donde se maneje la excepción). Por ejemplo:// Capturando una excepción en un método
class testcap {
static int slice0[] = { 0,1,2,3,4 };
public static void main( String a[] ) {
try {
uno();
} catch( Exception e ) {
System.out.println( "Captura de la excepcion en main()" );
e.printStackTrace();
}
}
static void uno() {
try {
slice0[-1] = 4;
} catch( NullPointerException e ) {
System.out.println( "Captura una excepcion diferente" );
}
}
}
Cuando se ejecute ese código, en pantalla observaremos la siguiente salida:> Captura de la excepcion en main()
> java.lang.ArrayIndexOutOfBoundsException: -1
at testcap.uno(test5p.java:19)
at testcap.main(test5p.java:9)
Con todo el manejo de excepciones podemos concluir que se proporciona un método más seguro para el control de errores, además de representar una excelente herramienta para organizar en sitios concretos todo el manejo de los errores y, además, que se pueden proporcionar mensajes de error más decentes al usuario indicando qué es lo que ha fallado y por qué, e incluso podemos, a veces, recuperar al programa automáticamente de los errores.

La degradación que se produce en la ejecución de programas con manejo de excepciones está ampliamente compensada por las ventajas que representa en cuanto a seguridad de funcionamiento de esos mismos programas.

No hay comentarios:

Publicar un comentario