cursojava

64
1. Orientación a Objetos en Java 1.1. Introducción a Java Java es un lenguaje de programación desarrollado en los años 90 por Sun Microsystems. Su principal característica es que se trata de un lenguaje multiplataforma, es decir, cualquier código java puede correr sobre cualquier plataforma sin necesidad de introducir cambios ni recompilar el código; dicho de otro modo; la portabilidad del código Java es máxima (“Write Once, Run Everywhere”). Al compilar código fuente java se obtiene código neutro (bytescode) que debe ser ejecutado por una máquina virtual denominada Java Virtual Machine (JVM). Es la JVM quien interpreta el código neutro y lo convierte a un código ejecutable por la plataforma particular sobre la que está corriendo el programa java. Así pues, toda la dependencia con la plataforma reside en la JVM; por lo que ésta es distinta para cada plataforma. Por otro lado la JVM antes de interpretar código neutro, somete a éste a un verificador el cual certifica que el código es correcto, es decir, todos los bytescode se ajustan a la especificación de la JVM, no se atenta contra la seguridad del sistema, no provoca desbordamiento de la pila, no se hacen conversiones de tipo no permitidas, etc: Todo esto da a la plataforma java gran robustez y seguridad. Todas estas verificaciones de código hacen que la ejecución se haga más lenta y pesada; pero es imprescindible para garantizar un nivel de seguridad óptimo; a demás, estas operaciones sólo se realizan en el momento que una clase es cargada en memoria, posteriormente, durante el uso de la clase, ya no son necesarias estas verificaciones de código: Como resumen, destacar que las dos características principales de java son la portabilidad y la seguridad. Cargador de clases Ejecución Verificador de código Generador de código Intérprete Hardware

Upload: jose-nose-mentiende

Post on 29-Dec-2015

12 views

Category:

Documents


2 download

TRANSCRIPT

Page 1: CursoJava

1. Orientación a Objetos en Java

1.1. Introducción a Java Java es un lenguaje de programación desarrollado en los años 90 por Sun Microsystems. Su principal característica es que se trata de un lenguaje multiplataforma, es decir, cualquier código java puede correr sobre cualquier plataforma sin necesidad de introducir cambios ni recompilar el código; dicho de otro modo; la portabilidad del código Java es máxima (“Write Once, Run Everywhere”). Al compilar código fuente java se obtiene código neutro (bytescode) que debe ser ejecutado por una máquina virtual denominada Java Virtual Machine (JVM). Es la JVM quien interpreta el código neutro y lo convierte a un código ejecutable por la plataforma particular sobre la que está corriendo el programa java. Así pues, toda la dependencia con la plataforma reside en la JVM; por lo que ésta es distinta para cada plataforma. Por otro lado la JVM antes de interpretar código neutro, somete a éste a un verificador el cual certifica que el código es correcto, es decir, todos los bytescode se ajustan a la especificación de la JVM, no se atenta contra la seguridad del sistema, no provoca desbordamiento de la pila, no se hacen conversiones de tipo no permitidas, etc: Todo esto da a la plataforma java gran robustez y seguridad. Todas estas verificaciones de código hacen que la ejecución se haga más lenta y pesada; pero es imprescindible para garantizar un nivel de seguridad óptimo; a demás, estas operaciones sólo se realizan en el momento que una clase es cargada en memoria, posteriormente, durante el uso de la clase, ya no son necesarias estas verificaciones de código: Como resumen, destacar que las dos características principales de java son la portabilidad y la seguridad.

Cargadorde clases

Ejecución

Verificador de código

Generador de código

Intérprete

Hardware

Page 2: CursoJava

En cuanto a los que se refiere a Orientación a objetos, Java es un lenguaje orientado a objetos puro, es decir, todo código java debe estar, necesariamente, encapsulado en una clase (la única excepción a esta regla son los tipo primitivos que no son objetos).

1.2. Clases y objetos en Java Una posible definición de clase puede ser: ‘Agrupación de datos (atributos) y métodos que operan sobre esos datos’. Es la unidad de programación en los lenguajes orientados a objetos: public class MiClase { ... declaración de atributos y métodos } Un objeto es una ocurrencia concreta de una clase. Se puede decir que una clase es un tipo de objeto. Para crear un objeto se debe usar el operador new seguido del nombre de la clase de la que se quiere crear un objeto: MiClase unObjeto = new MiClase();

Toda clase pública java debe escribirse en un fichero fuente java (archivo con extensión java) que se llame como la clase. Por ejemplo, la clase pública MiClase debe estar contenida en un fichero llamado MiClase.java:

package misprogramas.utiles; import java.util.*; import java.io.PrintWriter; public class MiClase {

... declaración de atributos y métodos }

1.3. Miembro de una clase Java Los miembros de una clase Java los constituyen sus atributos o campos (estado del objeto) y sus métodos (funciones para manipular/operar con el estado del objeto). 1.3.1. Variables miembros Las variables miembro de una clase Java son sus atributos o campos, es decir, los datos que contiene la clase. Los atributos de una clase pueden ser tipo primitivos (int, char, double...) o referencias a otros objetos. Dependiendo del ámbito de visión de la variable miembro, ésta se puede clasificar es:

- Variable miembro pública (public). Los atributos de este tipo pueden ser accedidos por cualquier otro objeto java. Por motivos de encapsulación, no se recomienda declarar variables miembro públicas, para evitar que otras clases accedan directamente a la estructura interna de la clase y con ello provocar una acoplamiento.

- Variable miembro protegida (protected). Los atributos de este tipo pueden ser accedidos por cualquier clase que extienda de ésta directa o indirectamente y por cualquier otra clase que pertenezca al mismo

Page 3: CursoJava

paquete de la clase. Este tipo de métodos son heredados mediante herencia.

- Variable miembro de ámbito paquete (por defecto). Los atributos de este tipo sólo pueden ser accedidos por las clases que pertenecen al mismo paquete

- Variable miembro privada (private). Los atributos de esta tipo sólo pueden ser accedidos por la propia clase y sus inner class. Es el ámbito de acceso más restrictivo y el más recomendable para conseguir un nivel del encapsulamiento adecuado.

Veamos un ejemplo de declaración de atributos con diferentes ámbitos de visión: public class ClaseConAtributos { public String cadena; protected int numero; double decimal; //ambito paquete private boolean logico; } Para acceder a los atributos de un objeto se debe usar el operador ‘.’: ClaseConAtributos obj = new ClaseConAtributos(); obj.cadena = “Java es un lenguaje de programación”; Java inicializa todos los atributos que son referencia a objetos a nulo y los atributos que son tipos primitivos los inicializa a 0, menos el tipo boolean que lo inicialza a false. Dependiendo de si el atributo es propio para cada objeto o si todos los objetos de la misma clase comparten el atributos, podemos clasificar las variables miembro en:

- Variable miembro de instancia. Cada objeto distinto tendrá su propia copia de este atributo. A no ser que se especifique lo contrario, todos los atributos de una clase son variables miembro de instancia.

- Variable miembro de clase. Todos los objetos de una clase comparten el atributo (atributo estático). Para especificar que un atributo estático se debe anteponer la palabra reservada static en la declaración del atributo:

public class ClaseConAtributoEstatico { public static String cadena; } Para acceder a un atributo estático de una clase no es necesario crear un objeto de dicha clase, ya que el atributo existe aunque no exista ningún objeto; así pues, se debe usar el nombre de la clase para acceder a sus atributos estáticos: String s = ClaseConAtributoEstatico.cadena;

Page 4: CursoJava

1.3.2. Métodos miembros Todo el código Java de una clase debe estar encapsulado en métodos. Sólo hay una excepción a esto: los bloques estáticos o iniciaizadores. Los bloques estáticos son bloques de código java que se ejecutan cuando la clase es cargada por la JVM y se utilizan, normalmente, para inicializar atributos estáticos: public class ClaseConBloqueEstatico {

public static String cadena; static { cadena=”Clase inicializada”; }

} Dependiendo del ámbito de visión del método, éste se puede clasificar es:

- Método público (public). Los métodos de este tipo pueden ser accedidos por cualquier otro objeto java. Constituyen la interfaz pública de la clase, es decir, la forma con la que el resto de objetos se comunicará con los objetos de esta clase.

- Método protegido (protected). Los métodos de este tipo pueden ser accedidos por cualquier clase que extienda de ésta directa o indirectamente y por cualquier otra clase que pertenezca al mismo paquete de la clase.

- Método de ámbito paquete (por defecto). Los métodos de este tipo sólo pueden ser accedidos por las clases que pertenecen al mismo paquete. Este tipo de métodos no son heredados mediante herencia.

- Método privado (private). Los métodos de este tipo sólo pueden ser accedidos por la propia clase y sus inner class. Es el ámbito de acceso más restrictivo y el más recomendable para conseguir un nivel del encapsulamiento adecuado.

Veamos un ejemplo de declaración de métodos con diferentes ámbitos de visión: public class ClaseConMetodos{ public String obtenerCadena(int numCaracteres) { ...

} protected int getNumero() { ...

}

//ambito paquete double convierteADecimal(String cadena, int numDecimales) {

... }

private boolean estaCorrecto() { ... }

Page 5: CursoJava

} Para acceder a los métodos de un objeto se debe usar el operador ‘.’: ClaseConMetodos obj = new ClaseConMetodos (); String cadena = obj.obtenerCadena(10); Dependiendo de si el método es propio para cada objeto o si todos los objetos de la misma clase comparten el método, podemos clasificar los métodos en:

- Método de instancia. Cada objeto distinto tendrá su propia copia de este método. Sólo este tipo de método puede acceder a las varibles miembro de instancia, es decir, a los atributos no estáticos. A no ser que se especifique lo contrario, todos los métodos de una clase son métodos miembro de instancia.

- Método de clase. Todos los objetos de una clase comparten el método (método estático). Este tipo de método sólo puede accder a los atributos estáticos. Para especificar que un método es estático se debe anteponer la palabra reservada static en la declaración del método:

public class ClaseConMetodoEstatico { public static String obtenerCadena(int numCaracteres) { ...

} } Para acceder a un método estático de una clase no es necesario crear un objeto de dicha clase, ya que el método existe aunque no exista ningún objeto; así pues, se debe usar el nombre de la clase para acceder a sus métodos estáticos: String s = ClaseConMetodoEstatico.ObtenerCadena(10);

1.3.2.1. Sobrecarga de métodos El nombre de un método más el tipo y orden de los parámetros que recibe, constituye lo que se denomina signatura del método. En Java, no pueden existir en la misma clase dos métodos con las misma signatura; pero si pueden existir dos métodos que se llamen igual pero varíen en el número, en el tipo y/o en el orden de los parámetros. Y a esto es lo que se denomina sobrecarga de métodos: métodos que se llaman igual; pero los parámetros que reciben varían en número, tipo y/o orden. Nótese que en la definición de sobrecarga no aparece el tipo de retorno del método. Vamos un ejemplo en el que aparecen sobrecargas válidas y no válidas.

public class ClaseConSobrecarga{ public String obtenerCadena(int numCaracteres) { ... }

public String obtenerCadena(int numCaracteres, String valorInicial) { ...

Page 6: CursoJava

}

public String obtenerCadena(String valorDefecto,int numCaracteres) { ... }

//sobrecarga no válida public StringBuffer obtenerCadena(String valorDefecto,int numCaracteres) { ... } La última sobrecarga no es válida porque ya existe un método con ese nombre y parámetros, aunque el tipo de retorno sea distinto.

1.3.2.2. Constructores

Al crear un objeto se invoca a un tipo especial de método llamado constructor. Los métodos constructores de una clase se deben llamar igual que la clase y no tienen tipo de retorno: public class MiClase { public MiClase() { System.out.println(“Método constructor por defecto”);

} } Toda clase debe poseer, al menos, un constructor. Si al codificar una clase no se

le añade un constructor; el compilador de java le añade un constructor sin parámetros (constructor por defecto).

Los constructores no pueden ser invocados directamente, son invocados por la JVM cuando se crea un objeto nuevo mediante el operador new:

MiClase obj = new MiClase(); Los constructores deben ser usados para inicializar correctamente el objeto y

deben recibir como parámetros toda la información necesaria para su correcta inicialización.

Los constructores también se puede sobercargar y utilizando la palabra reservada this() se puede invocar desde un constructor a otro y de esta manera reutilizar el código de inicialiación que hace el otro constructor:

public class DosConstructores { private int miDato; public DosConstructores(int miDato) {

this.miDato= miDato; } public DosConstructores() { this(1); //llamada a constructor sobrecargado } }

La sentencia this() debe ser la primera del constructor.

1.3.2.3.Finalizadores

Page 7: CursoJava

En Java no es necesario destruir explícitamente los objetos que ya no se usan. La JVM dispone de un componente llamado Recolector de Basura que se encarga de encontrar aquellos objetos no accesibles desde ninguna parte de la Aplicación y destruirlos para liberar la memoria que ocupan. El Recolector de Basura es invocado directamente por la JVM cuando ésta se queda sin memoria. Mediante el método estático gc() de la clase System se puede forzar la llamada al Recolector de Basura:

//Se llama al Recolector de Basura System.gc();

Antes de que el Recolector de Basura destruya un objeto, éste invoca a su método finalizador. El método finalizador está definido en la clase Object de la siguiente manera:

protected void finalize() throws Throwable;

Este método debe ser usado para liberar recursos que pudiera estar ocupando el objeto; como por ejemplo, ficheros, conexiones con la base de datos:

import java.io.FileReader; public class LectorFichero {

private FileReader fichero;

protected void finalize() throws Throwable { if(fichero!=null) { fichero.close(); } }

} El método finalize() no hace nada en la clase Object. Si se lanza algún error en el método finalize(), éste es ignorado y el objeto es destruido de todas formas. 1.3.2.4.Clases ejecutables: método main()

Para que una clase java sea ejecutable, debe contar con un método llamado main() que reciba un único parámetro de tipo array de String, que sea estático y que no devuelva nada:

public static void main(String [] args);

Por ejemplo, el famoso programa HolaMundo en java sería: public class HolaMundo {

public static void main(String [] args) { System.out.println(“Hola mundo”); }

} Para la ejecución de este programa habría que escribir el comando: Java HolaMundo

Page 8: CursoJava

Ante este comando, se arranca la JVM, la cual intenta localizar en su classpath una clase llamada HolaMundo; si la encuentra busca en la definición de dicha clase un método llamado main() que no devuelve nada, que sea estático y que sólo reciba un parámetro de tipo array de String, si lo encuentra ejecuta dicho método y sino lanza una excepción. El parámetro args contiene los parámetros con los que ha sido llamado el programa java; si éste no ha sido llamado con ninguno el parámetro args será un array de longitud 0. 1.3.3. Modificador final El modificador final puede afectar tanto a clases como a atributos y métodos:

- Una clase con el modificador final, significa que no puede ser heredada; es decir, no puede ser superclase de ninguna clase.

- Una atributo con el modificador final, significa que su valor no puede ser cambiado. Las variables que se definen en los métodos así como los parámetros de dichos métodos también se pueden declarar finales, y por lo tanto no pueden cambiar de valor. En Java los atributos declarados como estáticos y finales se consideran constantes.

- Un método con el modificador final, significa que no puede ser sobrescrito por ninguna subclase.

Veamos en un ejemplo el uso de este modificador: public final class ClaseFinal{ private final String atributoFinal; public final String metodoFinal(int numCaracteres) { ... }

public void metodo(final int numCaracteres) { }

public void metodo2() { final String cadena=”no se puede modificar”; ... }

1.4. Paquetes en Java 1.4.1. La palabra resevada package Los paquetes son grupos relacionados de clases e interfaces que proporcionan un

mecanismo conveniente para menejar un gran número de clases evitando los conflictos de nombres; además, un paquete puede contener otros paquetes (subpaquetes); con lo que se crea una estructura en árbol que es muy apropiada para organizar las clases de un aplicación java.

Mediante la palabra reservada package se especifica a que paquete pertenece la clase:

Page 9: CursoJava

package miaplicacion.accesobd; public class ConectorBD { … }

En este ejemplo, la clase ConectorBD pertenece al paquete accesobd que a su vez pertenece al paquete miaplicacion La definición de a qué paquete pertenece una clase debe ser la primera sentencia que se escriba en el fichero, antes de la definición de la clase; como se puede ver en el ejemplo. Si no se especifica, mediante la sentencia packege a qué paquete pertenece una clase, Java la incluirá en el paquete por defecto; por lo tanto toda clase o interfaz java siempre perteneces a una clase. La ruta de paquetes java debe corresponderse con una ruta de directorios en el disco, es decir la clase ConectorBD debe estar el el fichero:

<directori_base>/miaplicacion/accesobd/ConectorBD.java

Si esto no es así; Java dará un error de compilación.

1.4.2. Nombre cualificado de una clase Como se indicó anteriormente, los paquetes java son el mecanismo que se debe usar para organizar las clases de un proyecto y poder nombrarlas sin entrar en conflicto de nombres. Dicho de otro modo; dos clases que pertenezcan a paquetes distintos pueden llamarse igual; no hay conflito de nombres:

package miaplicacion.accesobd; public class ConectorBD { … } package utiles.bd; public class ConectorBD { … }

Esta definición de clases es correcta, aunque las dos clases se llamen igual; ya que pertenecen a paquetes distintos. Esto es así porque Java, para localizar una clase no lo hace por el nombre simple de ésta; sino por su nombre cualificado, es decir, el nombre de clase precedido por la ruta de paquetes en la que se encuentra. El nombre cualificado de una clase sí que debe ser único; ya que sino; ante dos clases con el mismo nombre cualificado, Java no sabría cual debe usar:

miaplicacion.accesobd.ConectorBD utiles.bd.ConectorBD

Como se puede ver, ambas clases del ejemplo tienen nombres cualificados distintos.

1.4.3. La palabra resevada import

Page 10: CursoJava

Cuando desde código Java se quiere hacer referencia a una clase, no se debe usar el nombre simple de ésta; ya que no es único y puede provocar ambigüedad, sino que hay que usar el nombre cualificado de la clase; que se tiene garantía que es único: package utiles; public class FechaActual {

public static void main(String [] args) { //Error de compilación, en Java existen dos clases que se

//llaman Date java no sabe cual debe usar Date fechaActual = new Date();

System.out.println(“Fecha/hora actual: ”+ fechaActual); } } package utiles; public class FechaActual {

public static void main(String [] args) { //Se ha eliminado la ambigüedad usando el nombre cualificado java.util.Date fechaActual = new java.util.Date();

System.out.println(“Fecha/hora actual: ”+ fechaActual); } }

Pero, como se ve en el ejemplo, trabajar con el nombre cualificado de las clases es demadiado engorroso. Para evitar esto, se debe usar la palabra reservada import, mediante la cual se especifica qué clases va a usar la clase que se está escribiendo; de tal manera que en el código no se hace necesario usar el nombre cualificado de la clase; sino que se puede usar el simple:

package utiles; import java.util.Date;

public class FechaActual { public static void main(String [] args) { //Se ha eliminado la ambigüedad usando import Date fechaActual = new Date();

System.out.println(“Fecha/hora actual: ”+ fechaActual); } }

Se pueden especificar tantas sentencias import como se quiera. Las sentencias import debe ir justamente despuedes de la sentencia package; antes que comience la definición de la clase o interfaz. El paquete java.lang (en el que se encuentran las clases base de java) se importa simpre por defecto; es decir, no es necesario especificar sentencias import para las clases de este paquete. Tampoco es necesario especificar sentencias import para clases que se encuentren en el mismo paquete que la clase que se está escribiendo. Se puede usar ‘*’ para indicar que se quiere importar todas las clases de un determinado paquete: import java.util.Date;

import java.util.Vector; import java.util.Iterator; Estas tres sentencias se pueden sustituir por: import java.util.*; Al utilizar ‘*’ se importa todas las clases del paquete pero no las clases de los

subpaquetes.

Page 11: CursoJava

1.4.4. Ámbito paquete La organización en paquetes de las clases permite la definición de un ámbito de acceso propio de java: ámbito paquete; que es el ámbito que se establece por defecto, es decir, no hay una palabra reservada en Java para denominar este ámbito de acceso. Una clase puede acceder a todo lo definido en otra clase del mismo paquete salvo lo que se haya declarado en ésta como privado. Esto es así, porque el grado de acoplamiento que puede existir entre clases del mismo paquete puede ser alto. Dicho de otro modo en un mismo paquete deben encontrarse clases muy ligadas entre sí y, que en general, se dediquen a una única función conjunta; por ejemplo, en el paquete miaplicacion.accesobd se deben encontrar todas las clases de mi aplicación destinadas al acceso a la base de datos. Vamos un ejemplo del ámbito paquete: package miaplicacion.emplados; public class Empleado { String nombre; ... } package miaplicacion.emplados; public class ListaEmpleado { private List empleados; public getNombre(int pos) { Empleado empleado = (Empleado) empleados.get(pos) return empleado.nombre; } } El atributo nombre de la clase Empleado está definido con ámbito paquete (es el ámbito por defecto, no tiene palabra reservada asociada); y por lo tanto puede ser accedido por cualquier otra clase de su mismo paquete.

1.5. La Herencia en Java La herencia en Java es simple, esto es, una clase sólo puede tener una superclase directa. Una clase hereda de su superclase todos sus métodos y atributos de ámbito public y protected. Los constructores no se heredan. La herencia en java se especifica mediante la palabra reservada extends: public MiClase extends SuperClase {...} Colocando la palabra reservada final sobre una clase impide que la clase pueda ser especializada; es decir, que pueda ser superclase de alguna clase:

public final class MiClase {…}

1.5.1. Sobrescritura de métodos Como se ha indicado antes, en Java una clase hereda de su superclase todos los atributos y métodos declarados con ámbito protected y public. Pues bien, una clase puede volver a definir el cuerpo de uno de los métodos que haya heredado y así de

Page 12: CursoJava

esta manera, cambiar el comportamiento de la clase con respecto a la clase padre. A esto es lo que se conoce como sobrescritura: public class EscritorMensaje { public void escritorMensaje(String msg) { System.out.println(msg); } } public class EscritorMensajeEntreComillas extends EscritorMensaje { public void escritorMensaje(String msg) {//sobrescritura System.out.println(“’”+msg+”’”); } } Para que la sobrescritura sea válida el método debe ser especificado en la subclase con la misma signatura y tipo de retorno que en la clase padre.

La palabra reservada final colocada sobre un método impide que la implementación de dicho método sea sobrescrita por alguna subclase:

protected final int unMetodo() {...}

1.5.2. Los constructores en la herencia Los métodos constructores no se heredan. Desde un constructor se puede especificar a qué constructor de la clase padre invocar mediante la palabra reservada super: public Class ClasePadre { public ClasePadre(String cadena) { ...

} } public Class ClaseHija extends ClasePadre { private int numero; public ClaseHija (int numero) {

super(“soy ClaseHija”); ...

} } Si no se especifica a que constructor de la clase padre se invoca, la JVM supone que se está invocando al constructor por defecto y si la clase padre no tiene constructor por defecto, se producirá un error de compilación. Esta constructor: public Class ClaseHija extends ClasePadre { public ClaseHija (int numero) { ...

} } Es equivalente a este otro: public Class ClaseHija extends ClasePadre { public ClaseHija (int numero) { super(); //Error de compilación ...

}

Page 13: CursoJava

} Aunque nosotros no metamos la sentencia super(), el compilador la introduce y se produce un error de compilación porque ClasePadre no tiene un constructor por defecto. La sentencia super() debe ser la primera del constructor y es incompatible con el uso

de la sentencia this(), es decir, o se invoca a un constructor sobrecargado o se invoca a un constructor de la clase padre.

1.6. La clase java.lang.Object

Toda clase java desciende directa o indirectamente de la clase Object, es decir, la clase Object se encuentra en el punto más alto de toda jerarquía de clases java. El hecho de que toda clase descienda de la clase Object ofrece dos ventajas muy interesantes: - Toda clase tiene una funcionalidad mínima que ha heredado de la clase

Object (toString(), hashCode()...) - Se pueden construir clases genéricas que operan con objetos de la clase

Object (por ejemplo la clase java.util.Vector)

1.7. Arrays en Java Un array es una colección de objetos o tipos primitivos. En un array sólo se pueden guardar objetos del tipo (o subtipo) del que se ha declarado el array: String [] arraysCadenas = new String[10]; De esta manera se está declarando un array de String con capacidad para 10 objetos de tipo String. Tras ejecutar esta línea de código la JVM reserva memoria para 10 referencias que estarán inicialmente apuntando a nulo. Para referirse a un elemento concreto del array se deben usar los los corchetes y el índice del elemento; por ejemplo, para darle valor al quinto elemento del array anterior: arraysCadenas[4] = “Una cadena”; Como se puede ver en el ejemplo; los índices válidos para el array arraysCadenas van desde 0 a 9. Java también permite que un array se inicialice dándole valor a cada uno de los elementos que van a estar contenidos en el array: String [] arraysCadenas = {“yo”, “tu”}; Este código es equivalente a: String [] arraysCadenas = new String[2];

ArraysCadenas[0] = “yo”; ArraysCadenas[0] = “tu”; Como se ha indicado antes, los arrays en java pueden ser de tipos primitivos; por

ejemplo: int [] arraysNumeros = new int[5]; char [] arraysCaracteres = {‘J’,’A’,’V’,’A’};

Page 14: CursoJava

Los arrays en Java son objetos, es decir, en Java todo array desdiente directa o indirectamente de Object; por lo tanto el siguiente código es correcto:

int [] arraysNumeros = new int[5]; String cadena = arrayNumeros.toString(); Todo array dispone de un atributo público llamado length que contiene la logitud del array. Para cada clase Java existe una clase de array asociada. Es decir, cuando se crea un clase Java, también se crea, implícitamente, una clase array del nuevo tipo creado. Esta clase array sigue la misma jerarquía de objetos que la clase que a la que está asociada. Por ejemplo, si se crea una clase A que extiende de una clase B, los arrays de objetos A, extienden de los arrays de objetos B, y a su vez, éstos extienden de arrays de Object y por último, éstos extienden de Object. Los arrays de tipo primitivos no extienden de array de Object; sino directemente de Object. Veamos todo esto con un ejemplo, dada las siguientes clases: public Empleado { ... } public class Jefe extends Empleado { ... } El siguiente código es correcto: Jefe [] lista = new Jefe[3]; //Se puede hacer casting implícito a array de Empleado Empleado [] lista2 = lista; //Se puede hacer casting implícito a array de Objet Object [] lista3 = lista; //Se puede hacer casting implícito a Object Object objeto = lista;

Por último indicar que mediante el método estático arraycopy() de la clase System, se puede copiar un array en otro. Por ejemplo, en el siguiente código se crea un array copia de otro; pero que tiene el doble de capacidad: String [] arrayCadenas = {“cadena1”,”cadena2”, “cadena3”}; String [] arrayCadenasCopia = new String [arrayCadenas.length*2]; System.arraycopy(arrayCadenas,0, arrayCadenasCopia,0,arrayCadenas.length); arraycopy() recibe los siguientes parámetros:

- Array fuente - Posición inicial del array fuente desde donde empezará la copia - Array destino - Posición inicial del array destino en donde se irán copiando los elementos - Número de elmentos que se van a copiar.

1.8. Clases Abstractas

Page 15: CursoJava

Una clase abstracta es una clase que no puede ser instancia, es decir, no pueden crearse directamente objetos de dicha clase. Las clases abstractas necesitan ser especializadas para que puedan ser usadas; es decir, se precisa crear una nueva clase no abstracta que extienda de la clase abstracta. De esta nueva clase sí se podrá crear objetos (indirectamente también se crea un objeto de la superclase abstracta) y por lo tanto usar la funcionalidad tanto de esta clase como de su superclase abstracta. Para declarar una clase como abstracta hay que anteponerle la palabra reservada abstract en la declaración de la clase: public abstract ClaseAbstracta { ... } Al ser la clase abstracta, la siguiente sentencia provocaría error de compilación: ClaseAbstracta obj = new ClaseAbstracta(); //error Las clases que incorporen algún método abstracto deben, necesariamente declararse como clases abstractas, sino se producirá un error de compilación. Pero no necesariamente todas las clases abstractas deben tener algún método abstracto, se pueden declarar abstractas clases completamente implementadas simplemente porque no tiene sentido que se puedan crear objetos de esa clase directamente. Si una clase extiende de una clase abstracta y no implementa sus métodos abstractos, dicha clase también habrá de ser declarada como abstracta o se producirá un error de compilación. 1.7.1. Métodos abstractos Un método abstracto es aquel para el que la clase sólo aporta su definición (signatura + tipo de retorno); pero no ofrece implementación para dicho método; sino que son las subclases las que están obligadas a darle cuerpo al método. Las clases que incorporen algún método abstracto deben declararse obligaroriamente como abstractas. Sólo los modificadores de acceso public y protected son válidos para métodos abstractos: public abstract class ClaseAbstracta{

public abstract String obtenerCadena(); }

public class ClaseConcreta extends ClaseAbstracta{

public String obtenerCadena() { return “Clase Concreta”;

} } 1.9. Interfaces

Una interfaz tiene un nivel de abstracción mayor que el de una clase abstracta; ya qué solo plantea una comportamiento (conjunto de métodos abstractos) que alguna clase implementará. En las interfaces sí se admite la herencia múltiple:

Page 16: CursoJava

public interface MiInterfaz extends SuperInterfaz1,… SuperInterfazN { … declaración de constantes … declaración de métodos } Ejemplo de interfaz: public interface Lista { //constante public static final int NUM_MAX_ELEMENTOS=100;

public void annadir(Object obj);

public Object obtener(int pos); public void borrar(int pos); public int getNumElementos();

} Como se ha indicado antes, alguna clase debe implementar los métodos definidos por el interfaz, ello se hace usando la palabra reservada implements: public class MiClase implements MiInterfaz {...} Una clase java puede extender directamente de una única clase; pero implementar múltiples interfaces. Así pues, las interfaces son una alternativa válida a la herencia múltiple: public class MiClase extends SuperClase implements Interfaz1,..., InterfazN { … } Al igual que ocurre con las clases abstractas, sino una clase implementa un interfaz determinado y no le da cuerpo a todos los métodos definidos en el interfaz, entonces la clase debe ser declarada como abstracta o se producirá un error de compilación.

1.10. ‘Perfiles’ de un objeto Java. Casting

Un objeto se instancia de una clase concreta; pero puede ser apuntado por referencias de distinto tipo (perfiles del objeto). Por ejemplo, dada la siguiente definición de clase: public class MiClase extends SuperClase implements MiInterfaz { ... } Son válidas las siguientes referencias que se crean apuntando al mismo objeto de la clase MiClase: MiClase ref1 = new MiClase(); SuperClase ref2 = ref1; MiInterfaz ref3 = ref2;

Page 17: CursoJava

Las tres referencias están apuntando al mismo objeto; pero las tres hacen que el objeto se vea con un perfil distinto. Con esto un objeto java puede ser apuntado por una referencia cuyo tipo sea:

- El de la propia clase del objeto o de alguna de sus superclases - El de algún interfaz implementado por la clase del objeto o por alguna de sus

superclases En el código de arriba se está produciendo un casting implícito llamado

upcasting. En java el casting implícito es válido cuando se hace hacia arriba en la jerarquía de clases y debe ser explícito cuando se hace hacia abajo en la jerarquía:

MiClase ref1 = new MiClase();

SuperClase ref2 = ref1; MiInterfaz ref3 = ref1;

MiClase ref4 = ref2; //error de compilación El código de arriba provoca un error de compilación porque se está haciendo un

casting implícito cuando se precisa un casting explícito porque el casting se está haciendo hacia abajo en la jerarquía de clases:

MiClase ref1 = new MiClase();

SuperClase ref2 = ref1; MiInterfaz ref3 = ref1;

MiCase ref4 = (MiClase) ref2; //código correcto Java cuenta con el operador intanceof para determinar, en tipo de ejecución, si

un determinado objeto puede o no verse con un determinado perfil. La sintaxis de este operador es la siguiente:

<referencia a un objeto> instanceof <nombre de clase o interface>

Por ejemplo: if(ref2 instanceof MiClase) { System.out.println(“Este if devuelve cierto”); } Resumiendo, el casting hacia arriba en la jerarquía de clases se denomina casting

implícito o upcasting y el casting hacia abajo en la jerarquía de clases se denomina casting explícito y este tipo de casting puede provocar un error de conversión recogido en una excepción de tipo ClassCastException:

MiClase ref1 = new MiClase();

SuperClase ref2 = ref1; //upcasting o cating implícito MiInterfaz ref3 = ref1; //upcasting o cating implícito

MiCase ref4 = (MiClase) ref2; //casting explícito

1.11. Palabras reservadas this y super La palabra reservada this es una referencia que apunta el objeto actual. Es la

manera de poder referirse al objeto actual desde el código de su clase. También se puede usar para acceder a métodos y atributos del objeto; aunque esto solo es necesario cuando hay ambigüedades:

Page 18: CursoJava

public class MiClase { int numero; public boolean iguales(int numero) { return this.numero==numero;

} } En este ejemplo el uso de la palabra reservada this es fundamental para eliminar

la ambigüedad, ya que hay dos variables que se llaman numero; una es el atributo interno de la clase y la otra es el parámetro de entrada que recibe el método.

La palabra reservada super se usa para acceder a métodos y atributos de

cualquiera de las superclases a los que la clase actual pueda acceder, es decir, aquellos cuyo ámbito de visión sea public o protected. Al igual que la palabra reservada this sirve para romper ambigüedades:

public class SuperClase { protected int numero;

public SuperClase(int numero) { this.numero=numero; } public int suma(int sumando) { return this.numero+sumando; } } public class ClaseHija extends SuperClase {

protected int numero;

public SuperClase(int numero1, int numero2) { super(numer1);

this.numero=numero2; }

public int suma(int sumando) { return super.suma(sumando)+this.numero; } public int getNumero1() { super.numero; } }

En el ejemplo anterior se ve el uso que se hace de la palabra reservada super para acceder a métodos y atributos heredados de la clase padre y romper ambigüedades. Las dos clases tiene definidos un atributo con el mismo nombre numero; en estos casos se dice que el atributo de la clase hija está enmascarando al atributo de la clase padre y es necesario el uso de la palabra reservada super para poder acceder al valor del atributo numero en la clase padre; si no hubiera enmascaramiento; no sería necesario el uso de super, el compilador sería capaz por sí solo de resolver a qué se refiere la variable numero. El compilador intenta resolver las referencias de la siguiente manera: dado la siguiente sentencia Java se busca la definición de la variable obj en el siguiente orden:

Page 19: CursoJava

String cadena = obj.toString(); 1.- Que exista un parámetro o variable local al método llamado obj 2.- Que exista un atributo en la clase llamado obj 3.- Que exista un atributo en alguna superclase llamado obj.

Con esto, se hace imprescindible el uso de las palabras reservadas this y super

para indicar de manera explícita en que lugar se debe buscar la definición de la variable: public class SuperClase { protected String cadena;

public SuperClase(String cadena) { this.cadena= cadena; } } public class ClaseHija extends SuperClase { protected String cadena;

public ClaseHija (String cadena1, String cadena2) { super(cadena1);

this.cadena= cadena2; } public String concatena(String cadena) { String res = cadena; //parámetro res+=this.cadena; //atributo cadena en ClaseHija

res+=super.cadena; //atributo cadena en SuperClase return res;

} } El otro uso importante de la palabra reservada super es para acceder a la

definición de métodos sobrescritos por las subclases, de esta manara, desde las subclases se puede reutilizar el código de los métodos sobrescritos y sólo modificar lo que corresponda:

public int suma(int sumando) {

return super.suma(sumando)+this.numero; }

1.12. Resolución de referencias en Java Cuando se invoca a un método de una clase, la JVM busca de definición del método (es decir, el código que debe ejecutar) partiendo de la clase en la que ha sido instanciado el objeto. Por ejemplo, dado el siguiente conjunto de clases:

public class SuperClase { protected String cadena;

public SuperClase(String cadena) { this.cadena= cadena;

Page 20: CursoJava

}

public char caracter() { return this.cadena.charAt(0); } } public class ClaseHija extends SuperClase {

public ClaseHija (String cadena) { super(cadena); } public char caracter() { return this.cadena.charAt(this.cadena.length()-1); } } y el siguiente trozo de código java: ClaseHija obj1 = new ClaseHija(“Cadena”); SuperClase obj2 = obj1; char c = obj2.caracter();

El resultado que se obtendrá será ‘a’ (último carácter de cadena), ya que la JVM buscará el código a ejecutar partiendo de la clase de la que ha sido instanciada obj2, es decir, buscará el código partiendo de la clase ClaseHija y por lo tanto el código que ejecutará será:

public char caracter() { return this.cadena.charAt[this.cadena.length()-1]; }

Cuando se invoca a un método sobrecargado, la JVM busca de definición del método (es decir, el código que debe ejecutar) partiendo del tipo de las referencias que se pasan como parámetro al método. Por ejemplo, dado el siguiente conjunto de clases:

public class SuperClase { } public class ClaseHija extends SuperClase { } public class ClaseOperador {

public String opera(SuperClase obj) { return “Metodo SuperClase”; } public String opera(ClaseHija obj) { return “Metodo ClaseHija”; }

} y el siguiente trozo de código java: ClaseHija obj1 = new ClaseHija(“Cadena”);

Page 21: CursoJava

SuperClase obj2 = obj1; ClaseOperador co = new ClaseOperador(); String res = co.opera(obj2); El resultado que se obtendrá será ‘Método SuperClase’, ya que la JVM busca el

método a ejecutar partiendo del tipo del que son las referencias que se pasan como parámetro en la llamada al método y la referencia obj2 es del tipo SuperClase; por lo tanto el código que se ejecutará será:

public String opera(SuperClase obj) { return “Metodo SuperClase”; }

1.13. Herramientas básicas en el JSDK El JSDK (Java Standard Developer Kit), es el conjunto de herramientas básicas necesarias para poder desarrollar en Java. Su funciones principales son:

- Compilación de clases Java - Ejecución de clases Java

Otras funciones son:

- Generación de documentacion (javadoc) - Crear ficheros jar - Ejecución de Applets (appletviewer) - ...

1.12.1. Compilación de una clase Java El comando javac compila código fuente Java y lo convierte en bytecodes que ya son ejecutables por la máquina virtual. La sintaxis con la que se usa el comando es la siguiente:

javac [opciones] fichero.java

donde fichero.java es la ruta relativa o absoluta al fichero fuente java. Es importante no omitir la extensión del fichero. El compilador almacena los bytecodes resultantes en un fichero llamado nombredeclase.class. El compilador sitúa este fichero en el mismo directorio en el que estaba el fichero fuente (a menos que se especifique la opción -d). Las opciones más usuales son:

- classpath path.Especifica el path que javac utilizará para buscar las clases de las que depende la clase que se quiere compilar (no es necesario indicar las clases básicas de java (todas aquellas que cuelgan del paquete java.) ya que la máquina virtual sabe localizarlas por si sola). Sobreescribe el path por defecto generado por la variable de entorno CLASSPATH. Los directorios o archivos jar, deben estar separados por puntos y comas. Por ejemplo:

javac -classpath .;C:\lib\utiles.jar;C:\tools\java\clases MiClase.java

- d directorio. Especifica el directorio raiz en el que se crearan los ficheros .class. Por ejemplo:

Page 22: CursoJava

javac -d c:\salida_java MiClase.java

Si la clase MiClase pertenece al paquete miaplicacion; hará que se cree en el directorio salida_java un subdirectorio llamado miaplicacion y dentro de él se creará el fichero MiClase.class.

Al especificar el nombre de la clase a compilar, se pueden especificar comodines (‘*’ y ‘?’), de tal manera que se compilarán todos los ficheros que cumplan la condición impuesta; y además esta compilación se hará en orden; es decir, si por ejemplo la clase A y B dependen de la clase C y ésta no depende de ninguna otra; primero se compilará la clase C y luego A y B. Por ejmplo: javac *.java Compila todas las clases del directorio. 1.12.2. Ejecución de una clase Java El comando java ejecuta en una máquina virtual los bytescodes que el compilador ha generado para para una clase java. La sintaxis con la que se usa el comando es la siguiente: java [opciones] NombreClase [argumentos] NombreClase debe ser el nombre cualificado de la clase que se quiere ejecutar; la cual debe contener un método main() estático; sino la JVM dará una excepción. Por ejemplo, supóngase que se quiere ejecutar la clase MiClase, la cual pertenece a un paquete llamado aplicacion; y supóngase también queel archivo compilado se encuentra, por ejemplo en c:\salida_java\aplicacion\MiClase.class, entonces para ejecutar la clase debemos situarnos en el directorio salida_java y escribir el siguiente comando: Java –cp . aplicacion.Miclase Mediante la opción –cp . se le dice a la JVM que busque las clases en el directorio actual; esto es necesario hacerlo si en la variable de entorno CLASSPATH no se ha añadido el directorio actual. A demás, es implescidible que no situemos en el directorio salida_java para la JVM encuentre la clase, ya que el nombre cualificado de la clase le indica la ruta de directorios que debe seguir para encontrar la clase a ejecutar. Los argumentos con los que se ejecute la clase serán accesibles desde el método main() a través del atributo String [] args. Las opciones más usuales son:

- cp path.Especifica el path que java utilizará para buscar las clases de las que depende la clase que se va a ejecutar (no es necesario indicar las clases básicas de java (todas aquellas que cuelgan del paquete java) ya que la máquina virtual sabe localizarlas por sí sola). Sobreescribe el path por defecto generado por la variable de entorno CLASSPATH. Los directorios o archivos jar, deben estar separados por puntos y comas. Por ejemplo:

Page 23: CursoJava

java -cp .;C:\lib\utiles.jar;C:\tools\java\clases MiClase

- DnombrePropiedad=valor. Define o redefine un valor de una propiedad de sistema. nombrePropiedad es el nombre de la propiedad cuyo valor se quiere establecer o cambiar y valor es el valor que se le va a dar a la propiedad. Las propiedades de Sistema son accesibles desde la clase java.lang.System. En la documentación en javadoc de esta clase aparecen algunas de las propiedades de Sistema que contiene. Por ejemplo, esta línea de comando:

java -Duser.lang=es ...

Cambia el valor de la propiedad user.lang a es; es decir, se cambia el lenguaje de la máquina virual a español. Para acceder a la propiedad, desde una clase java:

String valorPropiedad = System.getProperty("user.lang");

- jar NombreFicheroJar.jar. Ejecuta una aplicación java contenida en un fichero jar. Un fichero jar es un conjunto de clases java comprimidas en un fichero en formato zip que tiene extensión .jar y que aparte de contener clases java; puede contener otros tipos de recursos (imágenes, ficheros de texto...). Los ficheros jar suelen contener un fichero especial llamado manifest.mf que debe encontrarse en un directorio llamado META-INF. Este fichero contiene diversas propiedades de la clases contenidas del jar. Una de estas propiedades es Main-Class: nombre_cualificado_clase que contiene el nombre de la clase que arranca la aplicación contenida en el jar. Evidentemente dicha clase debe contener un método main() estático. Por ejemplo, supongamos una jar llamado MiAplicación.jar y la propiedad Main-Class es la siguiente:

Main-Class: miaplicacion.Arranque

Para ejecutar el jar escribiríamos el siguiente comando:

java –jar MiAplicacion.jar la JVM arrancaría la aplicación ejecutando el método main() de la clase

miaplicacion.Arranque.

1.14. Reglas de nombrado en Java Toda clase Java se recomienda que cumpla las reglas de nombrado que hay definidas. El código Java que sigue las reglas de nombrado es más fácil de entender por cualquier programador java que está acostumbrado a estas reglas de nombrado. Elemento Regla Ejemplo Paquete Todo minúscula package

miaplicacion.accesobd; Clase La primera letra de cada

palabra que forme parte del nombre de la clase en

public class GeneradorFicherosAyuda {

Page 24: CursoJava

mayúscula y el resto de la palabra en minúscula

Variables miembro, parámetros y variables

Igual que la regla para las clases, salvo que la primera palabra va entera en minúscula.

private int numUsuariosConectados;

Métodos miembro Igual que la regla de las variables

public int calculaMaximaRenta() {

Constantes Todo en mayúscula, separando cada palabra que forme parte del nombre de la constante con un subrayado

private int NUM_MAX_ELEMENTOS=100;

Page 25: CursoJava

2. Tratamiento de errores en Java: Excepciones Las excepciones son objetos que representan errores en el funcionamiento de la

aplicación. Cuando ocurre un error durante la ejecución de un determinado trozo código se crea un objeto de alguna clase que extienda de la clase java.lang.Exception y se lanza usando el operador throw.

2.1. Captura de excepciones

Cuando desde un método se hace una llamada a otro método que puede provocar excepciones, java obliga a que se capture la excepción o que el propio método también la lance. Para tratar excepciones se debe usar los bloques try-catch-finally. Un bloque try-catch-finally encierra un trozo de código que puede provocar excepciones. La estructura del bloque es la siguiente:

try { ...código que puede provocar excepciones }catch(ClaseExcepcion1 e1) { ...tratamiento de las excepciones de la clase ClaseExcepcion1 }catch(ClaseExcepcionN e1) { ...tratamiento de las excepciones de la clase ClaseExcepcionN }finally{ ...trozo de código que siempre se ejecuta, incluso cuando se produce algún error } Si dentro del bloque try-catch no hay ninguna sentencia que pueda provocar

alguna de las excepciones declaradas en la parte catch, java da un error de compilación. El bloque finally contiene un conjunto de sentencias que la JVM garantizan que

se ejecutan siempre; incluso si antes ha ocurrido un error o se ha ejecutado una sentencia return que termina la ejecución del método.

2.2. Declaración de excepciones

Como se ha dicho antes, un método debe tratar o declarar todas las excepciones que se pueden producir en el código del método. Si dentro del código de un método se puede provocar alguna excepción y el método no la captura; entonces, en la cabecera del método se debe declarar que el método lanza la excepción:

public int metodo() throws ClaseExcecion1, …, ClaseExcecionN {...} Si dentro del método se puede producir alguna excepción que ni es capturada y

está declarada en la cabecera del método como que se puede lanzar, entonces java da un error de compilación.

Así pues, un método java debe: •Declarar todas las excepciones que pueda lanzar y no va a tratar •Tratar todas las excepciones que pueda producir y que no son declaradas

2.3. Creación de nuevos tipos de errores Java aporta un gran número de clases de Excepciones diferentes, representando ,

cada una de ellas, un tipo de error determinado. Pero las aplicaciones pueden desarrollar ellas mismas sus propios niveles de errores creando objetos que extiendan de la clase java.lang.Exception:

public class ClaseConExcepciones {

Page 26: CursoJava

public int divide(int dividendo, int divisor) throws DivisionPorCeroException { if(divisor==0) { throw new DivisionPorCeroException();

}else { return dividendo/divisor; }

} public void imprimirDivision(ind dividendo, int divisor) { try { int div = divide(dividendo,divisor); System.out.println(“Resultado de la división: ”+div); }catch(DivisionPorCeroException dpce) { System.out.println(“Se ha intentado dividir por cero”); } }

}

El primer método divide() lanza un excepción; que al no tratarla debe indicar que la lanza en su cabecera. Por su parte el método imprimirDivision() posee un bloque try-catch para tratar las posibles excepciones que se lance con las llamadas al método divide(). La clase DivisionPorCeroException es una excepción personalizada y debe extender de la clase Exception:

public class DivisionPorCeroException extends Exception { } Las clases que se usan para definir niveles de errores personalizados, son un

poco especiales; porque extienden de una clase (en este caso Exception) a la que, en la mayoría de los casos, no aportan funcionalidad adicional. Esto es así porque lo más importante en las excepciones personalizadas es el nombre de ésta, que ya de por sí da mucha información del error ocurrido.

2.4. RuntimeException

Existe un tipo especial de excepciones que son aquellas que desciende de la clase RuntimeException (NumberFormatException, NullPointerException...) que no necesitan ser ni declaradas en los métodos ni capturadas, es decir, java no obliga ni a tratar ni a capturar este tipo de excepciones, como hace con el resto. Esto es así, porque estas excepciones pueden ocurrir en cualquier trozo de código java y por lo tanto sería muy engorroso (prácticamente imposible) escribir un método java.

2.5. java.lang.Error frente a java.lang.Exception

Java propone dos niveles de errores que pueden ocurrir dentro de la JVM:

- Errores que pueden ocurrir ejecutando código de la aplicación, que son representados mediante objetos que extienden de la clase Exception, por ejemplo, el intento de acceder a un método de una referencia que apunta a nulo provocaría el lanzamiento de un NullPointException

- Errores internos, no en la ejecución del código de la aplicación, sino dentro de la propia máquina virtual, que son representados mediante objetos que extienden de la clase Error; por ejemplo, si la máquina virtual se queda sin memoria, se lanza un OutOfMemoryError.

A su vez, ambas clases extienden de la clase java.lang.Throwable; sólo los objetos de esta clase pueden ser lanzados mediante el operador throw.

Page 27: CursoJava

2.6. Principales métodos de java.lang.Throwable Los principales métodos de Throwable y por tanto de Exception y Error son:

Método Descripción String getMessage() Devuelve el mensaje asociado al

error void printStackTrace() Lanza por la salida estándar la pila

de llamadas desde el método que provocó el error hasta el método inicial.

void printStackTrace(PrintWriter out)

Igual que el anterior, pero no por la salida estándar, sino por el PrintWriter que se le pasa como parámetro

Page 28: CursoJava

3. Entrada/Salida en Java La entrada/salida en java está basada en corrientes. Una corriente es un objeto por el que circula información. Las corrientes se pueden clasificar de varias maneras:

- Según la dirección: o Corrientes de entrada o Corrientes de salida

- Según el tipo de dato: o Corrientes de bytes o Corrientes de caracteres

- Según el origen de la información o Corrientes nodo o Corrientes filtro

Las clases de entrada/salida se encuentran en el paquete java.io

3.1.Clases base de la entrada/salida La entrada/salida en java se basa en cuatro clases abstracta, independientes de dispositivo sobre las que se basan el resto de clases: - InputStream. Es la superclase directa o indirecta de todas las corrientes de entrada de bytes. Métodos principales:

- int read(). Lee un byte - int read(byte []). Lee un array de bytes. Lee tantos bytes como

longitud tenga el array que se le pasa como parámetro - int read(byte[],int,int). Lee un array de bytes. Lee tantos bytes

como se indique en el tercer parámetro y se graban en el array empezando en la posición que marca el segundo parámetro.

- OutputStream. Es la superclase directa o indirecta de todas las corrientes de salida de bytes. Métodos principales:

- void write(int b). Escribe un byte - void write(byte []). Escribe un array de bytes. Escribe tantos bytes

como longitud tenga el array que se le pasa como parámetro - void write(byte[],int,int). Escribe un array de bytes. Escribe

tantos bytes como se indique en el tercer parámetro y se leen del array empezando en la posición que marca el segundo parámetro.

- Reader. Es la superclase directa o indirecta de todas las corrientes de entrada de caracteres. Métodos principales:

- int read(). Lee un carácter - int read(char []).Lee un array de carácter. Lee tantos caracteres

como longitud tenga el array que se le pasa como parámetro - int read(char[],int,int). Lee un array de carácter. Lee tantos

caracteres como se indique en el tercer parámetro y se graban en el array empezando en la posición que marca el segundo parámetro.

-

- Writer. Es la superclase directa o indirecta de todas las corrientes de salida de caracteres. Métodos principales:

- void write(int b). Escribe un carácter

Page 29: CursoJava

- void write(char []).Escribe un array de carácter. Escribe tantos caracteres como longitud tenga el array que se le pasa como parámetro

- void write(char[],int,int). Escribe un array de carácter. Escribe tantos caracteres como se indique en el tercer parámetro y se graban en el array empezando en la posición que marca el segundo parámetro.

- void write(String). Escribe una cadena. - void write(String, int,int). Escribe una subcadena de la cadena

que se le pasa como parámetro.

Todo error que se produzca en alguna operación de entrada/salida se representa mediante una excepción de la clase IOException.

3.2. Corrientes Nodo Son corrientes cuyo origen en un dispositivo físico (fichero, memoria...). Por ejemplo:

- FileInputStream. Corriente nodo cuyo origen en un fichero - SocketInputStream. Corriente nodo cuyo origen es un socket - StringReader. Corriente nodo cuyo origen es una cadena de texto - ByteArrayInputStream. Corriente nodo cuyo origen es un array en

memoria

3.3. Corrientes Filtro Son corrientes cuyo origen es otra corriente. Amplían la funcionalidad de la corriente origen. Normalmente no se suele trabajar con corrientes nodo directamente, sino que sobre ellas se “monta” una corriente filtro que nos facilita el trabajo con la corriente nodo.

A continuación se presentan algunas corrientes filtro: - BufferedInputStream

o Almacena datos que lee de un InputStream o Disminuye número de acceso al flujo origen

- BufferedReader o Equivalente al anterior pero para Reader o Permite leer de un Reader línea a línea

- BufferedOutputStream o Almacena datos que escribe en un OutputStream o Disminuye número de acceso al flujo origen

- BufferedWriter o Equivalente al anterior pero para Writer - PrintWriter

o Incorpora el método println(String) que escribe en el Writer sobre el que se apoya la cadena que recibe como parámetro y a continuación inserta un salto de línea.

3.4. Ejemplo 1: Escritura de la hora en un fichero

En este ejemplo se escribirá en un fichero la fecha y hora actual del sistema. Como se indicó anteriormente, se obtendrá un flujo nodo (en este caso un FileWriter, ya que vamos a escribir en un fichero) y sobre él se montará un flujo filtro para facilitar el trabajo (en este caso un PrintWriter):

Page 30: CursoJava

import java.io.FileWriter; import java.io.PrintWriter;

import java.io.IOException; import java.util.Date; public class EscribirFechaHora { public static void main(String args[]) throws IOException{ String nombreFichero = “C:\\FechaHora.txt”; FileWriter fWriter = new FileWriter(nombreFichero); PrintWriter pOut = new PrintWriter(fWriter); Date fechaActual = new Date(); pOut.println(fechaActual.toString());

//para vaciar el PrintWriter hacia su Writer origen pOut.flush();

pOut.close();// es importante cerrar los flujos } }

3.5. Entrada/salida estándar La entrada/salida estándar en java, se encuentran en la clase java.lang.System como dos atributos públicos y finales:

public final static PrintStream out; public final static InputStream in;

Por defecto, la entrada estándar es el teclado y la salida estándar el monitor.

3.6. Ejemplo 2: Presentación por pantalla de un fichero

En este ejemplo se presentará por pantalla el contenido de un fichero de texto cuyo nombre se recibirá por parámetro. Como se indicó anteriormente, se obtendrá un flujo nodo (en este caso un FileReader, ya que vamos a leer de un fichero) y sobre él se montará un flujo filtro para facilitar el trabajo (en este caso un BufferedReader ya que permite leer línea a línea. Del mismo modo, para escribir en la salida estándar montaremos sobre dicha salida estándar un PrintWriter, que permite escribir línea a línea):

package sevinge.cursos.io; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; public class LeerFichero { public static void main(String[] args) throws IOException { if (args.length == 0) { System.out.println("Falta el nombre del fichero a mostrar"); return; } //Abrimos el fichero FileReader fIn = new FileReader(args[0]); //Montamos sobre este Reader un BufferedReader //para poder leer línea a línea BufferedReader lector = new BufferedReader(fIn); //Montamos sobre la salida estádar un PrintWriter //para poder escribir sobre ella línea a línea PrintWriter escritor = new PrintWriter(System.out); //Leemos el fichero a través de lector //y lo escribimos a través de escritor String linea = lector.readLine();

Page 31: CursoJava

while(linea!=null) { escritor.println(linea); linea = lector.readLine(); } //para vaciar el PrintWriter hacia el flujo en el que está montado escritor.flush(); //cerramos los flujos lector.close(); escritor.close(); } }

Page 32: CursoJava

4. Serialización de Objetos Java

La serialización es un proceso que convierte un objeto java en una secuencia de bytes que son enviados a través de un flujo de salida (OutputStream) cuyo destino final puede ser un fichero, un socket, etcétera. La deserialización es el proceso contrario, es decir, la reconstrucción de un objeto java a partir de una secuencia de bytes que proceden de un flujo de entrada (InputStream) cuyo origen inicial puede ser un fichero, un socket, etcétera. La serialización es una forma sencilla de conseguir persistencia de objetos y es la base para la comunicación entre aplicaciones java (RMI, por ejemplo, se basa en la serialización) 4.1 Interfaz java.io.Serializable Para que una clase java sea serializable debe implementar el interfaz java.io.Serializable este interfaz no define ningún método que deba ser implementado; sino que es una simple marca para que la JVM sepa cuando un objeto es o no serializable. Si se intenta serializar un objeto que no lo es, se producirá una excepción del tipo java.io.NotSerializableException: import java.io.Serializable; public class ClaseSerializable implements Serializable { ... } Todos los tipos primitivos y las clases base sin serializables. 4.2 Serialización de un objeto: java.io.ObjectOutputStream La serialización de un objeto recae sobre la clase java.io.ObjectOutputStream que es un decorador de java.io.OutputStream añadiendo la funcionalidad de serializar objetos. Por ejemplo, supóngase que se quiere serializar un objeto de la clase ClaseSerializable guardándolo en un fichero: ClaseSerialible obj = new ClaseSerialible(); OutputStream out = new FileOutputStream(“c:\\ClaseSerializable.ser”); ObjectOutputStream objectOut = new ObjectOutputStream(out); ObjectOut.writeObject(obj);

ObjectOut.close();

Como se puede ver en el ejemplo, la creación de un objeto del tipo ObjectOutputStream precisa del OutputStream al que se envíe la secuencia de bytes en que se haya convertido el objeto serializado. La clase ObjetOutputStream cuenta con el método writeObject(), que serializa el objeto que se le pasa como parámetro. La serialización es recursiva, es decir al serializar un objeto se serializan todos sus atributos no transient; y al serializar cada uno de esos objetos, se serializan cada uno de sus atributos no transient; y así sucesivamente.

Page 33: CursoJava

4.3 Deserialización de un objeto: java.io.ObjectInputStream La deserialización de un objeto recae sobre la clase java.io.ObjectInputStream que es un decorador de java.io.InputStream añadiendo la funcionalidad de deserializar objetos. Por ejemplo, supóngase que se quiere deserializar un objeto de la clase ClaseSerializable a partir de la información contenida en un fichero: ClaseSerialible obj = new ClaseSerialible(); InputStream in = new FileInputStream(“c:\\ClaseSerializable.ser”); ObjectInputStream objectIn = new ObjectInputStream(in); ClaseSerialible obj=(ClaseSerialible) objectIn.readObject();

ObjectIn.close();

Como se puede ver en el ejemplo, la creación de un objeto del tipo ObjectInputStream precisa del InputStream del que se obtenga la secuencia de bytes a partir de la cual se reconstruirá el objeto. La clase ObjetInputStream cuenta con el método readObject(), que crea un objeto a partir de la secuencia de bytes que lea del InputStream que tiene asociado. Este método devuelve un Object, puesto que puede deserializar cualquier tipo de objeto serializable; con lo que se precisa un casting del objeto devuelto hacia la clase ClaseSerializable. La deserialización es recursiva, es decir al deserializar un objeto se deserializan todos sus atributos no transient; y al deserializar cada uno de esos objetos, se deserializan cada uno de sus atributos no transient; y así sucesivamente. Durante la deserialización de un objeto se comprueba que la versión de la clase del objeto serializado es compatible con la versión de la clase a la que actualmente puede acceder la JVM; si esto no es así se lanzará una excepción de tipo: java.io.InvalidClassException Por ejemplo, supóngase que se serializa un objeto de una clase que tiene un atributo de tipo String llamado nombre. Posteriormente se borra este atributo de la clase y dicha clase es recompilada. Si ahora se intenta deserializar el objeto, se produciría un error, ya que en el objeto serializado existe valor para un atributo llamado nombre, que no existe en la clase actual. Del mismo modo si se deserializa un objeto que tiene un atributo de una clase desconocida para la JVM, también se lanzará una excepción de tipo java.io.InvalidClassException. 4.4 Control de la serialización: atributos transient Puede ocurrir que en una clase serializable no se quiera que se serialicen determinados atributos por seguridad; porque, por ejemplo, contienen claves o códigos; o también puede ocurrir que otros atributos no se deban serializar porque su serialización no tiene sentido; por ejemplo, un atributo que sea una conexión a un base de datos. Y por último se tiene el caso de clases serializable que posee atributos internos de clases no serializable. Para evitar que estos atributos se serialicen, se debe colocar en su declaración la palabra clave transient: import java.io.Serializable; public class ClaseSerializable implements Serializable {

Page 34: CursoJava

private transient String atributoNoSerializable; ... } Cuando se deseriliza una objeto, sus atributos transient se inicializan a nulo, como es lógico (los tipos primitivos se inicilizan a 0, salvo boolean que se inicializa a false). 4.5 Control de la serialización: métodos writeObject() y readObject() Una clase serializable puede implementar los métodos writeObject() y readObject() con los que controlar el código que se ejecuta durante la serialización o deserialización del objeto y de esta manera, por ejemplo, poder inicializar correctamente los atributos transient:

private void writeObject(ObjectOutputStream stream) throws IOException; private void readObject(ObjectInputStream stream) throws IOException;

Mediante el primer método se puede especificar código que se ejecute cuando el objeto vaya a ser serializado y con el segundo se puede especificar código que se ejecute cuando se vaya a deserializar el objeto: import java.io.Serializable; import java.sql.Connection; public class ClaseSerializable implements Serializable { private String urlBaseDatos; private transient Connection conexion; ...

private void readObject(ObjectInputStream stream) throws IOException { stream.defaultReadObject();

this.conexion = ConnectionManager.getConnection(urlBaseDatos); }

} Realmente si una clase serializable implementa estos métodos, la JVM no usa el algoritmo estándar de serialización sobre los objetos de dicha clase; sino que delega dicho operación en estos métodos; por ello las clases ObjectInputStream y ObjetOutputStream cuentan con los métodos defaultReadObject() y defaultWriteObject() respectivamente, para que se ejecute el algoritmo por defecto sobre el objeto y usar estos métodos únicamente para especificar código adicional que se ejecute durante la serialización; como por ejemplo inicializar atributos transient. También se puede usar estos métodos para añadir información adicional en la serialización: import java.io.Serializable; import java.util.Date; public class ClaseSerializable implements Serializable { private String nombre;

public ClaseSerializable(Stgring nombre) { this.nombre=nombre; }

private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(new Date());

}

private void readObject(ObjectInputStream stream) throws IOException {

Page 35: CursoJava

stream.defaultReadObject(); Date fechaSerializacion = (Date) stream.readObject(); System.out.println(“El objeto fue serializado el: ”+fechaSerializacion);

} }

4.6 Externalizable Si una clase extiende de una clase no serializable; toda la información contenida en dicha clase no es serializada con el algoritmo por defecto; es decir, si se tiene la siguiente jerarquía de clases: public static class Clase1 { protected String s1; public Clase1() { this.s1=""; } public Clase1(String s) { this.s1 = s; } } public static class Clase2 extends Clase1 implements Serializable { private String s2; public Clase2(String s1, String s2) { super(s1); this.s2 = s2; } public String toString() { return this.s1+"-"+this.s2; } }

Al serializar un objeto de la clase Clase2, no se serializará el atributo heredado de la clase padre s1. Para evitar esto, Java define el interfaz Externalizable que extiende de Serializable: public interface Externalizable {

public void writeExternal(ObjectOutput out) throws IOException; public void readExternal(ObjectInput in) throws IOException,

ClassNotFoundException; } ObjectOutput y ObjectInput son los interfaces base que definen los métodos para la escritura/lectura de objetos. Las clases ObjectOutputStream y ObjectInputStream implementan estos interfaces. Toda la responsabilidad de la serialización recae en estos dos métodos: writeExternal() es responsable de todo lo que se escribe y readExternal() debe ser capaz de leer todo lo que se ha escrito desde writeExternal(). Las clases que implementen este interfaz deben tener un constructor por defecto porque la JVM invoca a dicho constructor antes de invocar al método readExternal(): public static class Clase2 extends Clase1 implements Externalizable { private String s2; public Clase2() { super(null); } public Clase2(String s1, String s2) { super(s1); this.s2 = s2;

Page 36: CursoJava

} public String toString() { return this.s1+"-"+this.s2; } public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(this.s1); out.writeObject(this.s2); } public void readExternal(ObjectInput in) throws IOException,

ClassNotFoundException { this.s1 = (String) in.readObject(); this.s2 = (String) in.readObject(); } }

Page 37: CursoJava

5. Programación Multi-Hilo en Java Un hilo es un flujo de ejecución secuencial e independiente dentro de un programa que competirá por los recursos del Sistema con el resto de hilos de ejecución, sean o no del mismo programa. Partiendo del número de hilos en los que se ejecuta un programa, se pueden clasificar éstos en dos:

- Mono-hilos. Programas que se ejecutan en un único hilo. Es decir, todo el código del programa es ejecutado por un único hilo. Este tipo de programas eran desarrollados principalmente en Sistemas Operativos que no tenían soporte al multi-hilo (por ejemplo MS-DOS). En estos Sistemas Operativos, el aprovechamiento los recursos CPU eran mínimos: cuando un programa se ejecutaba adquiría plenamente la CPU; por lo que el tiempo que el programa no estuviera ejecutando código (por ejemplo en espera de datos de E/S) la CPU estaba ociosa.

- Multi-hilos. Programas que se ejecutan en varios hilos. Es decir, el código del programa es ejecutado en hilos de ejecución distintos que compiten entre sí por la CPU. Este tipo de programas son más modernos y se ejecutan sobre Sistemas Operativos con soporte al multi-hilo. En estos Sistemas Operativos, el control de ejecución no recae sobre el programa (o hilo) que en estos momentos se está ejecutando; si no que es el propio Sistema Operativo el encargado de distribuir el tiempo de CPU entre los distintos hilos que compiten por ella (cambios de contexto); con el que el aprobechamiento del tiempo de CPU es máximo.

5.1. Multi-hilo en Java: java.lang.Thread Java es un lenguaje de programación con soporte al multi-hilo; es decir, Java aporta los elementos necesarios para que un programa pueda ser desarrollado en multi-hilo. La base de dichos “elementos” es la clase java.lang.Thread.. Es la clase necesaria para crear un hilo de ejecución en Java. Para lanzar en Java un hilo de ejecución independiente, se debe crea un objeto de la clase Thread e invocar a su método start(), en ese momento el hilo es lanzado y empezará su ejecución independiente y compitiendo con los otros hilos por los recursos del Sistema, CPU incluida: Thread miHilo = new Thread();

miHilo.start();

Cuando se ejecuta un programa Java a través de la máquina virtual:

java MiPrograma

La máquina virtual busca en la clase especificada (MiPrograma) un método llamado main() que debe ser estático y recibir un único parámetro que debe ser del tipo String[]. Si lo encuentra, crea un hilo de ejecución (hilo principal del programa) en el que se ejecutará el código de dicho método main(). Cuando termina de ejecutarse dicho método, la máquina virtual también termina su ejecución y desaparece como proceso activo en el Sistema Operativo. Pero si dentro de la ejecución del método main() se crea algún hilo y se lanza; la máquina virtual no dejará de ejecutarse hasta que no terminen todos los hilos de ejecución que se hayan lanzado, aunque el hilo principal haya terminado (es decir, terminó de ejecutarse el método main()).

Page 38: CursoJava

5.2 Especificación del código de un hilo En el apartado anterior se ha visto como se debe crear y lanzar un hilo en Java; pero no se ha indicado de que forma se especifica el código que se quiere ejecutar en el hilo. Java propone dos formas: Threads por derivación Se debe crear una nueva clase que extienda de la clase Thread y sobrescriba el método run() (el método run() dentro de la clase Thread no hace nada). Dentro del método run() se especificará el código del hilo. Cuando se quiera lanzar el hilo se debe crear un objeto de dicha clase y ejecutar su método start(); en ese momento, la JVM crea un nuevo hilo que ejecutará el código contenido dentro del método run():

public class SimpleThread extends Thread { public SimpleThread(String str) { super(str); } public void run() { System.out.println(“Hola Mundo, soy: ”+getName()); } } public class SimpleThreadMain { public static void main(String [] args) { Thread miHilo = new SimpleThread(“Mi primer hilo”);

miHilo.start(); }

Threads por implementación Se debe crear una clase que implemente el interfaz java.lang.Runnable. Este interfaz únicamente define un único método llamado run(). Dentro de este método se debe especificar el código del hilo. Cuando se quiera lanzar el hilo, se debe crear un objeto de dicha clase, luego crear un objeto Thread al que se le asocia dicho objeto y por último se invoca al método start() del objeto Thread para que el hilo sea lanzado. En ese momento, la JVM crea un nuevo hilo que ejecutará el código contenido dentro del método run():

public class SimpleRunnable implements Runnable { private String nombre; public SimpleRunnable (String str) { this.nombre=str; } public void run() { System.out.println(“Hola Mundo, soy: ”+nombre); } } public class SimpleRunnableMain { public static void main(String [] args) { Runnable miHilo = new SimpleRunnable(“Mi primer hilo”); Thread hilo = new Thread(miHilo);

Page 39: CursoJava

hilo.start(); }

Java aporta la creación de hilos por implementación porque no soporta la herencia múltiple. Gracias al interfaz Runnable, se pueden crear hilos que extiendan de una clase distinta a la clase Thread. 5.3 Parada de un hilo Un hilo deja de ejecutarse cuando termina la ejecución del método run() que corresponda. En los ejemplos anteriores los hilos finalizarían tras mandar a la salida estándar el mensaje de saludo. En ocasiones se precisa que un hilo no termine, ya que se quiere esté permanentemente realizando una determinada operación (por ejemplo escuchar por un puerto a la espera de peticiones o envíos de información desde otros Sistemas), en esos casos la operación realizada por el hilo se suele incluir en un bucle de tal manera que el método run no termine nunca:

public class HiloNoTerminaRunnable implements Runnable { public void run() { while(true) { <realizar_operación_continuamente> } } }

En este tipo de Thread, el problema es la detención del hilo. La clase Thread, en versiones anteriores de Java, disponía del método stop() que detenía el hilo. El problema era que está detención brusca del hilo podría provocar inestabilidad en la aplicación e incluso en la JVM; por ello, en las versiones actuales de Java, este método ha sido deprecado y actualmente no realiza ninguna acción; permanece en la clase Thread para que los programas que usaban anteriormente este método sigan funcionando. Así pues, la detención del hilo debe ser responsabilidad del programador del Thread que debe proveer en el Thread los mecanismos necesarios para que éste se detenga de manera correcta. Una forma muy extendida consiste en colocar una guarda en el bucle, de tal manera que el valor de esta guarda pueda ser modificado desde fuera del Thread y de esta manera conseguir una detención correcta del hilo:

public class ControlParadaThread extends Thread { private boolena parado=false; public void run() { while(¡parado) { <realizar_operación_continuamente> } <codigo_liberar_recursos>

} public boolean isParado() { return this.parado; }

public void parar() { this.parado=true;

Page 40: CursoJava

}

public void arranzar() { this.parado=false; this.start();

} }

Invocando desde cualquier otra zona de la aplicación al método parar() podríamos conseguir que el Thread se detuviera correctamente: miHilo.parar(); Donde miHilo es un objeto de la clase ControlParadaThread que ha sido arrancado; es decir, que en algún momento se ha invocado a su método start() o al método arrancar().. Si quisiéramos volver a arrancar el hilo no bastaría con poner el atributo parado a false, ya que al poner el atributo a true, finalizó la ejecución del método run() y por lo tanto finalizó la ejecución del hilo; por ello, el método arrancar no sólo pone el atributo parado a false; si no que invoca al método start(); que es el que realmente provoca que el método run() se ejecute en un hilo independiente. 5.4 Estados de Thread Un Thread Java puede estar en uno de los siguientes estados: Estado Descripción Creado El objeto Thread ha sido creado pero aún

no ha sido lanzado Listo El objeto Thread ha sido lanzado y está

listo para ocupar la CPU que en estos momentos está ocupada por otro Thread.

Ejecutándose El Thread está en estos momentos ocupando la CPU.

Dormido El Thread está durante un tiempo sin competir por la CPU, hasta que ese tiempo no pase el Thread no volverá a ocupar la CPU aunque no haya ningún otro Thread disponible para ocuparla.

Bloqueado El Thread está bloqueado esperando por un recurso (fichero, otro objeto, etc); hasta que el Thread no adquiera dicho recurso no podrá volver a competir por la CPU (estado Listo)

Suspendido El Thread ha sido suspendido mediante la invocación del método suspend(). El Thread no volverá a estado Listo (es decir, a competir por la CPU) hasta que otro Thread invoque al método resume() del Thread suspendido.

Muerto El objeto Thread ha concluido de ejecutar el método run()

Page 41: CursoJava

5.5 Sincronización de Threads Como se ha dicho anteriormente, el un Sistema multi-hilo, los distintos Threads compiten por ocupar los recursos del Sistema. Ciertos recursos no pueden ser accedidos por más de un Thread a la vez; ya que esto no fuera así, provocaría que el recurso no funcionara correctamente (por ejemplo, una impresora). Así pues la sincronización de Threads garantiza el acceso único a recursos compartidos; es decir, que en cualquier momento, sólo un Thread esté accediendo a dicho recurso compartido. 5.5.1 Monitores Java: Sincronización implícita La JVM no se tiene que preocupar de garantizar que el acceso a determinados recursos del Sistema es único (impresoras, ficheros, etc), ya que de esto se encarga en Sistema Operativo. Pero dentro de un programa Java también hay recursos compartidos (objetos) cuya sincronización sí es responsabilidad de la JVM. Por ejemplo supóngase que en una aplicación únicamente existe una instancia de una clase llamada ConsultorBD que es la que ejecuta todas las consultas contra la base de datos. Evidentemente dos Threads no podrían acceder directamente a este objeto (recurso compartido) para ejecutar dos sentencias distintas ya que ConsultorBD mezclaría ambas peticiones provocando errores. Para evitar esto, y facilitar la sincronización de Threads, la JVM asocia a cada objeto un monitor. Cuando un Thread accede a un objeto compartido, bloquea su monitor para, de esta manera, indicar que es él el que posee dicho recurso. Si se produjera un cambio de contexto (es decir, la CPU pasa a estar ocupada por otro Thread) y el nuevo Thread intenta acceder al recurso compartido; detectaría que su monitor está bloqueado por otro Thread y por lo tanto este Thread pasaría a estado Bloqueado hasta que el otro libere el objeto compartido. El monitor de un objeto no es bloqueado siempre que un Thread accede al objeto al que pertenece; si no que en el código hay que indicar de manera explícita qué partes del objeto hay que sincronizar. Para ello se debe usar la palabra reservada synchronized, la cual puede colocarse en dos zonas distintas:

- En la declaración de un método. Cualquier acceso a ese método provocaría que se bloqueara el monitor del objeto, entonces ningún otro Thread podría acceder a cualquier otra parte sincronizada del objeto, hasta que no termine de ejecutarse el método sincronizado:

public synchronized String obtenerResultado() { ... }

- A nivel de trozo de código (sección crítica). Se puede definir un trozo de

código de la siguiente manera: ... syncronized(objeto) { ... sección crítica } ...

Cuando un Thread entra en una sección crítica, ningún otro Thread podrá ejecutar código sincronizado del objeto que se indica al comienzo del bloque sincronizado hasta que no termine de ejecurtarse dicha sección crítica.

Page 42: CursoJava

5.5.2 Forzar bloqueos de Threads: sincronización explícita Puede ser que una aplicación Java no precise que el acceso a un determinado recurso compartido (objeto) se haga de uno en uno; pero sí que no más de N Threads accedan directamente a dicho recurso; por ejemplo para controlar la carga que soporta parte de la aplicación. Por ejemplo, supóngase una aplicación de Gestión de Empleados de una Empresa que una de sus funcionalidades es presentar un informe resumen de las actividades realizadas por cada empleado en un determinado periodo de tiempo. La generación de dicho informe implica un consumo importante de recursos del sistema; de tal manera que no se aconseja que se lancen más de 5 informes de este tipo a la vez. Para conseguir esto se podría usar el bloqueo explícito de Threads: cuando se pide un informe de este tipo, se comprueba el número de informes que se están generando en este momento y si ese número es mayor o igual a 5; se bloquea el Thread que está haciendo la nueva petición. Para conseguir esto todo objeto Java cuenta con los siguientes métodos (son métodos de la clase Object):

- wait(). Fuerza el bloqueo de un Thread hasta que otro Thread le avise. Este método sólo puede ser invocado en bloques sincronizados, es decir, cuando el Thread posea el monitor del objeto. Evidentemente cuando el Thread se bloquea mediante el uso de wait() libera el monitor del objeto. El método wait() cuenta con una sobrecarga en la que se puede especificar un tiempo máximo que se quiera que el Thread esté bloqueado; si transcurrido dicho tiempo el Thread no es liberado; se desbloquea automáticamente.

- notify(). Despierta a un Thread que se bloqueó mediante la invocación del método wait() del mismo objeto. La JVM elige aleatoriamente uno de los Threads bloqueados por este objeto y lo despierta. Este método también debe ser ejecutado en bloques sincronizados. El método notifyAll(), libera a todos los Threads bloqueados por el objeto.

Como ejemplo del uso de los métodos wait() y notify() se presenta el código de una clase que garantiza que no más de N peticiones de informe son procesadas a la vez. public class GeneradorInformes { private int concurrenciaMaxima; private int numPeticionesEnCurso; public GeneradorInformes(int concurrenciaMaxima) { this.concurrenciaMaxima= concurrenciaMaxima;

this.numPeticionesEnCurso= numPeticionesEnCurso; } public synchronized void generaInforme() { if(numPeticionesEnCurso>= concurrenciaMaxima) { this.wait(); //concurrencia máxima alcanzada } //Siempre que se llegue a esta altura

//del código se tiene garantía que como mucho //hay concurrenciaMaxima –1 peticiones atendiéndose

this.numPeticionesEnCurso++; generaInforme_();

Page 43: CursoJava

//tras generar el informe, notificamos a algún Thread de los que //están bloqueados para que continúe su ejecución

this.numPeticionesEnCurso--; this.notify();

} private void generaInforme_() { ... } }

Como se puede observar en el código el recurso compartido del ejemplo es el objeto de la clase GeneradorInformes. Únicamente indicar que cuando un Thread es bloqueado mediante un método wait(); para que vuelva a despertarse se debe invocar al método notify() del mismo objeto; es decir, no basta que el monitor del objeto compartido quede libre. Del mismo modo si hay N Threads bloqueados por un objeto; será preciso invocar N veces al método notify() de dicho objeto para liberarlos a todos (a no se que se invoque al método notifyAll()). 5.6 Prioridad en los Thread Con el fin de conseguir una correcta ejecución de un programa se establecen prioridades en los threads, de forma que se produzca un reparto más eficiente de los recursos disponibles. Así, en un determinado momento, interesará que un determinado proceso acabe lo antes posible sus cálculos, de forma que habrá que otorgarle más recursos (más tiempo de CPU). Esto no significa que el resto de procesos no requieran tiempo de CPU, sino que necesitarán menos. La forma de llevar a cabo esto es gracias a las prioridades. Cuando se crea un nuevo thread, éste hereda la prioridad del thread desde el que ha sido inicializado. Las prioridades viene definidas por variables miembro de la clase Thread, que toman valores enteros que oscilan entre la máxima prioridad MAX_PRIORITY (normalmente tiene el valor 10) y la mínima prioridad MIN_PRIORITY (valor 1), siendo la prioridad por defecto NORM_PRIORITY (valor 5). Para modificar la prioridad de un thread se utiliza el método setPriority(). Se obtiene su valor con getPriority(). El algoritmo de distribución de recursos en Java escoge por norma general aquel thread que tiene una prioridad mayor, aunque no siempre ocurra así, para evitar que algunos procesos queden “dormidos” permanentemente. 5.7 Acceso al Thread actual La clase Thread cuenta con una serie de métodos estáticos cuyo efecto se verían sobre el Thread actual; es decir, el Thread sobre el que se está ejecutando el código: Método Descripción currentThread() Devuelve una referencia al Thread actual. sleep() Duerme al Thread actual durante el tiempo

que se indica. Durante dicho tiempo el Thread no compitirá por la CPU, incluso cuando ningún otro Thread está ocupando la CPU.

Page 44: CursoJava

yield() Hace que el Thread actual renuncie a la CPU y ésta pase a estar ocupada por otro Thread. El Thread actual no es bloqueado; simplemente pierde la CPU pero está listo para volver a ocuparla

Interrupted() Indica si el Thread actual ha sido interrumpido en algún momento. La invocación de este método limpia el estado de interrumpido del thread; por lo que si se vuelve a invocar este método antes de que el Thread sea interrumpido, devolverá false.

Los métodos sleep() y yield() son muy útiles para conseguir un mejor reparto de CPU entre los distintos hilos de nuestro programa. Por ejemplo, cuando un thread debe estar continuamente ejecutándose para realizar una determinada operación (como esperar peticiones en un puerto); puede ser que consuma demasiada CPU solamente esperando la llegada de una nueva petición. Se podrían usar los métodos anteriores para conseguir una reparto más equitativo:

public class ControlParadaThread extends Thread { private boolena parado=false; public void run() { while(¡parado) { if(recibidaPeticion()) {

procesarPeticion(); }else { yield(); }

} } } En este ejemplo, si no se ha recibido una nueva petición, se cede la CPU a otro hilo; de esta forma se consigue un reparto más equitativo del tiempo de la CPU.

Page 45: CursoJava

6. Programación Socket en Java Un socket es un punto final en un enlace de comunicación de dos vías entre dos programas que se ejecutan en la red. Las clases Socket son utilizadas para representar conexiones entre un programa cliente y otro programa servidor. El paquete java.net proporciona dos clases; Socket y ServerSocket, que implementan los lados del cliente y del servidor de una conexión, respectivamente. Una aplicación servidor normalmente escucha a un puerto específico esperando una petición de conexión de un cliente. Cuando llega una petición de conexión, el cliente y el servidor establecen una conexión dedicada sobre la que poder comunicarse. Durante el proceso de conexión, el cliente es asignado a un número de puerto, y ata un socket a ella. El cliente habla al servidor escribiendo sobre el socket y obtiene información del servidor cuando lee de él. Igualmente, el servidor obtiene un nuevo número de puerto local (necesita un nuevo puerto para poder recibir nuevas peticiones de conexión a través del puerto original.) El servidor también ata un socket a este puerto local y comunica con él mediante la lectura y escritura sobre él. El cliente y el servidor deben ponerse de acuerdo sobre el protocolo ,esto es, debe ponerse de acuerdo en el lenguaje para transferir la información de vuelta a través del socket. 6.1 Socket en Cliente Cuando una aplicación Java cliente precisa conectarse con otra aplicación servidor (que no necesariamente tiene porque ser java), la cual espera recibir peticiones a través de un puerto; debe establecer una conexión con la máquina-puerto en la que está escuchando la aplicación servidor creando para ello un objeto de la clase java.net.Socket: Socket cliente = new Socket(“url_maquina_cliente”,<num_puerto>); En el momento en que se crea el objeto Socket, Java intenta establecer una conexión con la dirección y puerto que se especifica; si no se consigue se lanza una de las siguientes excepciones:

- UnknownHostException. Host desconocido - IOException. Si hay cualquier otro problema al establecer la conexión;

como por ejemplo que no hay ninguna aplicación escuchando en el puerto especificado.

La clase Socket cuenta con varios constructores distintos, mediante los cuales se puede especificar la url de la máquina a la que nos queremos conectar de formas distintas:

- Usando un objeto String y un entero, que representan la url de la máquina (su dirección IP o su nombre) y el número de puerto.

- Usando un objeto java.net.InetAddres y un entero, que representan la ip de la máquina y el número de puerto.

- Usando dos objetoa java.net.SocketAddres que representan la dirección a la que nos queremos conectar y la dirección cliente. La clase SocketAddres es una clase abstracta que representa la dirección de una Socket de manera independiente del protocolo de comunicación que se use.

Si la creación del objeto Socket no ha producido ninguna excepción, significa que la aplicación servidor ha aceptado nuestra petición y que por lo tanto se ha establecido la

Page 46: CursoJava

comunicación entre ambas aplicaciones. A partir de este momento la aplicación cliente, usando el objeto Socket podrá mandar y recibir información de la aplicación servidor. Por último indicar que se pueden crear Socket usando el contructor por defecto; es decir, sin especificar a qué máquina-puerto se debe conectar el Socket y posteriormente usar el método connect() para realizar la conexión a una máquina-puerto. 6.2 Lectura/Escritura de Socket Cliente Tras la creación del objeto Socket, la conexión ha quedado establecida y, como se indicó anteriormente, la aplicación cliente puede mandar y recibir información de la aplicación servidor. Para ello debe usar los siguientes métodos de la clase Socket:

- getInputStream(). Devuelve un objeto java.io.InputStream del que se podrá recibir información de la aplicación servidor. Sobre este InputStream se puede montar cualquier otro flujo que facilite la lectura de los datos; por ejemplo, si la información que se envía está en formato carácter se puede montar un java.io.BufferedReader que permite la lectura línea a línea:

Socket cliente = new Socket(“url_maquina_cliente”,<num_puerto>); InputStream in = cliente.getInputStream(); BufferedReader inLineas = new BufferedReader(new InputStreamReader(in));

- getOutputStream(). Devuelve un objeto java.io.OutputStream con el que se podrá mandar información a la aplicación servidor. Sobre este OutputStream se puede montar cualquier otro flujo que facilite la escritura de los datos; por ejemplo, si la información que se envía está en formato carácter se puede montar un java.io.PrtinWriter que permite la escritura línea a línea:

Socket cliente = new Socket(“url_maquina_cliente”,<num_puerto>); OutputStream out = cliente.getOutputStream(); PrintWriter outLineas = new PrintWriter (new OutputStreamWriter(out));

La invocación de cualquiera de estos métodos probocará una excepción de tipo java.io.IOException si el estado del Socket no es correcto (por ejemplo que el Socket ya este cerrado o que aún no esté conectado). Mediante los métodos isClosed() y isConnected() se puede consultar si el Socket está cerrado o conectado. Cuando se realiza una operación de lectura del socket y la aplicación servidor aún no ha mandado nada o no ha mandado lo suficiente como para que concluya la operación de lectura, que se quiere realizar, se probocará un bloqueo en la aplicación cliente, hasta que la aplicación servidor mande la suficiente información como para que concluya la operación de lectura que se quiere realizar o la aplicación servidor se desconecte, por ejemplo, en este código:

Socket cliente = new Socket(“url_maquina_cliente”,<num_puerto>); InputStream in = cliente.getInputStream(); Reader reader = new InputStreamReader(in); BufferedReader inLineas = new BufferedReader(reader); String linea = inLineas.readLine();

Page 47: CursoJava

La ejecución de la línea de código resaltada en negrita se bloqueará hasta que la aplicación servidor escriba un salto de línea. Para evitar este bloqueo se puede utilizar el método setSoTimeout(int milisegundos) para establecer el tiempo máximo que se puede esperar en el método read() una petición cliente. Si este tiempo trascurre y no se recibe ninguna petición, el método read() termina lanzando una excepción del tipo java.net.SocketTimeoutException. 6.3 Socket en Servidor Cuando una aplicación Java quiere poder atender peticiones remotas que realicen otras aplicaciones (que no necesariamente debe ser Java) a través de un puerto, debe usar objeto java.net ServerSocket. Un objeto ServerSocket se ata a un puerto de la máquina a la espera de peticiones: SocketServer servidor = new SocketServer(<num_puerto>); Si no se puede atar el puerto al Socket, porque por ejemplo el puerto ya esté acupado se lanza una excepción del tipo java.io.IOException. Tras la creación del objeto ServerSocket, la aplicación ya está lista para recibir peticiones de los clientes: Socket comunicacionCliente = servidor.accept(); El hilo de ejecución de la aplicación cliente queda bloqueado hasta que se reciba una petición de un cliente. Antes de invocar al método accept() se puede utilizar el método setSoTimeout(int milisegundos) para establecer el tiempo máximo que se puede esperar en el método accept() una petición cliente. Si este tiempo trascurre y no se recibe ninguna petición, el método accept() termina lanzando una excepción del tipo java.net.SocketTimeoutException. El método accept() devuelve un objeto Socket que la aplicación servidor debe usar para mandar y recibir información del cliente. Evidentemente este Socket se ata a un puerto distinto para que la aplicación servidor pueda seguir atendiendo otras peticiones. Hasta que no se vuelva a invocar al método accept(), cualquier petición que se reciba se perderá. Para evitar esto, tras la recepción de una petición, se crea un hilo independiente que la atiende y el hilo principal se pone nuevamente a esperar nuevas peticiones:

SocketServer servidor = new SocketServer(<num_puerto>); While(!terminar) {

Socket s = servidor.accept(); ProcesadorPeticion procesador = new ProcesadorPeticion(s); Thread hilo = new Thread(procesador); Hilo.start();

} public class ProcesadorPeticion implemments Runnable { private Socket comunicacionCliente; public ProcesadorPeticion(Socket s) {

comunicacionCliente=s; } public void run() {

Page 48: CursoJava

<código_procesar_peticion> } }

6.4 Cierre de un Socket Hasta que no se invoque al método close() de la clase Socket o SocketServer, el puerto al que está atado el socket no es liberado y por lo tanto ninguna otra aplicación podrá usar dicho puerto. Por ello se recomienda que tras finalizar el uso de un socket (tanto cliente como servidor) éste sea cerrado: socket.close(); Cualquier intento de usar un socket cerrado provocará una excepción de tipo java.io.IOException. Como se indicó anteriormente, mediante el método isClosed() se puede consutar si el socket está o no cerrado.

Page 49: CursoJava

7. Inner Class Las inner class (o clases anidadas o internas) son clases que se definen dentro del cuerpo de otra clase: public class ClaseContenedora {

… public class ClaseContenida { } } Se definen cuatro tipos de inner class:

1. Estáticas. 2. Miembro 3. Locales 4. Anónimas

Hay que señalar que la JVM no sabe nada de inner classs, ya que el compilador convierte estas clases en “clases normales”, contenidas en ficheros *.class cuyo nombre es ClaseContenedora$ClaseInterna.class. 7.1 Inner class estáticas Las inner class estáticas se definen con el modificar static en la declaración de la clase anidada:

public class ClaseContenedora { …

public static class ClaseContenida { } } Para crea un objeto de una inner class no se precisa un objeto de la clase contenedora; sino que se pueden crear objetos de este tipo directamente. Estos objetos pueden acceder a toda la parte estática (tanto pública como privada) de la clase contenedora; pero no pueden acceder a la parte no estática; ya que no se crean asociados a una instancia de la clase contenedora:

package listaelementos; public class Lista { private static int NUM_MAX_EMELEMNTOS=100; private Object [] elementos;

public static class ElementoLista { private Object valor; public ElementoLista(Object valor) { this.valor=valor;

} public getValor() { return this.valor; } public void metodoConErrores() {

//acceso correcto int n = Lista.NUM_MAX_ELEMENTOS; //acceso incorrecto ‘elementos’ no es esstático

Page 50: CursoJava

Object primerElemento = Lista.elementos[0]; }

} public add(ElementoLista elemento) { ... } } Una inner class estática podrá ser usada desde fuera de la clase contenedora, si el modificador de acceso lo permite, por ejemplo:

package aplicacion; import listaelementos.Lista; import listaelementos.Lista.ElementoLista; public class GestionEmpleados { private Lista empleados;

public void addEmpleado(String nombre){ ElementoCola elemento = new ElementoCola(nombre); empleados.add(elemento); } } Como se puede ver, la peculiaridad para acceder a la clase anidada está en el import. Por último indicar que Java permite definir interfaces anidados estáticos 7.2 Inner class miembro Las inner class miembro (o simplemente inner class) se definen sin usar el modificar static en la declaración de la clase anidada:

public class ClaseContenedora { …

public class ClaseContenida { } } Toda inner class existe dentro de un objeto de la clase contenedora; es decir, no se pueden crear objetos de clases inner class miembro que no estén asociados a un objeto de la clase contenedora: ClaseContenedora objClaseContenedora = new ClaseContenedora(); ClaseContenida objClaseContenida = objClaseContenedora.new ClaseContenida();

Como se puede observar, la creación de un objeto de una clase anidada se debe hacer a través de un objeto de la clase contenedora; al que queda asociado. Este objeto anidado puede acceder a todos los miembros de la clase contenedora (tanto públicos como privados); del mismo modo la clase contenedora también puede acceder a todos los miembros de sus objetos anidados:

package listaelementos; public class Lista { private static int NUM_MAX_EMELEMNTOS=100; private Object [] elementos;

Page 51: CursoJava

public static class ElementoLista { private Object valor; public ElementoLista(Object valor) { this.valor=valor;

} public getValor() { return this.valor; } public void metodoSinErrores() {

//acceso correcto int n = Lista.NUM_MAX_ELEMENTOS; //acceso correcto Object primerElemen = Lista.this.elementos[0]; }

} public add(ElementoLista elemento) { ... } } La palabra reservada this en una inner class hace referencia a los miembros de la propia clase; para referirse a los miembros de la clase contenedora, a la palabra this se debe anteponer el nombre de la clase contenedora; como se ve en el ejemplo:

Object primerElemen = Lista.this.elementos[0];

Una clase interna miembro puede contener otra clase interna miembro, hasta el nivel que se desee. Por ejemplo, C es una clase interna de B, que a su vez es una clase interna de A:

A a = new A();//se crea un objeto de la clase A A.B b = a.new B();//se crea un objeto de B dentro del objeto a A.B.C c = b.new C();//se crea un objeto de C dentro del objeto b

Las inner class miembro no pueden tener miembros estáticos (ni atributos ni métodos). 7.3 Inner class locales Las inner class locales se declaran dentro de un bloque de código (conjunto de sentencias java contenidas entre llaves), por ejemplo un método:

public class ClaseContenedora { …

public void metodo() { ... class InnerClassLocal { ... } ... InnerClassLocal icl = new InnerClassLocal(); ... } }

Page 52: CursoJava

Características de las clases internas locales:

1. Como las variables locales, sólo son visibles y utilizables en el bloque de código en el que están definidas.

2. Tiene acceso a todas las variables miembro y métodos de la clase contenedora, si ha sido definida desde un método no éstático de la clase contenedora; en ese caso, la palabra this se puede utilizar de la misma forma que en las clases internas miembro. Es decir, en esos casos la clase interna local queda asociada a la instancia de la clase contenedora:

public class ClaseContenedora { private String atributo;

public void metodoNoEstatico() {

... class InnerClassLocal_1 { public void metodoInnerClass() { //acceso correcto String s = ClaseContenedora.this.atributo; } } ... }

public static void metodoEstatico() { ... class InnerClassLocal_1 { public void metodoInnerClass() { //acceso incorrecto String s = ClaseContenedora.this.atributo; } } ... } } 3. Pueden utilizar las variables locales y argumentos del método en el que han sido

definidas, pero sólo si son final (esto es así porque en realidad la clase interna local trabaja con sus copias de las variables locales y por eso se exige que sean final y no se puedan cambiar).

public class ClaseContenedora { private String atributo;

public void metodo (String param1, final String param2) {

final int vble=0; class InnerClassLocal { public void metodoInnerClass() { //acceso incorrecto String s1 = param1; //acceso correcto String s2 = param2; //acceso correcto int n = vble; } } ... } }

Page 53: CursoJava

4. No pueden ser declaradas public, protected, private, pues su visibilidad es siempre la de las variables locales.

5. No pueden ser estáticas y por lo tanto no pueden tener miembros estáticos 7.4 Inner class anónimas Las inner class anónimas son muy similares a las inner class locales, pero sin nombre. En las clases internas locales primero se define la clase y luego se crea uno o más objetos. En las clases internas anónimas se unen estos dos pasos: como la clase no tiene nombre sólo se puede crear un único objeto. En el siguiente ejemplo se presenta una clase anónima que sobrescribe el método toString() de la clase Object:

public class ClaseContenedora { …

public void metodo() { ... Object o = new Object() { public String toString() { return super.toString()+" Clase anonima"; } }; System.out.println("Inner class anonima: "+ o); } } Las clases anónimas requieren una extensión de la palabra clave new. Se definen en una expresión de Java que está incluida en una asignación o que se pasa como parámetro a método.Formas de definir una clase anónima:

1. La palabra new seguida del nombre de la clase de la que hereda (sin extends) y la definición de la clase anónima entre llaves {…}. El nombre de la super-clase puede ir seguido de argumentos para su constructor.

2. La palabra new seguida del nombre de la interface que implementa (sin implements) y ladefinición de la clase anónima entre llaves {…}. En este caso la clase anónima deriva de Object. El nombre de la interface va seguido por paréntesis vacíos, pues el constructor de Object no tiene argumentos.

Page 54: CursoJava

8. Reflexión en Java La reflexión Java es un mecanismo que nos permite obtener información de las clases en tiempo de ejecución; así como cargar y usar nuevas clases cuyos nombres no aparecen explícitamente en el código de nuestra aplicación. Mediante la reflexión se puede, por ejemplo, a partir de un objeto obtener que campos constructores y métodos posee, invocar dichos métodos, crear nuevas instancias de la clase del objeto.... 8.1 La base de la reflexión en Java: la clase java.lang.Class La base de la reflexión en Java la constituye la clase Class la cual contiene información de un clase (meta-clase). Para cada clase Java que la JVM carga se crea un objeto Class asociado a dicha clase que contiene la información de la clase que se acaba de cargar: métodos, campos, constructores... El objeto Class asociado a una clase se puede obtener de forma estática (en tiempo de compilación) de dos maneras distintas:

- Mediante la expresión <nombre_de_clase>.class se obtiene el objeto Class asociado a la clase:

Class classDeString = String.class

- La clase Object cuenta con el método getClass() que devuelve el objeto Class asociado a la clase de la que es instancia el objeto:

String cadena = new String(“una cadena”); Class classDeString = cadena.getString();

Ambas formas devuelve en mismo objeto, ya que todos los objetos de una misma clase llevan asociado el mismo objeto Class; incluso todos los objetos de una clase son creados a través de dicho objeto Class.

Realmente todo tipo Java lleva asociado un objeto Class, es decir no sólo las clases llevan asociado un tipo Class; también las interfaces, arrays; incluso los tipos primitivos:

Class classDeInt = int.class;

A través de la sentencia anterior se obtendría el objeto Class asociado al tipo primitivo int. La clase Class cuenta con el método isPrimitive() que devuelve verdadero si el objeto Class está asociado a un tipo primitivo. Del mismo modo cuenta con el método isArray() mediante el cual se puede identificar si el objeto Class está asociado a un array. 8.2 Obtención de información sobre clases en tiempo de ejecución: método

forName() Las dos formas anteriores que hemos visto de obtener un objeto Class son de forma estática (en tiempo de compilación), es decir tenemos que conocer de antemano la clase de la que queremos obtener el objeto Class.

Page 55: CursoJava

Pero mediante el método estático forName() de la clase Class se puede obtener el objeto Class de una clase que no tiene porque conocerse en tiempo de compilación. En esto reside la potencia de la reflexión java: en la carga y uso de clases que no tienen porque aparecer durante la compilación; sino que son incorporadas a la JVM dinámicamente durante la ejecución de la aplicación y que nos permitirán variar sensiblemente el comportamiento de ésta. La base de esto último es el polimorfismo. Las aplicaciones se desarrollan partiendo de tipos base (generalmente interfaces o clases abstractas); pero sin conocer realmente las clases exactas que se usarán; ya que estás sólo podrán ser conocidas en tiempo de ejecución. Estas clases finales extienden de los tipos básicos que sí son conocidos por el programa y gracias a esto, nuestra aplicación podrá usarlos correctamente en tiempo de ejecución. Los tipos base definen un interfaz (conjunto de métodos) público que sus subclases implementarán y dicho interfaz es el que usa nuestra aplicación para trabajar con estos elementos sin necesidad de conocerlos en tiempo de compilación. Posteriormente, cuando la aplicación se ejecute, dependiendo de qué tipo concreto se use, se obtendrá uno u otro resultado. Por ejemplo, una pequeña aplicación que pinta figuras cuyas clases deben implementar el interfaz Figura: public interface Figura { public void dibujar(); } Es decir, nuestra aplicación sabrá “manejar” aquellas figuras que implementen este interfaz y usarlas correctamente para que se pinten. A continuación se muestra el código fuente de dos implementación de este interfaz: las clases Triangulo y Cuadrado. package sevinge.cursos.reflexion; public class Triangulo implements Figura { public void dibujar() { System.out.println(" a "); System.out.println(" a a "); System.out.println(" a a "); System.out.println(" a a "); System.out.println("aaaaaaaaa"); } } package sevinge.cursos.reflexion; public class Cuadrado implements Figura { public void dibujar() { System.out.println("aaaaaaaaa"); System.out.println("a a"); System.out.println("a a"); System.out.println("a a"); System.out.println("aaaaaaaaa"); }

Page 56: CursoJava

} Gracias a la reflexión la aplicación PintarFigura no necesita conocer los tipos Cuadrado y Triangulo; ni ningún otro que implemente el interfaz Figura; sólo necesita saber que los elementos que maneja son del tipo Figura y los usará a partir del interfaz público que ofrece este tipo: package sevinge.cursos.reflexion; public class PintarFigura { public static void main(String[] args) throws ClassNotFoundException, ClassCastException, InstantiationException, IllegalAccessException { if (args.length == 0) { System.out.println("Falta el nombre de la clase a usar para pintar la figura"); return; } //Se obtiene un objeto Class de la clase que pinta figuras Class claseFigura = Class.forName(args[0]); //A partir del objeto Class se crea una instacia del objeto Figura a pintar Object objFigura = claseFigura.newInstance(); Figura figura = (Figura) objFigura; //Se pinta la figura figura.dibujar(); } }

El uso de la aplicación sería, por ejemplo: java –cp . sevinge.cursos.reflexion.PintarFiguras sevinge.cursos.reflexion.Triangulo

Y la salida obtenida: a a a a a a a aaaaaaaaa El programa espera recibir como parámetro el nombre de la clase Figura que se desea pintar. A partir de este nombre de clase se procede a una carga dinámica de dicha clase utilizando el método forName(): Class claseFigura = Class.forName(args[0]); A partir de este objeto Class se crea una nueva instancia de la clase usando el método newInstance(): Object objFigura = claseFigura.newInstance(); El método newInstance() devuelve un objeto de la clase Object, pero la aplicación PintarFigura sabe que debe ser de una clase que implemente el interfaz Figura; por lo que realiza un casting explícito:

Figura figura = (Figura) objFigura;

Page 57: CursoJava

Y aquí es donde está la clave; la aplicación PintarFigura no sabe de que tipo exacto es objFigura ni le interesa; pero si sabe que debe implementar el interfaz Figura, así que trabaja con el objeto usando los métodos que ofrece este interfaz pero despreocupándose del tipo real del objeto (abstracción). Una vez que se tiene un objeto de tipo Figura, la aplicación puede trabajar con él: figura.dibujar(); Como se puede observar, en el código de PintarFigura no aparece ninguna referencia a las clases Triangulo y Cuadrado, la aplicación PintarFigura está totalmente desacoplada de los tipos concretos de figuras que se definan, con lo que si se incorporarán nuevos tipos de figura, la aplicación sería capaz de trabajar con ellos sin necesidad de recompilarla. Por último se presenta en el siguiente cuadro los distintos tipos de errores que pueden ocurrir en la aplicación y que están declarados en la cláusula throws del método main() de PintarFigura: Tipo Descripcion Línea de código en

PintarFigura ClassNotFoundException Este error ocurre con la

invocación del método estático forName() de la clase Class si la clase que se quiere cargar no existe

Class claseFigura = Class.forName(args[0]);

InstantiationException Este error ocurre con la invocación del método newInstance() de la clase Class si la clase de la que se quiere crear una nueva instancia no cuenta con un constructor por defecto

Object objFigura = claseFigura.newInstance();

IllegalAccessException Este error ocurre con la invocación del método newInstance() de la clase Class si la clase de la que se quiere crear una nueva instancia no tiene declarado como público el constructor por defecto

Object objFigura = claseFigura.newInstance();

ClassCastException Este error ocurre cuando se intenta efectuar un casting explícito no válido

Figura figura = (Figura) objFigura;

Page 58: CursoJava

8.3 Obtención de información sobre clases en tiempo de ejecución: Constructores La clase Class permite obtener los constructores públicos de los que dispone la clase. Cada constructor es representado mediante un objeto de la clase java.lang.reflect.Constructor: Class clase = Class.forName(<nombre_de_clase>);

Constructor [] constructores = clase.getConstructors();

Mediante el método getConstructors() se obtiene una array con todos los constructores públicos que hay definidos en la clase. Si se devuelve un array vacío es que la clase no contiene ningún constructor público. De esta otra forma, se obtiene un constructor concreto: Class clase = Class.forName(<nombre_de_clase>); Class [] clasesParametros = new Class[2];

clasesParametros[0] = String.class; clasesParametros[1] = int.class; Constructor constructor =

clase.getConstructor(clasesParametros); Se obtendría un constructor público de la clase que recibe como parámetro un objeto String y un entero. Se lanzará una excepción del tipo NoSuchMethodException si no existe en la clase un constructor que reciba esos parámetros. Las principales funcionalidades que ofrece la clase Constructor son las siguientes: Método Descripción Object newInstance(Object[] initargs)

Crea un nuevo objeto de la clase a la que pertenece el constructor. El array de objetos que recibe como parámetro se corresponde con los parámetros que recibe el constructor. Para los tipos primitivos se debe usar la clase envoltura correspondiente.

Class[] getExceptionTypes() Devuelve una array con los tipos de los parámetros que recibe el constructor.

Class[] getExceptionTypes() Devuelve un array con los tipos deferentes de excepciones que puede lanzar el constructor.

Veamos un ejemplo en el que se creará un objeto de la clase Participante que cuenta con un único constructor que recibe el nombre y la posición del participante: package sevinge.cursos.reflexion; public class Participante { private String nombre;

Page 59: CursoJava

private int posicion; public Participante(String nombre, int posicion) { super(); this.nombre=nombre; this.posicion=posicion; } public String getNombre() { return this.nombre; } public int getPosicion() { return this.posicion; } public String toString() { return ""+this.posicion+" "+this.nombre; } } La clase CreadorParticipante crea un objeto Participante, pero usando la reflexión java: package sevinge.cursos.reflexion; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class CreadorParticipante { public static void main(String[] args) throws IllegalArgumentException,

InstantiationException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException, ClassNotFoundException {

//Se obtiene el objeto Class Class claseParticipante = Class.forName("sevinge.cursos.reflexion.Participante"); //Se crea el array con los objetos Class que //representan los parámetros del constructor //que se quiere invocar Class [] clasesParametros = new Class[2]; clasesParametros[0] = String.class; clasesParametros[1] = int.class; //Se obtiene el constructor buscado Constructor constructor = claseParticipante.getConstructor(clasesParametros); //Se crea una instancia de Participante usando este constructor //Primero se crea el array de object con el valor de los parámetros Object [] parametros = new Object [2]; parametros[0] = "Francisco"; parametros[1] = new Integer(1); //se usa la clase envontura //Se invoca al método newInstance para crear el objeto Participante participante = (Participante) constructor.newInstance(parametros); System.out.println("Participante: "+participante); } }

Page 60: CursoJava

8.4 Obtención de información sobre clases en tiempo de ejecución: Campos La clase Class permite obtener los campos públicos de los que dispone la clase. Cada campo es representado mediante un objeto de la clase java.lang.reflect.Field:

Class clase = Class.forName(<nombre_de_clase>); Field [] campos = clase.getFields();

Mediante el método getFields() se obtiene una array con todos los campos públicos que hay definidos en la clase. Si se devuelve un array vacío es que la clase no contiene ningún campo público. Se puede obtener un campo concreto de una clase a partir del nombre de dicho campo:

Class clase = Class.forName(<nombre_de_clase>); Field campo = clase.getField(<nombre_del_campo);

Se lanzará una excepción del tipo NoSuchFieldException si no existe en la clase un campo con ese nombre. Las principales funcionalidades que ofrece la clase Field son las siguientes: Método Descripción Object get(Object obj) Devuelve el valor de campo al que

representa el objeto Field para el objeto que recibe como parámetro. Evidentemente este objeto debe ser de la clase a la que pertenece el campo. Existen métodos equivalentes que devuelve tipos primitivos: getInt(), getBoolean()...

void set(Object obj, Object value)

Establece el valor de campo al que representa el objeto Field para el objeto que recibe como parámetro. Evidentemente este objeto debe ser de la clase a la que pertenece el campo. Existen métodos equivalentes que establecen tipos primitivos: setInt(), setBoolean()...

Class getType() Devuelve el objeto Class asociado al tipo del campo

Supongamos que los campos de la clase Participante son públicos, en el siguiente ejemplo, la clase EstablecerDatosParticipante cambia el valor de los atributos de un objeto Paticipante usando la reflexión Java: package sevinge.cursos.reflexion; import java.lang.reflect.Field; public class EstablecerDatosParticipante { public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException, SecurityException, NoSuchFieldException { //Se crea el objeto Participante Participante participante = new Participante("Francisco",1); System.out.println("Participante (antes de la reflexion): "+participante);

Page 61: CursoJava

//Se obtiene el objeto Class Class claseParticipante = participante.getClass(); //Se obtiene el campo nombre Field campoNombre = claseParticipante.getField("nombre"); //Se modifica el valor del campo nombre campoNombre.set(participante,"Carlos"); //Se obtiene el campo posicion Field campoPosicion = claseParticipante.getField("posicion"); //Se modifica el campo posicion campoPosicion.setInt(participante,2); System.out.println("Participante (despues de la reflexion): "+participante); } }

8.5 Obtención de información sobre clases en tiempo de ejecución: Métodos La clase Class permite obtener los métodos públicos de los que dispone la clase. Cada método es representado mediante un objeto de la clase java.lang.reflect.Method:

Class clase = Class.forName(<nombre_de_clase>); Method [] metodos = clase.getMethods();

Mediante el método getMethods() se obtiene una array con todos los métodos públicos que hay definidos en la clase. Si se devuelve un array vacío es que la clase no contiene ningún método público. Se puede obtener un método concreto a partir del nombre de éste y los tipos (objetos Class) que recibe como parámetros, que es lo que constituye la signatura del método: Class clase = Class.forName(<nombre_de_clase>); Class [] clasesParametros = new Class[2];

clasesParametros[0] = String.class; clasesParametros[1] = int.class; Method metodo =

clase.getMethod(<nombre_del_metodo>,clasesParametros); Se obtendría un método público de la clase llamado <nombre_del_metodo> que recibe como parámetro un objeto String y un entero. Se lanzará una excepción del tipo NoSuchMethodException si no existe en la clase un método con ese nombre que reciba esos parámetros. Si se quiere obtener un método que no recibe parámetros, se pasará al método getMethod() null como array con los tipos de los parámetros. Las principales funcionalidades que ofrece la clase Method son las siguientes: Método Descripción Object invoke(Object obj, Object[] args)

Invoca al método asociado al objeto Field para el objeto que recibe como parámetro. Evidentemente este objeto debe ser de la clase a la que pertenece el método. El array de objetos que recibe como parámetro se corresponde con los parámetros que recibe el método. Para los tipos primitivos se debe usar la clase envoltura

Page 62: CursoJava

correspondiente. Class[] getExceptionTypes() Devuelve una array con los tipos de los

parámetros que recibe el método. Class[] getExceptionTypes() Devuelve un array con los tipos deferentes

de excepciones que puede lanzar el método.

la clase MostrarDatosParticipante muestra los datos de un objeto Participante invocando a sus método públicos getNombre() y getPosicion() usando reflexión: package sevinge.cursos.reflexion; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class MostrarDatosParticipante { public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException { //Se crea el objeto Participante Participante participante = new Participante("Francisco",1); //Se obtiene el objeto Class Class claseParticipante = participante.getClass(); //Se obtiene el método getNombre Method metodoGetNombre = claseParticipante.getMethod("getNombre",null); //Se invoca al método String nombre = (String) metodoGetNombre.invoke(participante,null); //Se obtiene el método getPosicion Method metodoGetPosicion = claseParticipante.getMethod("getPosicion",null); //Se invoca al método Integer pos = (Integer) metodoGetPosicion.invoke(participante,null); System.out.println(""+pos+" "+nombre); } } 8.6 Obtención de información sobre clases en tiempo de ejecución: Obtención de

la parte no pública de una clase La clase Class dispone de los métodos:

1. Constructor[] getDeclaredConstructors() 2. Constructor getDeclaredConstructor(Class[] parameterTypes) 3. Field[] getDeclaredFields() 4. Field getDeclaredField(String name) 5. Method[] getDeclaredMethods() 6. Method getDeclaredMethod(String name,Class[] parameterTypes)

Para acceder tanto a la parte pública como privada de la clase (también se obtienen los miembros de ámbito paquete y protegido). Evidentemente la parte no pública de la clase no podrá ser usada, es decir, si por ejemplo se intenta usar el método invoke() de un método privado se lanzará una excepción de tipo IllegalAccessException.

Page 63: CursoJava

8.7 Otras características de la clase Class Mediante la clase Class se pude obtener también qué intefaces implementa la clase y cual es sus superclase:

1. Class getSuperclass(). Devuelve el objeto Class asociado a la Superclase de la clase. Si el objeto Class está asociado a un tipo primitivo, este método devuelve null. Si el objeto Class está asociado a una interface, también devuelve null; lo mismo que para el objeto Class asociado a la clase Object.

2. Class[] getInterfaces(). Devuelve una lista con todos los interfaces implementados por la clase. Si el objeto Class está asociado a un tipo primitivo devuelve un array vacío. Si el objeto Class está asociado a un interface, devuelve la lista de interfaces de los que extiende. Sólo se devuelve los interfaces que directamente implementa la clase; es decir, si la clase extiende de otra clase que implementa otros interfaces, esos interfaces no son devueltos por este método.

La siguiente clase de ejemplo, mostraría por pantalla la jerarquía completa de la clase que se especifique por parámetro: package sevinge.cursos.reflexion; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class JerarquiaClase { public static void main(String[] args) throws ClassNotFoundException { if (args.length == 0) { System.out.println("Falta el nombre de la clase de la que se debe mostrar la jerarquía"); return; } //Se obtiene un objeto Class de la clase que se ha recibido por parámetro Class clase = Class.forName(args[0]); //Se guarda en una lista la jerarquía de clases List listaJerarquiaClases = new ArrayList(); Class c = clase; while(c!=null) { listaJerarquiaClases.add(0,c); c = c.getSuperclass(); } //Se pintan las clases indentadas String indentancion =" "; Iterator i = listaJerarquiaClases.iterator(); while(i.hasNext()) { Class c2 = (Class) i.next(); System.out.println(indentancion+c2.getName()); //Se incrementa la indentacion indentancion+=indentancion; } } }

Con la llamada a la clase: java –cp . sevinge.cursos.reflexion.JerarquiaClase java.util.Vector Se obtendría la salida:

Page 64: CursoJava

java.lang.Object java.util.AbstractCollection java.util.AbstractList java.util.Vector También se pueden obtener los modificadores con los que ha sido declarada una clase, un constructor, un campo y un método invocando al método getModifiers() de la clase que corresponda (Class, Constructor, Field o Method). El método getModifers() devuelve un entero donde están codificados todos los modificadores de la declaración del elemento. Mediante la clase java.lang.reflect.Modifier se puede decoficar lo devuelto por el método getModifiers(). Esta clase cuenta con un conjunto de métodos estáticos, uno para cada modificador válido, que devuelve bolean: isFinal(), isPublic()... Por ejemplo, el siguiente trozo de código nos dice si el campo nombre de la clase Participante es o no final: campo = claseParticipante.getField("nombre"); int modificadoresCampo = campo.getModifiers(); boolean esFinal = Modifier.isFinal(modificadoresCampo); Por último un cuadro con otros métodos interesantes de la clase Class: Método Descripción boolean isArray() Devuelve cierto si el objeto Class está

asociado aún array boolean isInterface() Devuelve cierto si el objeto Class está

asociado aún interface boolean isPrimitive() Devuelve cierto si el objeto Class está

asociado aún tipo primitivo boolean isInstance(Object obj)

Devuelve cierto si el objeto que recibe como parámetro es instancia de la clase a la que está asociada el objeto Class. Este método es equivalente al operador instanceof.

String getName() Devuelve el nombre cualificado (incluido paquetes) de la clase