Manual JAVA

62
Manual de Java + J2EE Walter Montesano

description

JAVA

Transcript of Manual JAVA

Page 1: Manual JAVA

Manual de Java + J2EE

Walter Montesano

Page 2: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 2 de 62

Java : Introducción al lenguaje

Historia Java se remonta al año 1991, cuando a un team-lider de Sun le ofrecieron un mejor puesto en otra empresa. El jefe del team-lider le propuso entonces, como contra-oferta, ponerlo a cargo de un proyecto atractivo: un sistema que controle electrodomésticos ante su aparente perspectiva de continuo desarrollo. La reducida potencia de cálculo y memoria de los electrodomésticos llevó a desarrollar un lenguaje sencillo capaz de generar código de tamaño muy reducido. Debido a la existencia de distintos tipos de CPUs y a los continuos cambios, era importante conseguir una herramienta independiente del tipo de CPU utilizada. Desarrollaron un código “neutro” que no dependía del tipo de electrodoméstico, el cual se ejecutaba sobre una “máquina hipotética o virtual” denominada Java Virtual Machine (JVM). Era la JVM quien interpretaba el código neutro convirtiéndolo a código particular de la CPU utilizada. Esto permitía lo que luego se convertiría en el principal lema del lenguaje: “Write Once, Run Everywhere”. A pesar de los esfuerzos realizados por sus creadores, ninguna empresa de electrodomésticos se interesó por el nuevo lenguaje. Como lenguaje de programación para computadoras, Java se introdujo a finales de 1995. Las claves fueron la aparición (y aceptación) de HotJava y la incorporación de un intérprete Java en la versión 2.0 del programa Netscape Navigator, produciendo una verdadera revolución en Internet. El mundo Java Al programar en Java no se parte de cero. Cualquier aplicación que se desarrolle se basa en un gran número de clases preexistentes. Algunas de ellas pueden ser creadas por el propio usuario, otras pueden ser comerciales, pero siempre hay un número muy importante de clases que forman parte del propio lenguaje (el API o Application Programming Interface de Java). Java incorpora en el propio lenguaje muchos aspectos que en cualquier otro lenguaje son extensiones propiedad de empresas de software o fabricantes de computadoras (threads, ejecución remota, componentes, seguridad, acceso a bases de datos, etc.). Por eso muchos expertos opinan que Java es el lenguaje ideal para aprender a programar, porque incorpora todos estos conceptos de un modo estándar, mucho más sencillo y claro que con las citadas extensiones de otros lenguajes. Esto es consecuencia de haber sido diseñado más recientemente y por un único equipo. El principal objetivo del lenguaje Java es llegar a ser el “nexo universal” que conecte a los usuarios con la información, pudiendo ésta residir en la computadora local, en un servidor de Web, en una base de datos o en cualquier otro lugar. JDK (Java Development Kit) es el entorno de programación es suministrado por Sun de forma gratuita, pudiéndose encontrar en la dirección web: http://Java.sun.com/j2se/. Es de consenso que el entorno jdk no es el más adecuado para el desarrollo de aplicaciones Java, debido a funcionar única y exclusivamente mediante comandos de consola, ya que hoy en día la programación se suele ayudar de entornos visuales, como JBuilder, JCreator, Eclipse, BlueJ, etc. Sin embargo, puede ser un entorno bastante útil para aprender el lenguaje, ya que aunque los entornos visuales nos hagan mucho trabajo siempre es necesario ir al código para modificarlo y obtener el comportamiento deseado, lo cual quiere decir que necesitamos dominar el lenguaje y es más fácil llegar a este dominio escribiendo códigos completos en un entorno “hostil” que no nos ayuda, que simplemente remodelando códigos ya generados por entornos visuales.

Page 3: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 3 de 62

Finalmente, JRE (Java Runtime Environment) es un conjunto de programas y bibliotecas que permiten que un programa Java se ejecute en una computadora. O sea, es el paquete que instala la máquina virtual en una PC. El JDK y el JRE se sirven de variables de entorno y de parámetros en las llamadas a los ejecutables, entre las cuales las más importantes son:

• PATH: es la ruta (la ruta es un conjunto de directorios o "carpetas" separados por punto y coma) donde se encuentran los ejecutables.

• CLASSPATH: es la ruta donde se encuentran las clases necesarias para el proyecto. • JAVA_HOME: es la ruta a la raíz del JRE. Aunque JBoss busca en esta variable la raíz

del JDK. Características de Java La compañía Sun describe el lenguaje Java como “simple, orientado a objetos, distribuido, interpretado, robusto, seguro, de arquitectura neutra, portable, de altas prestaciones, multitarea y dinámico”. A continuación, se explicaran algunas de estas características: Simple: es un lenguaje sencillo de aprender. Su sintaxis es la de C++ “simplificada”. Los creadores de Java partieron de la sintaxis de C++ y trataron de eliminar de este todo lo que resultase complicado o fuente de errores en este lenguaje. Orientado a objetos: posiblemente sea el lenguaje más orientado a objetos de todos los existentes; en Java todo, a excepción de los tipos fundamentales de variables (int, char, long...), es un objeto. Distribuido: Java está muy orientado al trabajo en red, soportando protocolos como TCP/IP, UDP, HTTP y FTP. Por otro lado el uso de estos protocolos es bastante sencillo comparándolo con otros lenguajes que los soportan. Robusto: el compilador Java detecta muchos errores que otros compiladores solo detectarían en tiempo de ejecución o incluso nunca. (ej: if(a=b) then...) el compilador Java no nos dejaría compilar este código. Seguro: sobre todo en los Applet. Estos son programas diseñados para ser ejecutados en una página Web. Java garantiza que ningún Applet puede escribir o leer de nuestro disco o mandar información del usuario que accede a la página a través de la red (como, por ejemplo, la dirección de correo electrónico). En general no permite realizar cualquier acción que pudiera dañar la máquina o violar la intimidad del que visita la página Web. Portable: en Java no hay aspectos dependientes de la implementación de los distintos compiladores, todas las implementaciones de Java siguen los mismos estándares en cuanto a tamaño y almacenamiento de los datos. Esto no ocurre así en C++. Arquitectura neutral: el código generado por el compilador Java es independiente de la arquitectura: podría ejecutarse en un entorno UNIX, Mac o Windows. El motivo de esto es que, el que realmente ejecuta el código generado por el compilador no es el procesador de la computadora directamente, sino que este se ejecuta mediante una máquina virtual. Esto permite que los Applets de una Web pueda ejecutarlos cualquier máquina que se conecte a ella, independientemente de qué sistema operativo emplee (siempre y cuando la computadora en cuestión tenga instalada una máquina virtual de Java). Los programas desarrollados en Java presentan diversas ventajas frente a los desarrollados en otros lenguajes como C/C++. La ejecución de programas en Java tiene muchas posibilidades: ejecución como aplicación independiente (Stand-alone Application), ejecución como applet, ejecución como servlet, etc. Un applet es una aplicación especial que se ejecuta dentro de un navegador o browser (por ejemplo Netscape Navigator o Internet Explorer) al cargar una página HTML desde un servidor Web. El applet se descarga desde el servidor y no requiere instalación en la computadora donde se encuentra el navegador. Un servlet es una aplicación

Page 4: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 4 de 62

sin interfaz gráfica que se ejecuta en un servidor de Internet. La ejecución como aplicación independiente es análoga a los programas desarrollados con otros lenguajes. Recolector de basura: El recolector de basura es una característica que permite que los objetos inicializados, se destruyan automáticamente cuando ya no están referenciados. En Java no hay que preocuparse por quedarse sin memoria, porque no han sido destruidos los objetos que ya no están referenciados. Esto es un problema, por ejemplo en C++, que hay que destruir manualmente los objetos sin referencias. Java lo hace automáticamente.

Palabras reservadas Existe una serie de palabras reservadas las cuales tienen un significado especial para Java y por lo tanto no se pueden utilizar como nombres de variables. Dichas palabras son:

abstract boolean break byte case catch

char class const* continue default do

double else extends final finally float

for goto* if implements import instanceof

int interface long native new null

package private protected public return short

static super switch synchronized this throw

throws transient try void volatile while

(*) son palabras reservadas, pero no se utilizan en la actual implementación del lenguaje Java.

Variables Una variable es un nombre que contiene un valor que puede cambiar a lo largo del programa. De acuerdo con el tipo de información que contienen, en Java hay dos tipos principales de variables: 1. Variables de tipos primitivos. Están definidas mediante un valor único que puede ser entero, de punto flotante, carácter o booleano. Java permite distinta precisión y distintos rangos de valores para estos tipos de variables (char, byte, short, int, long, float, double, boolean). 2. Variables referencia. Las variables referencia son referencias o nombres de una información más compleja: arrays u objetos de una determinada clase. Desde el punto de vista del papel o misión en el programa, las variables pueden ser: 1. Variables miembro de una clase: Se definen en una clase, fuera de cualquier método. Pueden ser tipos primitivos o referencias. 2. Variables locales: Se definen dentro de un método o, más en general, dentro de cualquier bloque entre llaves {}. Se crean en el interior del bloque y se destruyen al finalizar dicho bloque. Pueden ser también tipos primitivos o referencias. A continuación se dará una tabla con los distintos tipos de variables y su rango:

TIPO TAMAÑO RANGO byte 1 byte Valores numéricos de –128 a 127

Page 5: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 5 de 62

short 2 bytes Valores numéricos de –32.768 a 32.767 int 4 bytes Valores numéricos de –2.147.483.648 a 2147483647

long 8 bytes Valores numéricos de -9.223.372.036.854.775.808 a 9.223.372.036.854.775.807

float 4 bytes Valores numéricos hasta 38 cifras double 8 bytes Valores numéricos hasta 308 cifras char 2 bytes Valores alfanuméricos boolean 1 byte Solo admite TRUE o FALSE

Como puede apreciar, no existe en Java un tipo de dato primitivo para cadenas de texto. En su lugar se utiliza una clase, String. Los objetos de esta clase se manipulan por métodos, por ejemplo, para comparar cadenas se utiliza equals o equalsIgnoreCase. if (txtLoc.equalsIgnoreCase(txtLocSel))

Por otro lado, la concatenación se realiza directamente con el operador +. cad = txtLoc + ", ARGENTINA";

Si se concatena una cadena con un número, éste se convertira a String previamente. cad = "GBH " + 5 // cad pasa a valer "GBH 5"

Veremos más sobre la clase String más adelante. Un aspecto a tener en cuenta es que en Java las variables locales no se inicializan a ningún valor, en tanto que las variables de instancia y las variables miembro de un arreglo, sí se inicializan (a 0 las de tipos enteros, a 0.0 las de coma flotante, a false las booleanas, null para las variabled de referencia, y a '\0' las que son de tipo char). Si tuvierámos un código como el siguiente {

int x;

x = x + 1;

...

}

Java no permitiría compilar el programa, pues la variable x nunca fue inicializada. Conversión entre tipos numéricos Las normas de conversión entre tipos numéricos son las habituales en un lenguaje de programación: si en una operación se involucran varios datos numéricos de distintos tipos todos ellos se convierten al tipo de dato que permite una mayor precisión y rango de representación numérica. double <- float <- long <- int <- short <- byte Es posible convertir un dato de jerarquía “superior” a uno con jerarquía “inferior”, arriesgándonos a perder información en el cambio. Este tipo de operación (almacenar el

Page 6: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 6 de 62

contenido de una variable de jerarquía superior en una de jerarquía inferior) se denomina cast. int i = 9,k;

float j = 47.9F;

k = (int)j; //empleo de un cast

Ámbito de las variables Éste en Java viene dado por los corchetes: {}; una vez definida una variable en un código dejará de existir cuando se acabe el bloque de código en el que se definió. Los bloques de código empiezan con “{“ y acaban en “}”, por lo que la variable dejará de existir cuando se cierre el corchete que esté justo antes que ella en el código. En Java no se permite la definición de una variable con el mismo nombre de otra variable de ámbito superior (como sí se permite en C): {

int x = 12;

{

int x = 96; /* ilegal en Java, no en C++ */

}

}

Operadores Los operadores básicos de Java son + , - , * , / para suma, resta, producto y división. El operador / representa la división de enteros si ambos operandos son enteros. Su módulo puede obtenerse mediante el operador %. Además existen los operadores decremento e incremento: -- y ++ respectivamente. La operación que realizan son incrementar y decrementar en una unidad a la variable a la que se aplican. Su acción es distinta según se apliquen antes (++a) o después (a++) de la variable. Además de los operadores aritméticos, tenemos los operadores lógicos: ! Not lógico == Test de igualdad != Test de desigualdad < Menor que > Mayor que <= Menor o igual que >= Mayor o igual que && And lógico & And lógico (evalúa todos los términos siempre) || Or lógico | Or lógico (evalúa todos los términos siempre) Respecto de la asignación, Java dispone, además de =, de otros operadores. Se trata de versiones abreviadas del operador (=) que realizan operaciones “acumulativas” sobre una variable. La tabla muestra estos operadores y su equivalencia con el uso del operador igual (=).

Page 7: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 7 de 62

Operador Utilización Expresión equivalente += op1 += op2 op1 = op1 + op2

-= op1 -= op2 op1 = op1 - op2

*= op1 *= op2 op1 = op1 * op2

/= op1 /= op2 op1 = op1 / op2

%= op1 %= op2 op1 = op1 % op2

Operador instanceof El operador instanceof permite saber si un objeto pertenece o no a una determinada clase. Es un operador binario cuya forma general es,

objectName instanceof ClassName

Y que devuelve true o false según el objeto pertenezca o no a la clase. O sea que, por polimorfismo, también devuelve true cuando el objeto es de una clase hija de ClassName, o bien cuando ClassName es una interfaz y el objeto la implementa.

Operador condicional (?) Este operador, tomado de C/C++, permite realizar bifurcaciones condicionales sencillas. Su forma general es la siguiente:

booleanExpression ? res1 : res2

Donde se evalúa booleanExpression y se devuelve res1 si el resultado es true, y res2 si el resultado es false. Es el único operador ternario (tres argumentos) de Java. Como todo operador que devuelve un valor puede ser utilizado en una expresión. Estructuras de decisión Las estructuras de decisión permiten ejecutar una de entre varias acciones, en función del valor de una expresión lógica o relacional. Se tratan de estructuras muy importantes, ya que son las encargadas de controlar el flujo de ejecución de un programa. Existen dos bifurcaciones diferentes: if y switch. If Existen tres formas de implementar esta estructura, y son: if, if else e if else if. A continuación se explicara cada una de estas implementaciones: · if: esta estructura permite ejecutar un conjunto de sentencias en función del valor que tenga la expresión de comparación (se ejecuta si la expresión de comparación tiene valor true). Tiene la forma siguiente: if (expresion) {

sentencias;

}

Page 8: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 8 de 62

Las llaves {} sirven para agrupar en un bloque las sentencias que se han de ejecutar, y no son necesarias si sólo hay una sentencia dentro del if. · if else: análoga a la anterior, de la cual es una ampliación. Las sentencias incluidas en el else se ejecutan en el caso de no cumplirse la expresión de comparación (false): if (expresion) {

sentencias1;

} else {

sentencias2;

}

· if else if: Permite introducir más de una expresión de comparación. Si la primera condición no se cumple, se compara la segunda y así sucesivamente. En el caso de que no se cumpla ninguna de las comparaciones se ejecutan las sentencias correspondientes al else. if (expresion1) {

sentencias1;

} else if (expresion2) {

sentencias2;

} else if (expresion3) {

sentencias3;

} else {

sentencias4;

}

Switch Se trata de una alternativa a la bifurcación if else if cuando se compara la misma expresión con distintos valores. Su forma general es la siguiente: switch (expresion) {

case valor1:

sentencias1;

break;

case valor2:

sentencias2;

break;

case valor3:

sentencias3;

break;

case valor4:

sentencias4;

break;

case valor5:

sentencias5;

break;

case valor6:

sentencias6;

break;

[default:

sentencias7;]

Page 9: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 9 de 62

}

Las características más relevantes de switch son las siguientes: 1. Cada sentencia case se corresponde con un único valor de expresión. No se pueden establecer rangos o condiciones sino que se debe comparar con valores concretos de tipo entero (byte, short, int) o con constantes de enumeración. 2. Los valores no comprendidos en ninguna sentencia case se pueden gestionar en default, que es opcional. 3. En ausencia de break, cuando se ejecuta una sentencia case se ejecutan también todas las case que van a continuación, hasta que se llega a un break o hasta que se termina el switch. Estructuras repetitivas Se utilizan para realizar un proceso repetidas veces. Se denomina también bucle, lazo o loop. El código incluido entre las llaves {} (opcionales si el proceso repetitivo consta de una sola línea), se ejecutará mientras se cumpla unas determinadas condiciones. Hay que prestar especial atención a los bucles infinitos, hecho que ocurre cuando la condición de finalizar el bucle (expresion) no se llega a cumplir nunca. Se trata de un fallo muy típico. Bucle while Las sentencias se ejecutan mientras expresión sea true. while (expresion) {

sentencias;

}

Bucle for La forma general del bucle for es la siguiente: for (inicializacion; expresion; avance) {

sentencias;

}

Que es equivalente a utilizar while en la siguiente forma: inicializacion;

while (expresion) {

sentencias;

avance;

}

La sentencia o sentencias de inicialización se ejecutan al comienzo del for, y la o las sentencias de avance se ejecutan después de cada ciclo. La expresión se evalúa al comienzo de cada iteración. El bucle termina cuando la expresión de comparación (condición de corte) toma el valor false. Cualquiera de las tres partes puede estar vacía. La inicialización y el incremento pueden tener varias expresiones separadas por comas. Bucle do while

Page 10: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 10 de 62

Es similar al bucle while pero con la particularidad de que el control está al final del bucle (lo que hace que el bucle se ejecute al menos una vez). Una vez ejecutados las sentencias, se evalúa la condición: si resulta true se vuelven a ejecutar las sentencias incluidas en el bucle, mientras que si la condición se evalúa a false finaliza el bucle. do {

statements

} while (booleanExpression);

Sentencias break y continue La sentencia break es válida tanto para en los bucles como en el switch. Hace que se salga inmediatamente del bucle o bloque que se está ejecutando, sin realizar la ejecución del resto de las sentencias. La sentencia continue se utiliza en los bucles (no en bifurcaciones). Finaliza la iteración “i” que en ese momento se está ejecutando (no ejecuta el resto de sentencias que hubiera hasta el final del bucle), vuelve al comienzo del bucle y comienza la siguiente iteración (i+1). Sentencia return Otra forma de salir de un bucle (y de un método) es utilizar la sentencia return. A diferencia de continue o break, la sentencia return sale también del método o función. En el caso de que la función devuelva alguna variable, este valor se deberá poner a continuación del return (return value;). Arrays En Java los arrays son un objeto. Como tales se crean mediante el comando new. La sintaxis en la definición de un array es la siguiente: Tipo_datos[] nombre_array = new Tipo_datos[tamano_array];

Tipo_datos es el tipo de los datos que se almacenarán en el array (int, char, String... o cualquier objeto). Tamano_array es tamaño que le queremos dar a este array. Veamos un ejemplo: int[] edades = new int[10];

En este ejemplo hemos definido un array llamado edades, en el que podremos almacenar 10 datos tipo entero. El primer elemento de un array se sitúa en la posición 0, exactamente igual que en C. Si quisiésemos realizar un bucle que recorriese los elementos de este array escribiríamos un código del tipo: for(int i= 0; i< 10; i++){

System.out.println(“Elemento “ + i + edades[i]);

}

Page 11: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 11 de 62

Veremos más sobre arreglos más adelante en este manual (ver Conjuntos de datos).

Programación orientada a objetos En los años 60 la programación se realizaba de un modo “clásico” (no orientado a objetos). Un programa era un código que se ejecutaba, los trozos de código que se podían emplear en varias ocasiones a lo largo del programa (reusar) se escribían en forma de procedimientos que se invocaban desde el programa, y esta era la única capacidad de reuso de código posible. Según los códigos se fueron haciendo más grandes y complejos este estilo de programación se hacía más inviable: es difícil programar algo de grandes dimensiones con este estilo de programación. La única posibilidad de repartir trozos de código relativamente independientes entre programadores son los procedimientos, y al final hay que juntar todos estos con el programa central que los llama, siendo frecuente encontrar problemas al unir estos trozos de código. En los años 70 se empezó a imponer con fuerza otro estilo de programación: POO, programación orientada o objetos (en la literatura suele aparecer como OOP, Object Oriented Programing). Aquí un programa no es un código que llama a procedimientos, aquí un programa es un montón de objetos, independientes entre si, que dialogan entre ellos pasándose mensajes para llegar a resolver el problema en cuestión. A un objeto no le importa en absoluto como está implementado otro objeto, que código tiene o deja de tener, que variables usa.... sólo le importa a qué mensajes es capaz de responder. Un mensaje es la invocación de un método de otro objeto. Un método es muy semejante a un procedimiento de la programación clásica: a un método se le pasan uno, varios o ningún dato y nos devuelve un dato a cambio o no devuelve nada. Si hay que repartir un programa de grandes dimensiones entre varios programadores a cada uno se le asignan unos cuantos objetos, y en lo único que tendrán que ponerse de acuerdo entre ellos es en los mensajes que se van a pasar; la forma en que un programador implemente sus objetos no influye en absoluto en lo que los demás programadores hagan. Esto es así gracias a que los objetos son independientes unos de otros (cuanta mayor sea la independencia entre ellos de mayor calidad serán). Si analizamos lo que hemos dicho hasta aquí de los objetos veremos que estos parecen tener dos partes bastante diferenciadas: la parte que gestiona los mensajes, que ha de ser conocida por los demás, y que no podremos cambiar en el futuro sin modificar los demás objetos (sí es posible añadir nuevos métodos para dar nuevas funciones al objetos sin modificar los métodos ya existentes). La otra parte es el mecanismo por el cual se generan las acciones requeridas por los mensajes el conjunto de variables que se emplean para lograr estas acciones. Esta segunda parte es, en principio, totalmente desconocida para los demás objetos (a veces no es así, pero es lo ideal en un buena OOP). Por ser desconocida para los demás objetos podemos en cualquier momento modificarla sin que a los demás les importe, y además cada programador tendrá total libertad para llevarla a cabo como él considere oportuno. La OOP permite abordar con más posibilidades de éxito y con un menor coste temporal grandes proyectos de software, simplificándole además la tarea al programador. Clases y herencia Una clase es la “plantilla” que usamos para crear los objetos. Todos los objetos pertenecen a una determinada clase. Un objeto que se crea a partir de una clase se dice que es una instancia de esa clase. Las distintas clases tienen distintas relaciones de herencia entre si: una clase puede derivarse de otra, en ese caso la clase derivada o clase hija hereda los métodos y

Page 12: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 12 de 62

variables de la clase de la que se deriva o clase padre. En Java todas las clases tienen como primer padre una misma clase: la clase Object. Definición de una clase La forma más general de definición de una clase en Java es: [modificador [,modificador*]] class NombreClase [extends

nombreClasePadre] [implements nombreInterface [,nombreInterface*]] {

DeclaraciónDeVariables;

DeclaraciónDeMétodos;

}

Los campos que van entre corchetes son optativos. NombreClase es el nombre que le queramos dar a nuestra clase, nombreClasePadre es el nombre de la clase padre, de la cual hereda los métodos y variables. Finalmente, tras la palabra implements se ponen todas las interfaces (separadas por coma) que implementa la clase. Sólo podemos tener una clase public por unidad de compilación, aunque es posible no tener ninguna. Creación y referencia a objetos Un objeto en el ordenador es esencialmente un bloque de memoria con espacio para guardar las variables de dicho objeto. Crear el objeto es sinónimo de reservar espacio para sus variables, inicializarlo es darle un valor a estas variables. Para crear un objeto se utiliza el comando new. Veámoslo sobre un ejemplo: class Fecha{

int dia,mes,ano;

Fecha(){

dia=1;

mes = 1;

ano = 1900;

}

Fecha (int _dia, int _mes, int _ano){

dia= _dia;

mes = _mes;

ano = _ano;

}

}

Fecha hoy;

hoy = new Fecha();

Con el primer comando hemos creado un puntero que apunta a una variable tipo fecha, como está sin inicializar apuntara a null. Con el segundo inicializamos el objeto al que apunta hoy, reservando espacio para sus variables. El constructor las inicializará, tomando el objeto hoy el valor 1-1-1900.

Page 13: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 13 de 62

Fecha unDia = new Fecha(8,12,1999);

Con esta sentencia creamos una variable que se llama unDia con valor 8-12-1999. Una vez creado un objeto será posible acceder a todas sus variables y métodos públicos, así por ejemplo en el ejemplo anterior unDia.dia = 8. Si la variable fuese privada (como en el ejemplo) solo podría acceder a ella el objeto particular al que pertenece esa variable. El único capaz de acceder a los atributos privados del objeto es el objeto mismo: class Fecha{

private int dia;

private int mes

private int ano;

Fecha() {

dia=1;

mes = 1;

ano = 1900;

}

Fecha (int ndia, nmes, nano) {

dia= ndia;

mes = nmes;

ano = nano;

}

}

De este modo no podríamos acceder a las variables de la clase fecha, para acceder a ella tendríamos que hacerlo mediante métodos que nos devolviesen su valor. A esto se le denomina encapsulamiento. Esta es la forma correcta de programar OOP: no debemos dejar acceder a las variables de los objetos por otro procedimiento que no sea paso de mensajes entre métodos. A su vez, cuando creamos un objeto de una clase padre, luego podemos apuntar con ese objeto a un objeto de una clase hija. Por ejemplo: class Persona {

protected int dni;

}

class Cliente extends Persona {

protected int nroCliente;

}

public class Test {

public static void main (String args []) {

Persona javi;

Cliente hectorCampora;

javi = new Persona();

hectorCampora = new Cliente();

javi = hectorCampora;

}

}

Constructores

Page 14: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 14 de 62

El constructor es un método cuyo nombre coincide con el nombre de la clase y que nunca devuelve ningún tipo de dato, no siendo necesario indicar que el tipo de dato devuelto es void. Pueden tener distintos accesos (public, protected, private). Este método es llamado cuando se construye un objeto, es el método responsable de la inicialización del objeto. Los constructores se emplean para inicializar los valores de los objetos y realizar las operaciones que sean necesarias para la generación de este objeto (crear otros objetos que puedan estar contenidos dentro de este objeto, abrir un archivo o una conexión de internet.....). Como cualquier método, un constructor admite sobrecarga. Cuando creamos un objeto (ya se verá más adelante como se hace) podemos invocar al constructor que más nos convenga. class animal{

int edad;

String nombre;

public animal(){

}

public animal(int _edad, String _nombre){

edad = _edad;

nombre = _nombre;

}

public void nace(){

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

}

public void get_nombre(){

System.out.println(nombre);

}

public void get_nombre(int i){

System.out.println(nombre +" " +edad);

}

public void get_edad(){

System.out.println(edad);

}

}

Método finalize En Java no existen los destructores. Para suplir esta ausencia existe un método en la clase Object que es llamado cuando el recolector de basura ha determinado la inalcanzabilidad de un objeto y se dispone a "limpiarlo" de la pila. Se trata del método protected void finalize() throws Throwable;

Si nuestra clase redefine este método, cumplimos con el mismo objetivo que si tuviésemos un destructor.

Page 15: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 15 de 62

this Es una variable especial de sólo lectura que proporciona Java. Contiene una referencia al objeto en el que se usa dicha variable. A veces es útil que un objeto pueda referenciarse a si mismo: class Cliente{

public Cliente(String n){

//Llamamos al otro constructor. El empleo de this ha de ser

//siempre en la primera línea dentro del constructor.

this(n, Cuenta.nuevo_numero());

.....

}

public Cliente (String n, int a){

nombre = n;

numero_cuenta = a;

}

.....

}

Otro posible uso de this, que ya se ha visto en ejemplos anteriores es diferenciar entre variables locales de un método o constructor y variables del objeto. public animal(int edad, String nombre){

//this.edad = variable del objeto perro

//edad = variable definida sólo dentro del constructor

this.edad =edad;

this.nombre=nombre;

}

super Del mismo modo que this apunta al objeto actual, tenemos otra variable, super, que apunta a la clase de la cual se deriva nuestra clase class Gato {

void hablar(){

System.out.println("Miau");

}

}

class GatoMagico extends Gato {

boolean gente_presente;

void hablar(){

if(gente_presente)

//Invoca al método sobreescrito de la clase padre

super.hablar();

else

System.out.println("Hola");

}

Page 16: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 16 de 62

}

Uno de los principales usos de super es, como ya se empleó en un ejemplo, llamar al constructor de la clase padre. En este caso, la llamada a super debe encontrarse en la primer línea del constructor. Atributos y métodos estáticos A veces, los atributos no se refieren a un objeto puntual, sino más bien a la clase que engloba los objetos. Esto mismo puede ocurrir con los métodos. Estos atributos y métodos referidos a la clase se llaman estáticos, y se definen anteponiendo el modificador static. Desde el contexto de un método estatico, no puede hacerse referencia a this (pues se trata de una referencia al objeto actual, y en este contexto no hay un objeto actual) ni a ningún método o atributo de instancia (sin explicitar el objeto propietario de ese atributo o método). Para inicializar estos atributos que pueden ser llamados antes de que se cree ningún objeto (o sea que no podemos contar con el constructor para inicializarlos) existe el inicializador estático. Se trata de un método que no devuelve nada, sin modificador de acceso, y sin parámetros. static {

cntAlumnos = 25;

}

}

Modificadores de acceso

Los modificadores son palabras reservadas que asignan, algunos, cierta característica o particularidad a una clase, a una interfaz, a un atributo o bien a un método. Mientras que otros, a veces llamados los de visibilidad o de accesibilidad, determinan el ámbito del elemento, desde dónde se va a poder acceder a él, o sea, qué clases pueden utilizarlo. Los modificadores de accesibilidad son public, protected y private. Si no se utiliza ninguno de ellos, se asume una visbilidad por defecto según el caso. Este comportamiento se muestra en la tabla siguiente en la columna "default" (algunos llaman "package" a este comportamiento por defecto, dado que la idea siempre es que el elemento sea accesible sólo desde el paquete donde se encuentra). Los modificadores de características son static, final, abstract, transient, volatile, synchronized, native y strictfp. A continuación hay una tabla sobre los modificadores más usados.

Page 17: Manual JAVA

public protected private default static final abstract

class visible desde otros paquetes N/A N/A visible sólo dentro del paquete N/A

No puede

tener clases

hijas - Si es

final, no

puede ser

abstract

No puede

tener clases

hijas - Si es

abstract, no

puede ser

final

métodovisible desde otros paquetes -

hereda

visible solo dentro del paquete -

hereda

visible sólo dentro de la clase -

no heredaprotected

Método

estático

No puede ser

redefinido

Debe estar

en una clase

abstracta, y

sólo puede

usar public o

protected. El

método no

tiene código.

atributovisible desde otros paquetes -

hereda

visible solo dentro del paquete -

hereda

visible sólo dentro de la clase -

no heredaprotected

Atributo

estático

Una vez

seteado el

valor, no

puede

modificarse

N/A

interface visible desde otros paquetes N/A N/A visible sólo dentro del paquete N/A N/A

No puede

tener

interfaces

hijas

inner classvisible desde otros paquetes -

hereda

visible solo dentro del paquete -

hereda

visible sólo dentro de la clase -

no heredaprotected

Clase

estática

No puede

tener clases

hijas - Si es

final, no

puede ser

abstract

No puede

tener clases

hijas - Si es

abstract, no

puede ser

final

métodos de

interfaces

visible desde otros paquetes -

heredaN/A N/A public N/A N/A

El método no

tiene código

atributos de

interfaces

visible desde otros paquetes -

heredaN/A N/A public static final

atributo de

clase

Una vez

seteado el

valor, no

puede

modificarse

N/A

Page 18: Manual JAVA

Página 18 de 62

Los menos usados son: transient: Se aplica a atributos. Indica que el mismo no debe ser tenido en cuenta en la serialización. synchronized: Se aplica a métodos. Indica que debe establecerse un bloqueo sobre la clase (si el método es estático) o sobre el objeto (si es de instancia) durante la ejecución del método. volatile: Se aplica a atributos estáticos. Indica que el atributo se bloquee con cada acceso para asegurar sincronismo cuando el atributo será accedido desde código "no-synchronized". native: Se aplica a métodos, y especifica que el cuerpo del método se encuentra en otro archivo, escrito en otro lenguaje dependiente de la plataforma.Dependiente de strictfp: Se aplica a métodos y clases. Aplicado a un método, todos los cálculos de punto flotante se realizan estrictamente de acuerdo al estándar IEEE 754. Todos los valores -incluyendo los intermedios- deben ser expresados como flotantes o doubles IEEE. Si el modificador se aplica a una clase, todos los métodos de la clase son, implícitamente, strictfp. Un listado de todas las combinaciones posibles:

Modificador Clase Atributo Método Constructor Bloque de código

public sí sí sí sí no protected no sí sí sí no default sí sí sí sí sí private no sí sí sí no final sí sí sí no no abstract sí no sí no no static no sí sí no sí native no no sí no no transient no sí no no no volatile no sí no no no synchronized no no sí no sí Herencia Cuando en Java indicamos que una clase “extends” (extiende) otra clase estamos indicando que es una clase hija de esta y que, por lo tanto, hereda todos sus métodos y variables. Este es un poderoso mecanismo para la reusabilidad del código. Al heredar de una clase partimos de su estructura de variables y métodos, y luego sólo añadimos lo que necesitemos o modificamos lo que no se adapta a nuestros requerimientos. Veamos un ejemplo: class animal{

protected int edad;

String nombre;

public animal(){

}

public animal(int _edad, String _nombre){

edad = _edad;

nombre = _nombre;

}

public void nace(){

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

}

public void get_nombre(){

System.out.println(nombre);

Page 19: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 19 de 62

}

public void get_nombre(int i){

System.out.println(nombre +" " +edad);

}

public void get_edad(){

System.out.println(edad);

}

}

//La clase perro extiende a animal, heredando sus métodos y

//variables

public class perro extends animal{

perro(){

edad = 0;

nombre ="Tobi";

}

perro(int edad, String nombre){

//Esta sentencia invoca a un constructor de la clase padre.

super(edad,nombre);

}

//Método estático que recibe un objeto de tipo perro e

//invoca a su método get_edad()

static void get1(perro dog){

//El métod get_edad() no está definido en perro, lo ha

//heredado de animal.

dog.get_edad();

}

public static void main (String[] args){

//Creamos un objeto de tipo perro

perro dog = new perro(8,"Bambi");

//Invocamos al método estático get1 pasándole el objeto

//de tipo perro creado.

perro.get1(dog);

}

}

Cuando se define un método de clase, no podrán existir métodos de instancia con el mismo nombre (aún cuando devuelvan tipos distintos), ni en la clase que lo definió, ni en las clases que heredan de ella. Existen clases que es deseable que existan sólo para agrupar un comportamiento común entre otras clases; pero esta clase padre no interviene directamente en el negocio, sino a través de sus clases hijas. En estos casos, la clase debe declararse abstract, y no podrá ser instanciada. Polimorfismo: Sobrecarga y Redefinición de métodos El polimorfismo es la característica de la POO por la cual un objeto puede comportarse de distintas manderas según el entorno; puntualmente, puede comprtarse como un objeto de una clase ancestra o como un objeto que implementa cierta interfaz.

Page 20: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 20 de 62

Para cumplimentar esto, el compilador se ha diseñado utilizando late binding, esto es, la llamada al método es enlazada con el método puntual en tiempo de ejecución, en vez de enlazarse en tiempo de compilación. Un aspecto del polimorfismo es lograr que clases hijas modifiquen el comportamiento provisto por el padre para adaptarlo a sus requerimientos. Este es el concepto de redefinición (override), escribir un método en una clase hija con -casi- la misma cabecera que la del método homónimo en la clase padre. Las únicas diferencias pueden ser:

• ampliar la visibilidad • reducir la lista de excepciones lanzadas

Por ejemplo: class animal{

protected int edad;

String nombre;

public animal(){

}

public animal(int _edad, String _nombre){

edad = _edad;

nombre = _nombre;

}

public void nace(){

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

}

public void get_nombre(){

System.out.println(nombre);

}

public void get_nombre(int i){

System.out.println(nombre +" " +edad);

}

public void get_edad(){

System.out.println(edad);

}

}

public class perro2 extends animal{

perro2(){

edad = 0;

nombre ="Tobi";

}

perro2(int edad, String nombre){

super(edad,nombre);

}

static void get1(perro2 dog){

dog.get_edad();

Page 21: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 21 de 62

//Cuando ejecutemos este método en vez de ejecutarse

// el código de la clase padre se ejecutará el código de la

// clase hija, ya que ésta ha sobreescrito este método.

dog.get_nombre(11);

}

//Sobreescribe al método de la clase padre.

public void get_nombre(int i){

System.out.println(nombre +" " +i);

}

public static void main (String[] args){

perro2 dog = new perro2(8,"hola");

perro2.get1(dog);

}

}

Otro aspecto del polimorfismo sería pretender que el comportamiento de una clase (sus métodos) se adapten a distintas peticiones del mismo tipo, o dicho de otra manera, el mismo nombre de método (petición) con distina información (parámetros). Java admite lo que se llama sobrecarga (overload) de métodos: puede haber varios métodos con el mismo nombre pero a los cuales se les pasan distintos parámetros. El tipo devuelto no influye: no puede haber dos métodos que reciban los mismos parámetros y que devuelvan tipos distintos. Según los parámetros que se le pasen se invocará a uno u otro método: class animal{

int edad;

String nombre;

public void nace(){

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

}

public void get_nombre(){

System.out.println(nombre);

}

public void get_nombre(int i){

System.out.println(nombre +" " +edad);

}

public void get_edad(){

System.out.println(edad);

}

}

Interfaces En Java no está soportada la herencia múltiple, esto es, no está permitido que una misma clase pueda heredar las propiedades de varias clases padres. En principio esto pudiera parecer una propiedad interesante que le daría una mayor potencia al lenguaje de programación, sin embargo los creadores de Java decidieron no implementar la herencia múltiple por considerar que esta añade al código una

Page 22: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 22 de 62

gran complejidad (lo que hace que muchas veces los programadores que emplean programas que sí la soportan no lleguen a usarla). Sin embargo para no privar a Java de la potencia de la herencia múltiple sus creadores introdujeron un nuevo concepto: el de interface. Una interface (o interfaz) es formalmente como una clase, con tres diferencias: primero, sus métodos no tienen cuerpo, sólo sabecera; segundo, a la hora de definirla, en vez de emplear la palabra clave class se emplea inteface; y tercero, todos los atributos serán implícitamente public static final. Veámoslo con un ejemplo: interface animal{

public int edad = 10; /* es un atributo estático, aún cuando no se

explicite*/

public String nombre = "Bob";

public void nace();

public void get_nombre();

void get_nombre(int i);

}

Cabe preguntarnos cuál es el uso de una interface si sus métodos están vacíos. Bien, cuando una clase implementa una interface lo que estamos haciendo es una promesa de que esa clase va a implementar todos los métodos de la interface en cuestión; de hecho, hasta que no los implemente no podrá ser compilada. De esta forma puede abstraerse un comprtamiento dejando que distintas clases lo implementen a su manera. Con esto no ahorramos código. Lo que se busca aquí es independencia de implementación, algo muy distinto de la reutilización de código que se busca con la herencia. Veámoslo con un ejemplo de una clase que implementa la anterior interface: interface animal{

public int edad = 10;

public String nombre = "Bob";

public void nace();

public void get_nombre();

void get_nombre(int i);

}

public class perro3 implements animal{

perro3(){

get_nombre();

get_nombre(8);

}

//Compruébese como si cambiamos el nombre del método a nac()

//no compila ya que no henos sobreescrito todos los métodos

//de la interfaz.

public void nace(){

System.out.println("hola mundo");

}

Page 23: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 23 de 62

public void get_nombre(){

System.out.println(nombre );

}

public void get_nombre(int i){

System.out.println(nombre +" " +i);

}

public static void main (String[] args){

perro3 dog = new perro3();

// Compruebese como esta línea da un error al compilar

// debido a intentar asignar un valor

// a una variable final

// dog.edad = 8;

}

}

Las variables que se definen en una interface llevan todas ellas los modificadores static final, aún cuando no está escrito, y es obligatorio darles un valor dentro del cuerpo de la interface. Además no pueden llevar modificadores private ni protected, sólo public. Su función es la de ser una especie de constantes para todos los objetos que implementen dicha interface. Por último decir que aunque una clase sólo puede heredar propiedades de otra clase puede implementar cuantas interfaces se desee, recuperándose así en buena parte la potencia de la herencia múltiple, como un beneficio extra de las interfaces. interface animal1{

public int edad = 10;

public String nombre = "Bob";

public void nace();

}

interface animal2{

public void get_nombre();

}

interface animal3{

void get_nombre(int i);

}

public class perro4 implements animal1,animal2,animal3{

perro4(){

get_nombre();

get_nombre(8);

//edad = 10; no podemos cambiar este valor

}

public void nace(){

System.out.println("hola mundo");

}

public void get_nombre(){

System.out.println(nombre );

}

Page 24: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 24 de 62

public void get_nombre(int i){

System.out.println(nombre +" " +i);

}

public static void main (String[] args){

perro4 dog = new perro4();

}

}

Clases Internas Las clases internas (o "embebidas", o "inner") son clases que se encuentran dentro de la definición de otra clase. Esto hace suponer que el uso de éstas es representar esos casos de inclusión donde la clase incluída tiene cierta complejidad, y tiene un vínculo tan fuerte con la clase envolvente que no es utilizada nunca por separado, sino siempre como parte de la clase envolvente. Las posibilidades son:

• Tener una clase como un atributo estático de otra clase. En este caso la clase interna puede acceder al entorno estático de la clase (atributos y métodos estáticos).

• Tener la clase interna como un atributo. Entonces la clase interna puede acceder a todos los atributos y métodos de la clase externa. Para referirse al objeto envolvente actual, puede utilizarse MiClaseExterna.this.

• Encontrar la clase interna definida dentro del cuerpo de un método. La clase interna puede entonces acceder a toda la información del objeto externo, más una instantánea de los valores de las variables locales del método.

• Tener una clase anónima. Estas clases se definen en la llamada a un método (entre los parámetros).

Las sintaxis para la definición de una clase anónima es la siguiente, dependiendo de si hereda de una clase o si implementa una interfaz: new nombreClasePadre ([argumentosDelConstructor]) { cuerpoDeLaClase }

o: new nomreDeLaInterfaz () { cuerpoDeLaClase }

Clases de utilidad y uso común Clase String Como vimos anteriormente, un objeto que pertenezca a esta clase almacenará cadenas de caracteres. Una particularidad de esta clase es que los objetos String son constantes; sus valores no pueden modificarse una vez que se crearon. La clase StringBuffer soporta cadenas modificables. Esto no significa que el siguiente código lanze un error String aux = "txt";

aux = "txt nuevo";

Page 25: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 25 de 62

sino que en la segunda línea, la variable aux pasa a apuntar a otro objeto, a otra dirección de memoria, en vez de modificarse la información guardada en la primera dirección. Linea 1:

Linea 2:

Otra peculiaridad de esta clase es que la instanciación de nuevos objetos no requiere una llamada explícita al constructor. En vez de esto: String aux = new String("algo");

se utiliza esto (por facilidad; lo anterior no produciría error alguno): String aux = "algo";

No es necesario crear una variable para acceder a los métodos de la clase; los mismos pueden accederse desde cualquier constante de cadena. El siguiente código es enteramente válido: if ("ratoncito".length() > 5) {

Java provee un soporte especial para el operador de concatenación (+) de cadenas, y para la conversión de otros objetos a String. La concatenación se implementa a través de la clase StringBuilder o bien de la clase StringBuffer, con el método append. Las conversiones están implementadas a través del método toString de la clase Object. Los métodos más usados son:

• char charAt(int index): el método devuelve el caracter que se enuentra en la posición "index" (la primera posición es cero). "ratoncito".charAt(4) devuelve 'n'

• int compareTo(String anotherString): Compara la cadena con "anotherString" y devuelve: 0 si ambas cadenas son iguales <0 si la cadena se encuentra antes que la cadena argumento >0 si la cadena se encuentra después de la cadena argumento El encontrarse antes y después depende del largo de las cadenas, y del orden de los caracateres que forman las cadenas en el conjunto Unicode utilizado. "ratoncito".compareTo("ratonillo") devuelve un valor menor que cero porque en la

txt txt nuevo aux

txt aux

Page 26: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 26 de 62

posición cinco (comenzando desde cero) el caracter 'i' es posterior al caracter 'c'. También existe el método compareToIgnoreCase que no toma en cuenta diferencias entre mayúsculas y minúsculas.

• boolean equals(Object a): Compara la cadena con un objeto, y devuelve true si el objeto a no es nulo, y si a representa la misma secuencia de caracteres que la cadena. También existe el método equalsIgnoreCase que no toma en cuenta las diferencias entre mayúsculas y minúsculas. "ratoncito".equals("Ratoncito") devuelve false

• int indexOf(char c): Devuelve la posición donde primero se encontró el caracter 'c' en la cadena. Si el caracter no se encontró devuelve -1. Además, se puede especificar desde qué posición empezar la búsqueda con el método int indexOf(char c, int startFrom), y se puede buscar una cadena en vez de un caracter. También existen los métodos lastIndexOf con las mismas consideraciones, pero buscando desde atrás hacia adelante en la cadena.

• int length(): devuelve el largo de la cadena "ratoncito".length() devuelve 9

• String substring(int beginIdex, int endIndex): devuelve la subcadena que se encuentra entre la posición beginIndex y la posición endIndex-1. El segundo argumento puede no existir; en ese caso, la subcadena va hasta el final de la cadena original. "ratoncito".substring(4,6) devuelve "n"

• String trim(): devuelve una copia de la cadena, sin los espacios al comienzo y al final de la misma. " ratoncito ".trim() devuelve "ratoncito"

• String toUpperCase(): convierte todos los caracteres de la cadena a mayúsculas. "R53tulP".toUpperCase() devuelve "R53TULP"

• String toLowerCase(): convierte todos los caracteres de la cadena a minúsculas. "R53tulP".toLowerCase() devuelve "r53tulp"

• valueOf: es un método estático que puede recibir un boolean, un char, un int, un long, un float, un double o incluso un objeto; en cualquier caso, devolverá una representación en String del argumento pasado. String.valueOf(67.9) devuelve "67.9"

Más información en http://java.sun.com/j2se/1.4.2/docs/api/java/lang/String.html Clase Math La clase java.lang.Math contiene métodos para realizar las operaciones numéricas básicas, tales como la exponenciación, la logaritmación, la raíz cuadrada, las funciones trigonométricas, etcétera. Todas las propiedades y los métodos de la clase son estáticos, por lo que para acceder a ellos no es necesario instanciar la clase en ningún objeto en particular. Además de métodos, la clase provee las constantes Math.E (base de los logaritmos neperianos), Math.PI (constante Pi, ambos son double). Por ejemplo, con Math.pow(double a, double b) obtenemos el resultado de elevar el número “a” a la potencia “b”. Otra función importantísima es el método random() para generar números aleatorios. A decir verdad, para la generación de números aleatorios la clase Math recurre a otra clase, puntualmente java.util.Random. Además de esta clase (java.lang.Math), se cuenta con un paquete (java.Math) donde se encuentran definidas unas clases de tipos de datos muy usuales: BigInteger (para modelar un entero grande) y

Page 27: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 27 de 62

BigDecimal (para modelar un número real exacto -hasta cierta precisión- y no por aproximación como float y double). Éstas suelen ser necesarias para comnicarse con la base de datos.

// Entero aleatorio entre 5 y 27, ambos inclusive

// (la distancia del intervalo es 22)

alea = (int) ((Math.round(Math.random() * 22) + 5));

Más información en http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Math.html Fechas Para el manejo de fechas existen varias clases en Java. La principal de ellas es la clase java.util.Date. Esta clase modela una fecha como la cantidad de milisegundos desde el primero de Enero de 1970 a las 0:00:00 (a este momento se le denomina "época"). Para obtener la fecha actual basta con crear un objeto Date llamando al constructor por defecto. Las clases java.sql.Date y java.sql.Timestamp fueron creadas para conectar a Java con las bases de datos, proveyendo tipos de datos de fecha simple y de fecha más hora respectivamente. Objetos de estas clases son los que reciben los métodos setDate y setTimestamp de la clase PreparedStatement para asignar valores a los parámetros tipo DATE y DATETIME, siempre respectivamente. La clase abstracta Calendar apunta a un manejo mucho más sofisticado de la fecha, con información desde la era hasta el día de semana. La clase hija más útil para el mundo occidental es GregorianCalendar. Para el pasaje entre un objeto tipo Date y una cadena de texto se utiliza la clase SimpleDateFormat. Al construir un objeto de esta clase se especifica un formato, que se utilizará para parsear la cadena (String -> Date) y para formatear la fecha (Date -> String). import java.text.ParseException;

import java.text.SimpleDateFormat;

import java.util.Date;

import java.util.GregorianCalendar;

public class DateTest {

public static void main(String[] args) {

Date dt = null;

SimpleDateFormat sdfEntrada = new SimpleDateFormat("dd/MM/yyyy");

SimpleDateFormat sdfSalida = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");

GregorianCalendar cal = new GregorianCalendar();

// Parsear una cadena a un Date

// esto puede arrojar una excepción ParseException

try{

dt = sdfEntrada.parse("18/8/1978");

}catch(ParseException pe){

System.out.println(pe.toString());

pe.printStackTrace();

}

// Formatear una fecha

Page 28: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 28 de 62

if (dt != null) System.out.println(sdfSalida.format(dt));

//Obtener información más completa sobre la fecha

// Imprimir la zona horaria

System.out.println(cal.getTimeZone().getDisplayName());

// Imprimir el año

System.out.println(cal.get(cal.YEAR));

// Imprimir booleano que indica si el año es bisiesto o no

System.out.println(cal.isLeapYear(cal.get(cal.YEAR)));

// Imprimir las horas de diferencias respecto de Greenwich

System.out.println(cal.get(cal.ZONE_OFFSET)/(1000*60*60));

}

}

La salida por consola de la ejecución de este programa es:

18/08/1978 00:00:00

Hora de Argentina

2006

false

-3

Conjuntos de Datos Arreglos (arrays) Un “array” es la forma más básica de agrupar datos. Todos los elementos del array deben ser del mismo tipo. Los arreglos se crean con el operador new seguido del tipo y número de elementos. Una vez creado el arreglo con un tamaño determinado, ese tamaño no puede ser modificado (los arreglos son conjuntos de tamaño fijo). Se puede acceder al número de elementos de un arreglo con la variable miembro implícita length (por ejemplo, arr.length). Se accede a los elementos de un arreglo con los corchetes [ ] y un índice que varía de 0 a length-1. Se pueden crear arreglos de objetos de cualquier tipo. Los elementos de un arreglo se inicializan al valor por defecto del tipo correspondiente (cero para valores numéricos, el carácter nulo para char, false para boolean, null para referencias a objetos). Como todos los objetos, los arreglos se pasan como argumentos a los métodos por referencia. Se pueden crear arreglos anónimos (por ejemplo, crear un nuevo arreglo como argumento actual en la llamada a un método). La inicialización de los arreglos se puede realizar de las siguientes maneras: 1. Con valores entre llaves {...} separados por comas. 2. Para arreglos de objetos, con varias llamadas a new dentro de las llaves {...}. Si se igualan dos referencias a un arreglo no se copia el vector, sino que se tiene un arreglo con dos nombres, apuntando al mismo y único objeto. Para crear una referencia a un arreglo son posibles dos formas:

double[] x; // preferible

double x[];

Luego se deberá crear el arreglo en sí con el operador new:

x = new double[100];

Page 29: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 29 de 62

La creación de la referencia y la creación del arreglo mismo se pueden unir en una sola línea:

double[] x = new double[100];

String dias[] = {"lunes", "martes", "miércoles", "jueves", "viernes", "sábado",

"domingo"};

Un arreglo puede tener también dos dimensiones. En ese caso se les denomina matrices. Una matríz se puede crear directamente en la forma:

int [][] mat = new int[3][4];

O bien se puede crear de modo dinámico dando los siguientes pasos: 1. Crear la referencia indicando con un doble corchete que es una referencia a matriz:

int[][] mat;

2. Crear el arreglo de referencias a las filas:

mat = new int[nfilas][];

3. Reservar memoria para los vectores correspondientes a las filas:

for (int i=0; i<nfilas; i++)

mat[i] = new int[ncols];

A continuación se presentan algunos ejemplos de creación de matrices bidimensionales:

// crear una matriz 3x3

// se inicializan a cero

double mat[][] = new double[3][3];

int [][] b = {{1, 2, 3}, {4, 5, 6}};

int c = new[3][]; // se crea el arr. de referencias a matrices c

[0] = new int[5];

c[1] = new int[4];

c[2] = new int[8];

En el caso de una matriz b, b.length es el número de filas y b[0].length es el número de columnas (de la fila 0). Por supuesto, las matrices bidimensionales pueden contener tipos primitivos de cualquier tipo u objetos de cualquier clase. Colecciones (Java Collections Framework) Una colección es simplemente un objeto que agrupa múltiples elementos en una sola unidad. Las colecciones se usan para almacenar, devolver, manipular y comunicar datos en conjunto. Típicamente representan datos de un conjunto natural de cosas. Y la Collections Framework es la arquitectura que unifica la representación y manipuación de colecciones. Primeramente, este framework se compone de una serie de interfaces que definen los tipos de conjuntos que pueden existir. Luego, existen implementaciones genéricas de estas interfaces.

Interfaces A continuación se muestran las interfaces que se encuentran en el núcleo de la JCF.

Page 30: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 30 de 62

Un Set (o conjunto) es un tipo especial de colección, así como un SortedSet (o conjunto ordenado) es un tipo especial de Set, y así sucesivamente. Nótese que en la jerarquía participan dos árboles distintos: un Map (o mapa) no es una colección verdadera. La siguiente lista describe las interfaces del núcleo:

• Collection: es la raiz de la jerarquía de colecciones. Una Collection representa un grupo de objetos conocidos como sus elementos. La interfaz Collection es el denominador común que todas las colecciones implementan, y se usa para pasar colecciones de un lugar a otro y para manipularlas cuando se desea la máxima generalidad.

• Set: es una colección que no puede contener elementos duplicados. Esta interfaz modela la abstracción matemática del conjunto.

• List: es una colección ordenada (se la suele llamar también secuencia). Las listas pueden contener elementos duplicados. El usuario de una lista generalmente tiene control preciso sobre dónde en la lista es insertado cada elemento y puede acceder a ellos mediante un índice entero (posición).

• Queue: es una colección tipo pila o cola, utilizada para contener varios elementos antes de su procesamiento. Aparte de las operaciones básicas de las colecciones, una cola provee operaciones adicionales para la inserción, extracción e inspección. Las colas típicamente, pero no necesariamente, ordenan los elementos en una forma FIFO (primero en entrar - primero en salir). Cualquiera sea el orden utilizado, la cabeza de la cola es el elemento que extraerá con una llamada a remove o poll, y el final de la cola es donde se insertarán los elementos nuevos. Más información en http://java.sun.com/docs/books/tutorial/collections/interfaces/queue.html

• Map: es un objeto que mapea claves a valores. Un mapa no puede contener claves duplicadas. Cada clave se mapea a lo sumo a un valor.

• SortedSet: es un conjunto que mantiene sus elementos en un orden ascendente. • SortedMap: es un mapa que mantiene sus mapeos en un orden ascendente de claves.

Clases Vector y ArrayList La clase Vector implementa un arreglo de objetos que puede cambiar de tamaño. Por debajo subyace un arreglo de objetos (como atributo privado de la clase), y se muestran métodos que se encargan de hacer "crecer" -o mejor dicho, "crear un arreglo más grande"- cuando se agotaron las posiciones del arreglo subyacente. Estas propiedades también se aplican a la clase ArrayList, aunque las diferencia es que la clase Vector tiene todos sus métodos “sincronizados” (un aspecto que hace al paralelismo y al bloqueo de objetos). Ambas clases son hijas de AbstractList (una implementación de la interfaz List en una clase abstracta) y permiten elementos null. Más información en http://java.sun.com/j2se/1.4.2/docs/api/java/util/Vector.html

Page 31: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 31 de 62

Clases HashMap y Hashtable Una tabla hash, o "vector asociativo", es una tabla que mapea "llaves" o "claves" a "valores". Cualquier objeto no nulo puede ser usado como llave o como valor. La diferencia entre un objeto de la clase Hashtable y un objeto de la clase HashMap es que el segundo permite valores nulos y la llave nula. Además, los métodos provistos por HashMap son todos sincronizados. Estas clases no garantizan el orden del mapa; en particular, no se garantiza que el orden permanecerá constante en el tiempo. Recorriendo colecciones Existen dos formas de recorrer colecciones: con una construcción for-each, y usando Iterator Construcción for-each La construcción for-each, introducida en la versión 1.5 del JDK, permite recorrer una colección o un arreglo usando un bucle for. El siguiente código usa la construcción for-each para imprimir cada elemento de una colección en una línea distinta. for (Object o : collection)

System.out.println(o);

Más información sobre esta nueva forma de uso de for en http://java.sun.com/docs/books/tutorial/java/nutsandbolts/for.html Clase Iterator Un Iterator es un objeto que permite recorrer una colección y quitar elementos de ella selectivamente. Se puede obtener un Iterator de una Collection llamando al método iterator(). A continuación se muestra la definición de la interfaz Iterator. public interface Iterator<E> {

boolean hasNext();

E next();

void remove(); //optional

}

El método hasNext() devuelve true si la iteración tiene más elementos, y el método next() devuelve el siguiente elemento en la iteración. El método remove() quita el último elemento retornado por next() de la colección subyacente. El método remove puede ser llamado una sola vez por cada llamada a next(), y arrojará una excepción si esta regla es violada. Este método remove() es la única forma segura de modificar una colección durante una iteración.

Page 32: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 32 de 62

Excepciones Java incorpora en el propio lenguaje la gestión de errores. El mejor momento para detectar los errores es durante la compilación. Sin embargo prácticamente sólo los errores de sintaxis son detectados durante este periodo. El resto de problemas surgen durante la ejecución de los programas. En el lenguaje Java, una Exception es un cierto tipo de error o una condición anormal que se ha producido durante la ejecución de un programa. Algunas excepciones son fatales y provocan que se deba finalizar la ejecución del programa. En este caso conviene terminar ordenadamente y dar un mensaje explicando el tipo de error que se ha producido. Otras, como por ejemplo no encontrar un fichero en el que hay que leer o escribir algo, pueden ser recuperables. En este caso el programa debe dar al usuario la oportunidad de corregir el error (indicando una nueva localización del fichero no encontrado, por ejemplo). Un buen programa debe gestionar correctamente todas o la mayor parte de los errores que se pueden producir. Hay dos “estilos” de hacer esto:

1. A la “antigua usanza”: los métodos devuelven un código de error. Este código se chequea en el entorno que ha llamado al método con una serie de if else if …, gestionando de forma diferente el resultado correcto o cada uno de los posibles errores. Este sistema resulta muy complicado cuando hay varios niveles de llamadas a los métodos.

2. Con soporte en el propio lenguaje: En este caso el propio lenguaje proporciona construcciones especiales para gestionar los errores o Exceptions. Suele ser lo habitual en lenguajes modernos, como C++, Visual Basic y Java.

Excepciones estándar de Java Los errores se representan mediante dos tipos de clases derivadas de la clase Throwable: Error y Exception. La siguiente figura muestra parcialmente la jerarquía de clases relacionada con Throwable:

La clase Error está relacionada con errores de compilación, del sistema o de la JVM. De ordinario estos errores son irrecuperables y no dependen del programador (excepto los errores de compilación...) ni debe preocuparse de capturarlos y tratarlos (aunque el código deberá estar libre de errores para compilarse). La clase Exception tiene más interés. Dentro de ella se puede distinguir:

1. RuntimeException: Son excepciones muy frecuentes, de ordinario relacionadas con errores de programación. Se pueden llamar excepciones implícitas.

Page 33: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 33 de 62

2. Las demás clases derivadas de Exception son excepciones explícitas. Java obliga a tenerlas en cuenta y chequear si se producen.

El caso de RuntimeException es un poco especial. El propio Java durante la ejecución de un programa chequea y lanza automáticamente las excepciones que derivan de RuntimeException. El programador no necesita establecer los bloques try/catch para controlar este tipo de excepciones. Representan dos casos de errores de programación:

1. Un error que normalmente no suele ser chequeado por el programador, como por ejemplo recibir una referencia null en un método.

2. Un error que el programador debería haber chequeado al escribir el código, como sobrepasar el tamaño asignado de un array (genera un IndexOutOfBoundsException automáticamente).

En realidad sería posible comprobar estos tipos de errores, pero el código se complicaría excesivamente si se necesitara chequear continuamente todo tipo de errores (que las referencias sean distintas de null, que todos los argumentos de los métodos sean correctos, y un largo etcétera). Las clases derivadas de Exception pueden pertenecer a distintos packages de Java. Algunas pertenecen a java.lang (Throwable, Exception, RuntimeException, …); otras a java.io (EOFException, FileNotFoundException, ...) o a otros packages. Por heredar de Throwable todos los tipos de excepciones pueden usar los métodos siguientes:

• String getMessage(): Extrae el mensaje asociado con la excepción. • String toString(): Devuelve un String que describe la excepción. • void printStackTrace(): Indica el método donde se lanzó la excepción.

Lanzar una Exception Cuando en un método se produce una situación anómala, podemos lanzar nosotros explícitamente una excepción. El proceso de lanzamiento de una excepción es el siguiente:

1. Se crea un objeto Exception de la clase adecuada. 2. Se lanza la excepción con la sentencia throw seguida del objeto Exception creado.

// Código que lanza la excepción MyException una vez detectado el error

MyException me = new MyException("MyException message");

throw me;

Esta excepción deberá ser capturada (atrapada, del inglés "to catch") y gestionada en el propio método o en algún otro lugar del programa (en otro método anterior en la pila o stack de llamadas). Al lanzar una excepción el método termina de inmediato, sin devolver ningún valor. Solamente en el caso de que el método incluya los bloques try/catch/finally se ejecutará el bloque catch que la captura y, si existiese, el bloque finally. Todo método en el que se puede producir uno o más tipos de excepciones (y que no utiliza directamente los bloques try/catch/finally para tratarlos) debe declarar en el encabezado qué excepciones puede lanzar por medio de la palabra throws. Si un método puede lanzar varias excepciones, se ponen detrás de throws separadas por comas, como por ejemplo:

public void leerFichero(String fich) throws EOFException,

FileNotFoundException {…}

Alternativamente, se puede poner únicamente una superclase de excepciones para indicar que se pueden lanzar excepciones de cualquiera de sus clases derivadas. El caso anterior sería equivalente a:

public void leerFichero(String fich) throws IOException {…}

Page 34: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 34 de 62

Las excepciones pueden ser lanzadas directamente por leerFichero() o por alguno de los métodos llamados por leerFichero(). Capturar una Exception Como ya se ha visto, ciertos métodos de los packages de Java y algunos métodos creados por cualquier programador producen (“lanzan”) excepciones. Si el usuario llama a estos métodos sin tenerlo en cuenta se produce un error de compilación con un mensaje del tipo: “… Exception java.io.IOException must be caugth or it must be declared in the throws clause of this method” o bien "Unhandled exception". El programa no compilará mientras el usuario no haga una de estas dos cosas:

1. Gestionar la excepción con una construcción del tipo try {…} catch {…}. 2. Re-lanzar la excepción hacia un método anterior en el stack, declarando que su método

también lanza dicha excepción, utilizando para ello la construcción throws en el header del método.

El compilador obliga a capturar las llamadas "excepciones explícitas", pero no protesta si se captura y luego no se hace nada con ella. En general, es conveniente por lo menos imprimir un mensaje indicando qué tipo de excepción se ha producido. Bloques try / catch En el caso de las excepciones que no pertenecen a las RuntimeException y que por lo tanto Java obliga a tenerlas en cuenta habrá que utilizar el bloque try/catch/finally. El código dentro del bloque try está “vigilado”: Si se produce una situación anormal y se lanza por lo tanto una excepción el control salta o sale del bloque try y pasa al bloque catch, que se hace cargo de la situación y decide lo que hay que hacer. Se pueden incluir tantos bloques catch como sean necesarios, cada uno de los cuales tratará un tipo de excepción. Las excepciones se pueden capturar individualmente o en grupo, por medio de una superclase de la que deriven todas ellas. El bloque finally es opcional. Si se incluye sus sentencias se ejecutan siempre, sea cual sea la excepción que se produzca o si no se produce ninguna. El bloque finally se ejecuta aunque en el bloque try haya un return. En el siguiente ejemplo se presenta un método que debe "controlar" una IOException relacionada con la lectura de ficheros y una MyException propia:

void metodo1(){

...

try {

// Código que puede lanzar las excepciones IOException y

// MyException

} catch (IOException e1) {

// Se ocupa de IOException simplemente dando aviso

System.out.println(e1.getMessage());

} catch (MyException e2) {

// Se ocupa de MyException dando un aviso y finalizando la

// función

System.out.println(e2.getMessage());

return;

} finally {

// Sentencias que se ejecutarán en cualquier caso

Page 35: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 35 de 62

...

}

...

} // Fin del metodo1

Puede existir un bloque try sin bloques catch. En este caso se debe incluir un bloque finally para que se pueda compilar. Esta situación se presenta cuando dentro del bloque try ningún método puede lanzar excepciones explícitas pero sí implícitas, y deseamos que en ese caso -si surge una de esas excepciones implícitas-, se haga algo (lo que se encuentra en el bloque finally) antes del abrupto final del programa, y sin darle ningún tratamiento con bloques catch. Como se puede deducir, no es una situación muy común. Relanzar una Exception Existen algunos casos en los cuales el código de un método puede generar una Exception y no se desea incluir en dicho método la gestión del error. Java permite que este método pase o relance (throws) la Exception al método desde el que ha sido llamado, sin incluir en el método el bloque try/catch correspondiente. Esto se consigue mediante la adición de throws más el nombre de la Exception concreta después de la lista de argumentos del método. A su vez el método superior deberá incluir los bloques try/catch o volver a pasar la Exception para "arriba" en la pila de llamadas. De esta forma se puede ir pasando la Exception de un método a otro hasta llegar al último método del programa, el método main(). El ejemplo anterior (metodo1) realizaba la gestión de las excepciones dentro del propio método. Ahora se presenta un nuevo ejemplo (metodo2) que relanza las excepciones al siguiente método:

void metodo2() throws IOException, MyException {

...

// Código que puede lanzar las excepciones IOException y MyException

...

} // Fin del metodo2

Según lo anterior, si un método llama a otros métodos que pueden lanzar excepciones explícitas, tiene dos posibilidades:

1. Capturar las posibles excepciones y gestionarlas. 2. Capturar las posibles excepciones y remitirlas hacia el método que llamó al método actual.

Si no hace ninguna de las dos cosas anteriores el compilador da un error. Si en vez de tratarse de excepciones explícitas se tratase de excepciones implícitas, el compilador no avisará de nada y el programa se compilará sin problemas. En caso de producirse una excepción, el programa aborta la ejecución sin que podamos controlar nada. Bloque finally {...} El bloque finally {...} debe ir detrás de todos los bloques catch considerados. Si se incluye (ya que es opcional) sus sentencias se ejecutan siempre, sea cual sea el tipo de excepción que se produzca, o incluso si no se produce ninguna. El bloque finally se ejecuta incluso si dentro de los bloques try/catch hay una sentencia continue, break o return. La forma general de una sección donde se controlan las excepciones es:

try {

Page 36: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 36 de 62

// Código “vigilado” que puede lanzar una excepción de tipo A, B o C

} catch (A a1) {

// Se ocupa de la excepción A

} catch (B b1) {

// Se ocupa de la excepción B

} finally {

// Sentencias que se ejecutarán en cualquier caso

}

El bloque finally es necesario en los casos en que se necesite recuperar o devolver a su situación original algunos elementos. No se trata de liberar la memoria reservada con new ya que de ello se ocupará automáticamente el garbage collector. Como ejemplo se podría pensar en un bloque try dentro del cual se abre un fichero para lectura y escritura de datos y se desea cerrar el fichero abierto. El fichero abierto se debe cerrar tanto si produce una excepción como si no se produce, ya que dejar un fichero abierto puede provocar problemas posteriores. Para conseguir esto se deberá incluir las sentencias correspondientes a cerrar el fichero dentro del bloque finally. Crear nuevas excepciones El programador puede crear sus propias excepciones sólo con heredar de la clase Exception o de una de sus clases derivadas. Lo lógico es heredar de la clase de la jerarquía de Java que mejor se adapte al tipo de excepción. Las clases Exception suelen tener dos constructores:

1. Un constructor sin argumentos. 2. Un constructor que recibe un String como argumento. En este String se suele definir un

mensaje que explica el tipo de excepción generada. Conviene que este constructor llame al constructor de la clase de la que deriva mediante super(String).

Al ser clases como cualquier otra se podrían incluir variables y métodos nuevos. Por ejemplo:

class MiExcepcion extends Exception {

public MiExcepcion() { // Constructor por defecto

super();

}

public MiExcepción(String s) { // Constructor con mensaje

super(s);

}

}

Herencia de clases y tratamiento de excepciones Si un método redefine otro método de una super-clase que utiliza throws, el método de la clase derivada no tiene obligatoriamente que poder lanzar todas las mismas excepciones de la clase base. Es posible en el método de la subclase lanzar las mismas excepciones o menos, pero no se pueden lanzar más excepciones. No puede tampoco lanzar nuevas excepciones ni excepciones de una clase más general. Se trata de una restricción muy útil ya que como consecuencia de ello el código que funciona con la clase base podrá trabajar igualmente con referencias de clases derivadas, respetándose de esta manera el polimorfismo.

Page 37: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 37 de 62

JDBC JDBC (Java DataBase Connectivity) es el framework provisto por Java para conectarse a bases de datos. La idea es independizar esta comunicación del motor puntual, brindando una interfaz común. A continuación veremos las características más básicas de la interfaz del desarrollador del API JDBC. Esto es, la parte del API que un desarrollador Java, dado un manejador para una determinada base de datos, necesita para interactuar con la base de datos. Esta parte del API permite al desarrollador realizar tres tareas:

1. Establecer una conexión con una base de datos 2. Enviar una consulta SQL a la base de datos 3. Procesar los resultados de la consulta.

El API JDBC del desarrollador de aplicaciones consta de dos partes, por un lado está el paquete java.sql, que contiene las clases e interfaces que permiten acceder a la funcionalidad básica del API JDBC. Este paquete forma parte de la edición estándar de la plataforma Java (J2SE), desde la versión 1.1 de ésta. Por otro lado están las clases e interfaces del paquete javax.sql, que forma parte de la edición empresarial de plataforma Java (J2EE), las que no serán vistas en este módulo.

Los manejadores Los manejadores, también llamados drivers, son un conjunto de clases que implementan las clases e interfaces del API JDBC necesarias para que una aplicación Java pueda conectarse con una BD. Cuando los desarrolladores de una BD desean que esta pueda ser accesible mediante JDBC éstos deben implementar un manejador para esa base de datos; la misión del manejador será traducir comandos estándar del API JDBC al protocolo nativo de esa base de datos. Cada base de datos emplea un protocolo diferente de comunicación, protocolos que normalmente son propietarios. El uso de un manejador, una capa intermedia entre el código del desarrollador y la base de datos, permite independizar el código Java que accede a la BD del sistema de BD concreto a la que estamos accediendo, ya que en nuestro código Java emplearemos comandos estándar, y estos comandos serán traducidos por el manejador a comandos propietarios de cada sistema de BD concreto. Si queremos cambiar el sistema de BD que empleamos lo único que deberemos hacer es reemplazar el antiguo manejador por el nuevo, y seremos capaces de conectarnos la nueva BD.

Tipos de manejadores Hay 4 tipos de manejadores JDBC, que difieren en si usan o no tecnología Java pura, en su rendimiento y en la flexibilidad para cambiar de base de datos. Veamos cuales son:

Puente JDBC-ODBC (tipo1) ODBC es un API estándar semejante a JDBC, que permite que lenguajes como C++ accedan de un modo estándar a distintos sistemas de BD. Un manejador tipo puente JDBC-ODBC delega todo el trabajo sobre un manejador ODBC, que es quien realmente se comunica con la BD. El puente JDBC-ODBC permite la conexión desde Java a bases de datos que no proveen manejadores JDBC. Fue muy útil cuando se creó el API JDBC, ya que muchas BD no disponían de manejadores JDBC, pero sí de manejadores ODBC. Empleando el puente JDBC-ODBC podía accederse a estas bases de datos empleando el API JDBC. Este tipo de manejador tiene dos desventajas: por un lado depende de código nativo, ya que el manejador ODBC no ha sido desarrollado en Java. Esto compromete la portabilidad de nuestro desarrollo. Por otro lado al emplear este tipo de manejador nuestra aplicación llama al

Page 38: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 38 de 62

gestor de manejadores JDBC, quien a su vez llama al manejador JDBC (puente JDBC-ODBC), quien llama al manejador ODBC, que es el que finalmente llama a la base de datos. Hay muchos puntos donde potencialmente puede producirse un fallo, lo cual hace que estos manejadores muchas veces sean bastante inestables.

El puente forma parte del jdk de SUN, está en el paquete sun.jdbc.odbc. Inicialmente este tipo de manejador fue bastante útil por aliviar la carencia de manejadores JDBC. Hoy en día es el menos recomendado por limitar la portabilidad de la aplicación, al requerir código nativo, y por emplear tantas capas para la comunicación con la base de datos, lo que a veces hace que se incremente la inestabilidad de estos manejadores. Manejador nativo (tipo2) Se basa en una biblioteca escrita en código nativo para acceder a la base de datos. El manejador traduce las llamadas JDBC a llamadas al código de la librería nativa, siendo el código nativo el que se comunica con las bases de datos. La librería nativa es proporcionada por los desarrolladores de la BD. Estos manejadores son más eficientes y tienen menos puntos de fallo que el puente JDBC-ODBC ya que hay menos capas entre el código de la aplicación y la base de datos. Sin embargo siguen teniendo el problema de pérdida de portabilidad por emplear código nativo.

Page 39: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 39 de 62

Manejador de JDBC-Net (tipo3) El manejador se comunica con un servidor intermedio que se encuentra entre el cliente y la base de datos. El servidor intermediario se encarga de traducir las llamadas del API JDBC al protocolo específico de la base de datos. No se requiere ningún tipo de código nativo en el cliente, por lo que la portabilidad de la aplicación está garantizada: el manejador es tecnología 100% Java. Además, si el intermediario es capaz de traducir las llamadas JDBC a protocolos específicos de diferentes sistemas de BD podremos emplear un mismo manejador para comunicarnos con diferentes bases de datos. Esto lo convierte en el manejador más flexible de todos. No obstante se trata de un driver complejo, ya que requiere la presencia de un middleware, una capa intermedia entre el cliente y la base de datos, por lo que no es muy común emplearlo en arquitecturas simples, sino más bien en arquitecturas sofisticadas donde muchas veces entran en juego varias bases de datos distintas.

Page 40: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 40 de 62

Manejador Java (tipo4) El manejador traduce directamente las llamadas al API JDBC al protocolo nativo de la base de datos. Es el manejador que tiene mejor rendimiento, pero está más ligado a la base de datos que empleemos que el manejador tipo JDBC-Net, donde el uso del servidor intermedio nos da una gran flexibilidad a la hora de cambiar de base de datos. Este tipo de manejadores tambien emplea tecnología 100% Java.

El API de JDBC, paquete java.sql

Page 41: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 41 de 62

Clase DriverManager Como su nombre indica esta clase es la gestora de los diversos drivers (manejadores) que haya en nuestra aplicación. Es posible que sea necesario en una misma aplicación tener varios manejadores para una misma base de datos, acceder a varias bases de datos que emplean distintos manejadores, o bien ambas situaciones a la vez. De ahí el interés de contar con este gestor de manejadores. Los métodos que más nos interesan de esta clase son

static Connection getConnection(String url) y

static Connection getConnection(String url, String user, String password)

Estos métodos intentan establecer conexión con la base de datos que le indiquemos en el campo url empleando para ellos todos los manejadores que hemos registrado. La diferencia entre un método y el otro, obviamente, es que en uno sólo se especifica la base de datos a la que nos queremos conectar, mientras que en el otro se indica también el nombre de usuario de la base de datos y su password. Más adelante explicaremos que es el objeto Connection que devuelven estos métodos. Lo que hace la clase DriverManager para intentar establecer conexión con la base de datos es invocar al método connect de la interface Driver, interface que como veremos deben implementar todos los manejadores. Esta operación se realizará con todos los manejadores que estén registrados; si el manejador devuelve null significa que no se ha podido conectar con la base de datos; entonces el gestor intenta de nuevo conectarse con otro manejador. Si consigue conectarse con un manejador no sigue intentándolo con el resto de los manejadores registrados, y si no consigue establecer la conexión con ningún manejador lanza una excepción tipo SQLException.

Page 42: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 42 de 62

Sintaxis de los URL de JDBC Los URL (Uniform Resource Locator) de JDBC identifican una base de datos, y un protocolo de conexión a esta, de un modo unívoco. Toda URL de JDBC consta siempre de tres partes: protocolo:subprotocolo:subnombre. Un ejemplo de URL de conexión podría ser: “jdbc:odbc:usuarios”. Veamos qué es cada una de las partes del URL:

1. Protocolo de la conexión. Siempre es jdbc. 2. Subprotocolo de la conexión. Identifica el tipo de mecanismo de conexión que emplearemos

para acceder a la base de datos. Es imprescindible especificarlo para que el DriverManager pueda saber qué tipo de manejador debe emplear para crear la conexión. En el URL ejemplo el subprotocolo empleado para la conexión a la base de datos será ODBC, por lo que el DriverManager intentará establecer la conexión con todos los manejadores registrados que sean de tipo 1, puente JDBC-ODBC.

3. Subnombre, identifica la base de datos con la que queremos establecer una conexión. En el ejemplo el subnombre es “clientes”; será el sistema ODBC quien proporcione, a partir de ese nombre, el resto de la información necesaria para establecer la conexión con la base de datos. Sin embargo si empleamos otro protocolo puede ser necesario incluir la dirección de red del servidor como subnombre. Así por ejemplo para conectar con una base de datos remota la sintaxis de la URL es:

protocolo:subprotocolo://servidor:puerto/subnombre

Page 43: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 43 de 62

Así por ejemplo la URL: jdbc:bdnet://kwcserver:4040/usuarios Emplearía el protocolo jdbc y un manejador de tipo JDBC-Net para conectarse a la hipotética base de datos “usuarios” a través del puerto 4040 de la máquina kwcserver. Registro de un manejador Registrar un manejador no es más que cargar en memoria una clase que implementa el interfaz Driver, clase proporcionada por los desarrolladores de la base de datos. Existen tres formas de registrar un driver:

1. Empleando el cargador de clases. Para ello empleamos, al igual que para cargar cualquier otra clase, el método forName(String clase) de la clase Class. Class.forName("com.mysql.Driver");

2. Instanciando la clase que implementa el interfaz Driver. De este modo tendremos una referencia al driver, pudiendo establecer la conexión directamente con él sin necesidad de emplear la clase DriverManager. La sintaxis es la misma que para instanciar cualquier otra clase: Driver driverPraMySQL = new com.mysql.jdbc.Driver();

3. Definir la propiedad jdbc.drivers. Podemos hacerlo de dos modos diferentes: a. Definiendo la propiedad jdbc.drivers en nuestra máquina virtual. La sintaxis es:

jdbc.drivers=nombreManejador1: nombreManejador2:…

jdbc.drivers=gjt.mm.mysql.Driver:oracle.jdbc.driver.OracleDriver;

b. Emplear la opción –D al invocar a la máquina virtual. Para ello debemos arrancar nuestra aplicación con el comando java -Djdbc.drivers=nombreManejador1: nombreManejador2:…

java -Djdbc.drivers=gjt.mm.mysql.Driver:oracle.jdbc.driver.OracleDriver;

Interfaz Driver Aunque es vital para establecer la conexión con la base de datos en la práctica el desarrollador de aplicaciones puede olvidarse de esta interfaz. Es responsabilidad del desarrollador del manejador proveer una clase que implemente esta interfaz, y será responsabilidad del DriverManager llamar al método connect de los drivers registrados para intentar establecer la conexión con la base de datos. No obstante es, posible crear un objeto tipo Driver -instanciando la clase desarrollada por el desarrollador del manejador- e invocar directamente a su método connect, obteniendo una conexión con la base de datos sin necesidad de emplear la clase DriverManager. De este modo tenemos más control sobre el driver que estamos empleando en la conexión, cuando hemos registrado más de un driver, ya que no nos arriesgamos a que se establezca conexión con la BD con un driver distinto al que nosotros deseamos usar. Interfaz Connection Representa una conexión con la base de datos. Permite crear objetos que representan consultas que se ejecutarán en la base de datos, y permite acceder a información sobre la base de datos y las posibilidades del manejador JDBC. En la tabla recogemos los principales métodos de la interfaz Connection. void close() Libera lo los recursos de esta conexión. void commit() Hace permanentes los cambios que se

realizaron desde el último commit o rollback. Statement

createStatement() Crea un objeto de tipo Statement.

Page 44: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 44 de 62

boolean getAutoCommit() Indica si está en modo auto-commit. DatabaseMetaData getMetaData() Devuelve un objeto tipo DatabaseMetaData

con meta información a cerca de la base de datos contra la cual se ha abierto esta conexión.

CallableStatement prepareCall(String

sql) Crea un objeto CallableStatement para ejecutar procedimientos almacenados SQL en la BD.

PreparedStatement prepareStatement

(String sql) Crea un objeto PreparedStatement para ejecutar consultas SQL parametrizadas en la BD.

void rollback() Deshace todos los cambios realizados desde la última vez que se ejecutó comit o rollback

void setAutoCommit (boolean autoCommit) Indica el modo de commit en el cual se opera. Interfaz Statement Esta interfaz permite enviar instrucciones SQL a la base de datos. Podemos obtener un objeto que implemente esta interfaz a partir del método Statement createStatement() de la interfaz Connection. Para enviar una consulta tipo SELECT se emplea el método execteQuery(String sql). Este método devuelve un objeto tipo Resultset. Para enviar una instrucción tipo DELETE, UPDATE, INSERT o una instrucción DDL (Data Definition Language) se emplea executeUpdate(String sql). Mediante el método execute(String sql) podemos ejecutar cualquiera de los comandos anteriores. El método execute devuelve un booleano indicando si hay ResultSet o no. En caso de haberlo, se lo puede recuperar con el método getResultSet(). El método executeUpdate devuelve un entero indicando la cantidad de registros afectados. Y el método executeQuery devuelve un ResultSet. Interfaz PreparedStatement Representa una instrucción SQL preparada, esto es, una instrucción SQL precompilada cuya ejecución es mucho más eficiente que ejecutar repetidas veces una misma instrucción SQL. Cuando vayamos a ejecutar varias veces las mismas instrucciones debemos emplear esta interfaz. Podemos obtener un objeto que implemente esta interfaz a partir del método PreparedStatement createPreparedStatement(String sql) de la interfaz Connection. La interfaz PreparedStatement extiende a Statement añadiendo una serie de métodos setXXX(int indice, XXX valor) que permiten, para cada ejecución de la instrucción, asignar un valor a los parámetros de la instrucción SQL precompilada (los parámetros comienzan de 1). El primer valor, siempre un entero, es el índice del parámetro al cual le vamos a asignar un valor, empezando a contar en 1, y “valor” es el valor que le asignamos a ese parámetro. Interfaz CallableStatement Permite ejecutar procedimientos almacenados. Extiende a la interfaz PreparedSatatement. Podemos obtener un objeto que implemente esta interfaz a partir del método CallableStatement

prepareCall(String sql) de la interfaz Connection. Los parámetros de entrada (o de entrada-salida) del procedimiento almacenado deben ser setados con los métodos set heredados de PreparedStatement. Los parámetros de salida (o los de entrada-salida) deben ser registrados (con su correspondiente tipo) antes de la ejecución mediante el método registerOutParameter().

Page 45: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 45 de 62

Luego de la ejecución, los parámetros de salida se pueden obtener a través de métodos get. También pueden obtenerse uno o más objetos ResultSet. Interfaz ResultSet Esta interfaz representa un conjunto de datos que son el resultado de una consulta SQL. La clase posee una serie de métodos XXX getXXX(int columna) y XXX getXXX(String columna) que permiten acceder a los resultados de la consulta (para la sintaxis concreta de estos métodos acudir al javadoc de la interfaz ResultSet). La diferencia entre el primer tipo y el segundo es que en el primero usamos ordinales de columnas (empezando desde 1) mientras que en el segundo usamos nombres de columnas. Para acceder a los distintos registros empleamos un cursor, que inicialmente apunta justo antes de la primera fila. Para desplazar el cursor empleamos el método next().

Una aplicación JDBC básica Toda aplicación que acceda a una base de datos empleando el API JDBC debe realizar una serie de pasos:

1. Establecer una conexión con la base de datos. 2. Ejecutar una o varias instrucciones SQL 3. Si las intrusiones devuelven datos (SELECT) debemos procesarlos. 4. Liberar los recursos de la conexión.

Conectándose a la base de datos En un primer lugar debemos de registrar el o los manejadores que queramos emplear para acceder a la base de datos. Para ello debemos asegurarnos que el archivo .jar que contiene los drivers de nuestra base de datos está incluido en el CLASSPATH que emplea nuestra máquina virtual. Para registrar el manejador podemos emplear cualquiera de los procedimientos ya vistos. En este código se ha empleado el cargador de clases: Class.forName(String driver).

Para establecer la conexión emplearemos el método getConection de la clase DriverManager.

Page 46: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 46 de 62

String driver = "com.mysql.jdbc.Driver";

String url = "jdbc:mysql://kwcserver:3306/mydb";

Connection conexion = null;

try {

//Registramos el manejador

Class.forName(driver);

//Establecemos una conexión con la base de datos

conexion = DriverManager.getConnection(url, "user", "pwd");

...

Ejecutar las instrucciones SQL Para ejecutar las instrucciones SQL debemos crear un objeto tipo Statement, PreparedStatement o CallableStatement, dependiendo de si queremos ejecutar SQL, SQL precompilado o instrucciones no-SQL (procedimientos almacenados). Para ello empleamos los métodos definidos con tal propósito en la interface Connection. En nuestro caso será un objeto tipo Statement:

Statement st = conexion.createStatement().

A continuación ejecutamos las instrucciones SQL, empleando los métodos executeUpdate(String sql) para instrucciones tipo UPDATE, DELETE, INSERT o instrucciones DDL. Para las instrucciones de tipo SELECT empleamos el método Resultset executeQuery(String sql). En este caso primero se ejecuta una instrucción DDL, en la que se define una nueva tabla en la base de datos, a continuación creamos una serie de registros en la tabla, y finalmente realizamos una consulta en la cual pedimos el campo NOMBRE de todos los registros cuyo campo NIVEL_PARTICIPACION sea superior a 0.5.

//Creamos un objeto Statement que nos permitirá ejecutar instrucciones SQL

Statement st = conexion.createStatement();

//Ejecutamos una instrucción de DDL para crear una tabla

st.executeUpdate("create table usrs " +

"(NOMBRE varchar(25), " +

"LOGIN varchar(15)," +

"EDAD int, " +

"NIVEL_PARTICIPACION float)");

//Insertamos un grupo de registros en la tabla

st.executeUpdate( "insert into usuarios values('Pepe','pepe',23,

0.64)");

//Ejecutamos una consulta

Resultset resultset = st.executeQuery("select NOMBRE from usrs "+

"where NIVEL_PARTICIPACION > 0.5");

Procesar los resultados Tras realizar una consulta tipo SELECT obtenemos como resultado un conjunto de registros, a los cuales podemos acceder mediante la interface Resultset. Esta provee una serie de métodos getXXX(columna) que permiten acceder a cada campo de cada registro devuelto como respuesta a nuestra consulta. Es importante conocer a qué tipo Java se corresponde cada tipo SQL para saber mediante qué método debemos acceder al contenido de cada columna.

Page 47: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 47 de 62

La correspondencia entre tipos de datos Java y tipos de datos SQL se muestra en la siguiente tabla: Tipo Java Tipo SQL boolean BIT byte TINYINT short SMALLINT int INTEGER long BIGINT float REAL double DOUBLE java.math.BigDecimal NUMERIC String VARCHAR o LONGVARCHAR byte[] VARBINARY o LONGVARBINARY java.sql.Date DATE java.sql.Time TIME java.sql.Timestamp TIMESTAMP Debemos tener en cuenta siempre que cada objeto Resultset está ligado al objeto Statement que lo generó, y al objeto Connection de donde se obtuvo ese Statement. Por esto, si tenemos un objeto Resultset y ejecutamos una sentencia tipo SELECT con el Statement que lo había generado, los resultados de la anterior consulta se perderán, al igual que si la conexión a la base de datos se cierra.

// Mientras queden resultados por procesar.

// Recuerde que next() desplaza el cursor del Resulset una

// posición hacia delante cada vez que se invoca.

while (resultset.next()) {

// Accedemos a los resgistros resultado mediante los métodos

// getXXXX Indicando el nombre de la columna

System.out.println(resultset.getString("NOMBRE")+

", gracias por ser un usuario tan activo !");

}

Liberar los recursos de la conexión Una vez que hayamos terminado las operaciones que queríamos realizar en la base de datos debemos liberar todos los recursos que estaba consumiendo la conexión. Aunque estos recursos son automáticamente liberados por el GarbageCollector cuando sus correspondientes objetos son eliminados, es buena practica liberarlos manualmente, para que esta liberación se produzca en cuanto dejan de ser útiles. Los recursos que debemos liberar son el Resultset, el Statement y la propia conexión. Para liberarlos debemos invocar al método close() de sus correspondieres objetos. Invocar al método close() de Statement automáticamente libera los recursos de su ResultSet.

// No nos olvidemos de liberar todos los recursos que hemos empleado una

// vez que no necesitemos la conexión: debemos liberar tanto los recursos

// consumidos por el Resulset, por los Statement empleados y las propias

// Conexiones

try {

resultset.close();

statement.close();

conexion.close();

}catch(SQLException e){}

Page 48: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 48 de 62

Gestión de excepciones JDBC Todos los métodos definidos en las interfaces Connection, Statement y ResultSet, así como el método getConnection() de la clase DriverManager lanzan excepciones de tipo java.sql.SQLException, por lo que todo el código que de uno u otro modo acceda a la base de datos o procese los resultados de un acceso ha de estar dentro de una clausula try/catch, o bien ha de estar dentro de un método que lance excepciones de tipo SQLException. La Clase SQLException La excepción SQLException tiene algunas características que la diferencian del resto de las excepciones. Más que una excepción propiamente dicha, es una lista de excepciones encadenadas. Muchos y muy distintos problemas pueden suceder en el servidor, y no sería fácil almacenar toda la información correspondiente a todos los problemas en una única excepción. Por ello la clase SQLException ha sido diseñada como una cadena de Excepciones, permitiendo almacenar información de todos los errores surgidos en la operación. Esta clase tiene un método, getNextException(), que nos devuelve la siguiente excepción de la cadena , o null si no hay más excepciones encadenadas. El código que mostramos a continuación permite imprimir un código de error del fabricante de la base de datos, el mensaje de error, y la traza de todas las excepciones encadenadas en una SQLException

catch (SQLException e) {

do{

System.out.println(e.getErrorCode());

System.out.println(e);

e.printStackTrace();

e = e.getNextException();

}while (e != null);

}

Las principales causas de que se produzcan excepciones SQL son: 1. Se pierde la conexión con la base de datos. Muchas cosas pueden ir mal cuando la base de

datos es remota y se accede a ella a través de la red. 1. Se envía un comando SQL no válido; por ejemplo, tecleamos “wehere” en vez de “where”. En

este sentido resulta muy útil probar el SQL empleando alguna interfaz gráfica de nuestra base de datos antes de empotrarlo en el código Java.

2. Se hace referencia a una columna o tabla que no existen 3. Se emplea una función que no está soportada por la base de datos.

La clase SQLWarning Es una subclase de SQLException. Un SQLWarning se genera cuando se produce un error no fatal en la base de datos. Se encadenan igual que las excepciones SQL, pudiendo acceder al siguiente SQLWarning de la cadena mediante el método getNextWarning(). A diferencia que las excepciones los warning no se lanzan, cuando queramos acceder a ellos deberemos emplear el método getWarnings(), que está definido en las interfaces Connection, Statment y Resultset.

Metainformación

Page 49: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 49 de 62

El API JDBC nos permite acceder a metainformación sobre la base de datos y sobre el ResultSet generado por una consulta, es decir, a información a cerca de las características (no del contenido) de la base de datos y del Resultset, información como el nombre de la base de datos, la versión, el número y nombre de las columnas del Resultset, etc. La metainformación de la base de datos está representada por un objeto que implementa la interfaz java.sql.DatabaseMetaData, objeto al cual nos da acceso el método getMetaData() de la interface Connection. En DatabaseMetaData hay, por ejemplo métodos como getDriverName(), que nos devuelve el nombre del driver que estamos empleando, getDriverVersion(), que nos devuelve la versión del driver, getDatabaseProductName(), el nombre de la base de datos, getURL(), la URL de esta base de datos, etc. La metainformación del ResultSet está representada por un objeto que implementa la interfaz ResultSetMetaData, accesible mediante el método getMataData() de la interfaz ResultSet. En esta interfaz tenemos métodos para, por ejemplo, averiguar el número de columnas del ResultSet getColumnCount(), el nombre de cada columna, getColumnName(int columna), saber si podemos escribir en una columna, isWritable(int columna), clase Java correspondiente a una columna, getColumnClassName(int columna), etc. En este código se accede a la metainformación del ResultSet para averiguar el número y nombre de las columnas, y se imprime una tabla en la consola con el resultado.

public static void imprimeConsulta(ResultSet resultSet) {

try {

//Obtenemos la Metainformación de ResulSet

ResultSetMetaData resultSetMetaData =

resultSet.getMetaData();

//A partir de la metainformación obtenemos el número de

//columnas

int numeroDeColumnas =

resultSetMetaData.getColumnCount();

//Para todas las columnas

for (int i = 1; i <= numeroDeColumnas; i++) {

//Averguamos cual es el nombre de cada columna

String nombreDeLaColumna =

resultSetMetaData.getColumnName(i);

stringBuffer.append(nombreDeLaColumna + "\t");

}

//Ahora recorremos el resulset igual que en el primer ejemplo

while (resultSet.next()) {

//Accedemos a todas las columnas del registro

for (int i = 1; i <= numeroDeColumnas; i++) {

//Acedemos a todas como String, aunque empleando la

//metainformación sería posible averiguar que tipo de dato es

stringBuffer.append(resultSet.getString(i) + "\t");

}

}

System.out.println(stringBuffer.toString());

}

Transacciones Una transacción es un conjunto de operaciones que deben de realizarse de un modo atómico sobre la base de datos, esto es, o bien se realizan todas ellas correctamente, o bien no se realiza ninguna. Las transacciones son importantes para mantener la consistencia de la información en la base de datos.

Page 50: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 50 de 62

Imaginemos que tenemos un registro en la tabla “usuario”, la clave primaria de este registro es referenciada por un registro en una tabla llamada pedidos. Borrar a ese usuario implica borrar los pedidos que haya hecho ese usuario; en este caso podemos hacerlo con dos instrucciones DELETE, una sobre el registro del usuario y la otra sobre el pedido. Si la primera instrucción tiene éxito, y la segunda no, en la base de datos tendremos un pedido que referencia a un usuario que no existe. Para evitar este problema podemos agrupar las dos operaciones de borrado en una misma transacción; de ese modo o eliminamos ambos registros o no eliminados ninguno. En cualquier caso la información de la base de datos será consistente. Para emplear transacciones con el API JDBC debemos invocar el método setAutocommit(false) de la interfaz Connection. Por defecto la interfaz Connection ejecuta un commit, es decir, una confirmación de los cambios realizados en la base de datos, cada vez que ejecutamos una sentencia. Este método nos permite activar y desactivar este comportamiento. A continuación ejecutamos las operaciones que deseemos agrupar en una única transacción, y finalmente, si queremos confirmar los cambios, invocamos el método commit(), del interfaz Connection. En ese momento los cambios realizados se harán permanentes en la base de datos y se liberará cualquier tipo de bloqueo que haya realizado esta conexión. Si queremos cancelar los cambios invocamos al método rollback(), también de la interfaz Connection. Este método deshace todos los cambios hechos en la base de datos desde el último commit o rollback y libera cualquier tipo de bloqueo que haya realizado esta conexión. Cuando se gestionan las excepciones del código transaccional, siempre se suele poner un rollback en el catch, de ese modo si hay un error en la ejecución de alguna de las operaciones de la transacción deshacemos los cambios de las que se ejecutaron con éxito. De todas formas, cuando se desea trabajar con transacciones, lo primero a realizar es desactivar el modo auto-commit con el método setAutocommit(false), pues las conexiones son creadas por defecto en modo auto-commit.

Control de la concurrencia Al emplear transacciones para acceder a la base de datos pueden surgir una serie de problemas si hay varias conexiones abiertas al mismo tiempo contra la base de datos (la base se está accediendo de modo concurrente). Es importante conocer y entender cuales son esos problemas, así como conocer que niveles de aislamiento transaccional nos proporciona el API JDBC para evitarlos. Los problemas que pueden surgir son:

1. Lecturas sucias: una fila de la base de datos que ha sido modificada por una transacción y antes de que esta transacción ejecute un commit (si es que finalmente llega a ejecutarse) esa fila es leída por otra transacción.

1. Lecturas no repetibles: una transacción lee una fila, otra transacción modifica esta fila y la primera transacción vuelve a leer la fila, pero esta vez obtiene un resultado diferente de la primera lectura.

2. Lecturas fantasmas: una transacción selecciona todos los registros que satisfacen una determinada condición, otra transacción inserta un nuevo registro que satisface esa condición. La primera transacción vuelve a seleccionar todos los registros que satisfaces la misma condición, pero esta vez obtiene un registro adicional, el que insertó la segunda transacción.

Control del nivel de aislamiento transaccional El API JDBC nos permite controlar el nivel de aislamiento transaccional, esto es, cuales de los tres problemas recogidos en el apartado anterior vamos a tolerar en nuestra conexión y cuales no. Para ello empleamos el método setTransactionIsolation(int nivel) de la interfaz Connection. El entero que se le pasa es una variable estática y final definida dentro de la interfaz Connection. Los posibles valores son:

Page 51: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 51 de 62

1. Connection.TRANSACTION_NONE: Indica que la conexión no soporta transacciones. Podemos obtener el nivel de aislamiento de una conexión mediante el método getTransactionIsolation(), si su valor es éste, significa que la conexión no soporta transacciones.

2. Connection.TRANSACTION_READ_UNCOMMITED: Permite que sucedan lecturas sucias, fantasmas y no repetibles. Si una transacción finalmente ejecuta un rollback() otras transacciones pueden haber leído información incorrecta.

3. Connection.TRANSACTION_READ_COMMITED: Permite que sucedan lecturas fantasmas y no repetibles, pero no lecturas sucias.

4. Connection.TRANSACTION_REPETABLE_READ: Sólo permite que sucedan lecturas fantasmas, las lecturas sucias y no repetibles no sucederán.

5. Connection.TRANSACTION_SERIALIZABLE: Evita lecturas fantasmas, sucias y no repetibles. Podría parecer que lo ideal es emplear siempre el máximo aislamiento transaccional, sin embargo esto disminuye notablemente el rendimiento de la base de datos. En general el rendimiento de los accesos a la base de datos es inversamente proporcional al nivel de asilamiento transaccional que empleemos, por lo que debemos estudiar detenidamente que nivel trasnacional nos podemos permitir en nuestra aplicación.

Page 52: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 52 de 62

J2EE Introducción al modelo (1.4) J2EE es un modelo para el desarrollo de aplicaciones Java. Fue introducido por Sun en el año 1998 como un conjunto englobador de las tecnologías de servidor Java ya existentes para la época. O sea que Servlets, JSPs y EJBs existían desde antes de la presentación del modelo. El modelo se basa en la arquitectura cliente-servidor. Esto de "modelo" se refiere a que se está proponiendo un patrón para estructurar una aplicación. En ese sentido es un norte, una primera guía cuando se está diseñando la arquitectura de una solución software. A partir de ahí, cada aplicación tiene sus particularidades, lo que hace que sean muy pocas las que implementarán todo el modelo completo y sin alteraraciones. En el choque entre un modelo puramente conceptual y la realidad de cada aplicación, se sabe que no sería conveniente aplicar exactamente el mismo patrón a todas las aplicaciones posibles. Entonces, el modelo está pensado teniendo en cuenta esta posterior adaptabilidad al caso. Como consecuencia, en ciertos puntos se limita a proponer la solución más avanzada, y en otros recurre a la tecnología más usada al momento de concebirse. Por ejemplo, podría haberse elegido seguir el modelo J2EE. Pero por el tamaño de la aplicación -dicho de otra forma, el presupuesto-, y por la mantenibilidad requerida -para no excederse del presupuesto se aceptó una mantenibilidad no óptima-, el arquitecto podría proponer implementar una aplicación web con Servlets y JSPs. En este caso la capa de negocio quedaría a cargo de los mismos Servlets. Capas, Componentes y Contenedores Entonces, sabiendo que estamos en un terreno conceptual, el modelo J2EE propone la división de una aplicación en cuatro capas:

• La capa de cliente: se ejecuta en la computadora del usuario final. Brinda interacción y dinamismo siempre que la funcionalidad no demande una comunicación con el servidor para obtener o guardar más información.

• La capa de presentación: es la encargada de tomar las peticiones del cliente, ejecutar la lógica de navegación, y obtener una respuesta a dicha petición, consultando para ello a la capa de negocio. Una vez que se sabe la respuesta, esta capa se encarga también de escribirla en el protocolo que el cliente entienda.

• La capa de lógica de negocio: es la encargada de mantener las reglas del negocio. En esta capa se encuentra el modelo de objetos del sistema, tanto objetos de entidad como objetos gestores. Aparte de utilizarse como abreviatura de esta capa, la palabra "negocio" denomina algo más complejo que está en el mundo de lo social: es la organización que está por detrás, que contiene al sistema de información, sea que aquella tenga fines de lucro o no.

• La capa de datos: es la encargada de guardar los datos del negocio. De esta tarea se encargan las bases de datos de diferentes tipos y tecnologías, por lo que el modelo no ahonda demasiado en ella.

Cada capa está pensada para que pueda ejecutarse en computadoras diferentes. Aunque el espectro es amplio: podríamos tener más de una capa en una misma computadora (imaginemos un servidor web andando en una máquina y desde esa misma máquina acceder al sitio como un cliente más); o bien podríamos tener una misma capa alojada en más de una computadora (por ejemplo, cuando los datos residen en más de un servidor de bases de datos, replicándose mutuamente).

Page 53: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 53 de 62

Para llevar a cabo el trabajo tendremos "componentes", que no son otra cosa que programas (objetos, dentro del mundo Java). Estos componentes se ejecutarán en un "contenedor" apropiado. El contenedor brinda funcionalidad al componente que aloja, al mismo tiempo que le impone restricciones. Por ejemplo, podría encargarse de la persistencia de un bean de entidad (la persistencia siendo el guardar y recuperar información del bean en una base de datos), pero ese bean de entidad debe ajustarse a cierta versión de interfaz para que pueda ejecutarse en el contenedor. Veamos qué componentes y contenedores se encuentran en cada capa: Capa Contenedor Contenedor incluído Componente Cliente Client container HTML container HTML, JS Applet container Applets Presentación Web container Servlet container Servlets JSP container JSPs Lógica de negocio EJB container EJBs Datos En la capa de cliente se encuentra el "contenedor de cliente". Este contenedor suele estar implementado en un visor HTML o browser. En él se ejecutan los archivos HTML (estos archivos más que ejecutarse se "pintan") con su código JavaScript, y los Applets, si los hubiere. En la capa de presentación encontramos los componentes que toman peticiones y devuelven respuestas desde y hacia los clientes. Esta capa puede no existir en caso de que los clientes no sean tipo web (aquí web no significa HTTP, sino que podría usarse otro protocolo) sino que sean aplicaciones de escritorio que se sirven directamente de los componentes del negocio. Para la capa de la lógica de negocio, J2EE menciona que puede ser implementada con clases comunes, con JavaBeans, o con la solución de servidor más avanzada: los Enterprise JavaBeans (EJBs). Para la capa de datos, el modelo se adhiere a la tecnología más utilizada por el mercado: las bases de datos relacionales. No obstante, el modelo está preparado para tomar la información de otras fuentes, como pueden ser archivos de configuración, archivos XML, bases de datos orientadas a objetos, etc. Despliegue El despliegue o deployment (del verbo "deploy", desplegar) es la "instalación" de un módulo (un conjunto de componentes de una misma capa) en un contenedor. La aplicación, tras haber sido desarrollada, se comprime en un archivo con una estructura y una extensión dependientes del tipo de módulo de que se trate. Este archivo comprimido cuenta con un "descriptor de despliegue" o "deployment descriptor" que indica ciertas características de la aplicación, como los nombres JNDI requeridos, la configuración de seguridad, de persistencia, etc. El módulo web se empaqueta en un archivo .war que contiene sus componentes web (JSPs y Servlets, más archivos de recursos como gráficos). El módulo EJB se empaqueta en un archivo .jar que contiene los EJBs. Y ambos archivos (.war y .jar) se empaquetan en archivos .ear, que corresponden al módulo de aplicaciones de empresas. Un archivo .ear puede contener de 0 a n archivos .war, y de 0 a n archivos .jar. Módulo Extensión del archivo comprimido Descriptor de despliegue Aplicación de empresa .ear application.xml Aplicación Web .war web.xml Aplicación EJB .jar ejb-jar.xml

Page 54: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 54 de 62

Es común que se utilice, además del descriptor de despliegue estándar, un descriptor de despliegue particularizado para el contenedor. Una vez más, lo que en un principio se concibió para ser estándar, termina "personalizándose" y rompiendo el estándar. Lo que sí es estándar es la estructura de carpetas que tiene un archivo comprimido. Pero si al exportarse la estructura es la misma, no significa que mientras se desarrollaba los archivos hayan estado distribuidos en carpetas idénticas. De hecho, la estructura mientras se desarrolla varía de entorno a entorno. En la siguiente figura se muestra una comparación entre la forma de estructurar el proyecto según Eclipse 3.1 y según WebSphere 5.

Servlets Un Servlet es un componente web, una clase que implementa la interfaz javax.servlet.Servlet. Esta interfaz define métodos para el ciclo de vida del programa, para obtención de información del mismo, y para brindar un "servicio". Para minimizar el esfuerzo de implementar esta interfaz, J2EE provee una

Page 55: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 55 de 62

clase abstracta que brinda métodos por defecto, y adapta la interfaz a un protocolo puntual. Un Servlet HTTP es una clase que no sólo implementa esa interfaz sino que hereda de la clase javax.servlet.http.HttpServlet. Todos los servlets deben figurar en el descriptor de despliegue del módulo web. Allí se les asigna un alias que relaciona una URL con la clase puntual del Servlet. Típicamente, un Servlet HTTP atiende las peticiones web a través de varios métodos. Ocurre que esta clase divide el método service() de Servlet en los métodos doGet(), doPost(), doDelete(), doOptions(), doPut() y doTrace(), de los cuales los más utilizados son los dos primeros. El método doGet() que recibe las peticiones que usan formato GET, y el método doPost, que atiende las peticiones en formato POST. Generalmente se desea que el comportamiento del Servlet sea el mismo en ambos casos, por lo que suele ponerse, en el cuerpo de uno de estos métodos, una llamada al otro. En un Servlet se pueden especificar las tareas necesarias durante su ciclo de vida (métodos init() y destroy()). No es que se diga cuándo se instanciará, sino que al escribir el constructor, o el método destroy, se está diciendo qué se hará cuando sea instanciado y cuando sea eliminado. Asimismo pueden utilizarse "filtros" que se ubican como un entubamiento antes de la llamada a los métodos de servicio (doGet y doPost) del Servlet. Desde un servlet podemos recuperar la información que se encuentra en el formulario inserto en el objeto request. Esto se hace a través del método getParameter(String name). También podemos añadir información al objeto request, mediante el método setAttribute(String name, Object value). Asimismo, se puede añadir información al objeto session y al objeto application (aunque este objeto recibe el nombre de ServletContext). Recordemos que el objeto request es el que tiene el menor tiempo de vida: desde que el cliente hace una petición hasta que obtiene una respuesta a esa petición. El objeto session "vive" desde que un cliente realiza su primera petición, hasta que la sesión expira; la sesión puede expirar por una sentencia explícita, o bien porque transcurrió el período de inactividad especificado en la aplicación web. Finalmente, el objeto ServletContext (application) existe desde que se inicia la aplicación hasta que ésta es finalizada (o sea que el ciclo de vida es prácticamente concordante con el ciclo de vida del contenedor). La forma de obtener estos objetos es:

// Obtención del ServletContext (application)

ServletContext sc = this.getServletContext();

// o lo que es lo mismo

ServletContext sc2 = this.getServletConfig().getServletContext();

// Obtención del objeto session (si no existe, devuelve null)

HttpSession ses = request.getSession(false);

// Obtención del objeto session (si no existe, lo crea);

// idéntico a utilizar getSession(true)

HttpSession ses2 = request.getSession();

En el otro sentido, si podemos añadir información a estos objetos con los métodos setAttribute, podemos consultar esta información con los métodos Object getAttribute(String name), tanto desde el Servlet como desde un JSP. También se puede utilizar el método removeAttribute(String name) para quitar un atributo. Existen varias formas de direccionar el tráfico desde un Servlet hacia otro recurso web (Servlet o JSP). La primera forma es obtener un objeto RequestDispatcher desde el objeto request.

RequestDispatcher rd = request.getRequestDispatcher("../std/fset.jsp");

Page 56: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 56 de 62

Nótese que el objeto RequestDispatcher se relaciona con el recurso destino al momento de crearse. Una vez que tenemos este objeto, llamamos al método forward del mismo pasando como parámetros los objetos request y response con los que estuvimos trabajando en el Servlet.

rd.forward(request, response);

Una alternativa a esta forma es obtener el objeto RequestDispatcher desde el objeto ServletContext. En este caso, la URL con la que se relaciona el objeto sólo podrá ser una URL absoluta (en el caso anterior puede ser absoluta o relativa).

RequestDispatcher rd =

this.getServletContext().getRequestDispatcher("/jsp/std/fset.jsp");

Finalmente, aparte de hacer un forward se puede hacer un redirect hacia el recurso de destino. En este caso, lo que en realidad hacemos es devolver al cliente un código especial en la respuesta, indicando que el recurso no se encontraba en el servidor donde se estaba buscando, por lo que se lo redireccionará hacia otro servidor. Como se advierte, esta forma tiene un alto costo de performance, puesto que la petición volverá al cliente, y de allí de dirigirá de nuevo al servidor (al mismo o a otro), por lo que debería evitarse. De todas formas, la manera de hacerlo es:

response.sendRedirect("/jsp/std/fset.jsp");

JSPs La tecnología JSP es una especialización de los Servlets. Están pensados para escribir la respuesta HTML que se devolverá al cliente. Anteriormente a su introducción, los Servlets se encargaban de las dos tareas: ejecutar lógica, y escribir la respuesta. Algo que recuerda a CGI, pero que tiene como problema que mezcla código Java con HTML, reduciendo la legibilidad del archivo y volviéndolo grande y, en casos extremos, muy dificil de entender y mantener.

PrintWriter pw = response.getWriter();

pw.write("<html><head>");

Estas fueron las razones para que se creara un componente que se compila tal como en Servlet (sólo que se compila y ejecuta dinámicamente al recibir la primera petición), pero que se ve como un documento HTML. Otra ventaja de esto es que el rol de maquetador de páginas Web no necesita saber Java. Cada página es automáticamente compilada a un Servlet por el motor de JSP la primera vez que esa página es solicitada. Una vez que se compila, se ejecuta y el código compilado queda a disposición del contenedor por si reciba nuevamente una petición a esa página. El código compilado se desechará cuando el contenedor lo estime necesario. Una página JSP es archivo de texto simple que consiste en contenido HTML o XML con elementos JSP. El código fuente de una página JSP incluye:

• Directivas: Dan información global de la página, por ejemplo, importación de clases, página que majena los errores, bibliotecas de tags que se utilizarán, etc.

• Scripts de JSP: Es el código Java embebido en la página (se llaman Scriptlets). • Expresiones de JSP: Formatea las expresiones como cadenas para incluirlas en la página de

salida. • Código HTML y Javascript: muestra textualmente el código que se enviará en la respuesta.

Page 57: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 57 de 62

Una directiva de JSP es una declaración que proporciona la información del motor de JSP para la página que la pide. Su sintaxis general es:

<%@ directiva {atributo ="valor"} %>

dónde la directiva debe tener un número de atributos. Por ejemplo:

<%@ page import="java.util.Vector, java.math.BigDecimal" %>

En realidad, lo que ocurre es que se están seteando atributos (por ejemplo, el atributo import) de un objeto (por ejemplo, el objeto page). Otras directivas aparte de page son "include", para incluir archivos completos palabra por palabra, y "taglib", para incluir bibliotecas de etiquetas personalizadas. Para la directiva "page", otros atributos aparte de import son "errorPage", que indica la URL de la página que manejará las excepciones de JSP, y el atributo "isErrorPage", un booleano que indica si se trata de una página de error o de una página común. Los Scriptlets son bloques de código Java residentes entre los tags <% y %>. Hay algunos objetos implícitos disponibles para los Scripts desde entorno del Servlet. Vamos a verlos a continuación:

• request: Es la petición del cliente. Es normalmente una subclase de la case HttpServletRequest.

• response: Es la página JSP de respuesta y es una subclase de HttpServletResponse. • pageContext: Los atributos de la página y los objetos implicitos necesitan ser accesibles a

través de API, para permitir al motor de JSP compilar la página. Pero cada servidor tiene implementaciones específicas de cada uno de esos atributos y objetos. Para solucionar este problema, el motor de JSP utilizar una clase factory para devolver la implementación de clase PageContext del servidor. Esta clase PageContext es inicializada con los objetos response y request y algunos atributos de la directiva de la página (erropage, session, buffer y autoflush) y facilita los otros objetos implícitos para la pagina de petición.

• session: El objeto de sesión HTTP asociado a la petición. • application: Es lo que devuelve el Servlet cuando se llama a

getServletConfig().getContext(), o bien a getServletContext(). • out: El objeto que representa la salida de texto por pantalla. Es lo que se obtiene con el

método response.getWriter(); • config: El objeto ServletConfig de la página. • page: Es la forma que tiene la página para referirse a si misma. Se usa como alternativa al

objeto this • exception: Es una subclase libre de Throwable que es pasada a la página que majena los

errores. Finalmente, las expresiones son una magnifica herramienta para insertar código embebido dentro de la página HTML. Cualquier cosa que este entre los tags <%= y %> será evaluado, convertido a cadena y posteriormente mostrado en pantalla. La conversión desde el tipo inicial a String es manejada autómaticamente. Cuando se trabaja con archivos JSP, es crucial entender cabalmente los ditintos tiempos en los que se ejecutará la página. Primero existe el tiempo en que el motor compila la página. Para este tiempo es imprescindible realizar todas las importaciones necesarias. Luego viene el tiempo de ejecución de la página, donde se ejecutarán todos los Scriptlets y se evaluarán las expresiones, formando así un único documento HTML de respuesta. Este documento debe contener toda la información necesaria para el tercer tiempo de ejecución. El último tiempo de ejecución es cuando la respuesta llega al cliente, y el cliente interactúa con ella a través de JavaScript.

Page 58: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 58 de 62

Desde una página JSP podemos dirigirnos a otro componente enviando el formulario que generalmente se encuentra dentro de ella (haciendo un submit desde JavaScript), o bien modificando la dirección del objeto location.href, aunque de esta última forma no dispondremos del formulario. Para finalizar, cuando trabajo en esta capa recomiendo:

• Utilizar sólo un formulario por página. Son realmente pocas las veces en que necesitamos más de un formulario. Entonces, dejemos la complejidad y suciedad de los múltiples formularios para los casos estrictamente necesarios.

• Utilizar como nombre de formulario una palabra corta y representativa. Mi nombre favorito es "fo".

• Cerrar TODAS las etiquetas HTML, como si se tratase de XML (donde sí es obligatorio cerrar todos los tags). De esta forma nos ahorramos problemas con errores muy simples.

• Hacer el submit de un formulario por JavaScript. Aparte de ser necesario la mayoría de las veces, el código queda más mantenible.

• Utilizar el método POST en vez del método GET. De esta forma evitamos mostrar en la caja de dirección del browser la información interna de la aplicación.

• Al igual que el segundo punto de esta lista, esta recomendación es para aplicar no sólo en páginas JSP sino en todo el código que realicemos: ¡ INDENTAR !.

Taglibs Cuando programamos páginas JSP existen Scriptlets que se utilizan muy a menudo. Por ejemplo, hacer un Scriptlet para un bucle for, o para un if, o una expresión JSP para recuperar un valor. Es interesante la idea de utilizar tags o etiquetas para llevar a cabo estas funciones de lógica, porque dejan más limpio y legible el código, y porque quitan del desarrollador de páginas JSP la responsabilidad de saber Java. Piénsese en que es común que a esta tarea de escritura del JSP la realicen diseñadores gráficos. Entonces, el tiempo y costo que se ahorra una empresa por no necesitar que los desarrolladores JSP conozcan Java es muy considerable. Para ello, SUN proveyó las APIs necesarias para que todos pudiéramos escribir las bibliotecas de tags que considerásemos necesarias en nuestro proyecto. Y además, ofreció un conjunto de bilbiotecas "estándar", el JSTL (JSP Standard Tag Library) que nos proporcionan las funcionalidades más comunes. La JSTL se encuentra implementada por SUN y por el proyecto Jakarta de Apache. Para utilizar una taglib es necesario utilizar la directiva taglib en el archivo JSP. Esta directiva establece con un prefijo a utilizar en las llamadas a los tags de la biblioteca, y una URI donde se encuentra definida. Además, puede ser necesario incluir algunos .jar al proyecto, y/o archivos tld que contienen el conjunto de tags definidos por el .jar. También puede ser necesario declarar los taglibs utilizados por la aplicación en el archivo web.xml. Por ejemplo, en el JSTL se encuentran definidas cuatro bibliotecas para manejo de XMLs, de SQL, de formateo y de estructuras de control y repetitivas. Este último conjunto se denomina "core". Para disponer de él basta con incluir unos archivos denominados jstl.jar y otro llamado standard.jar. Luego, se utiliza una directiva como: <%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>

y ya se pueden utilizar los tags: <c:if test='${error != null}'>

<h1>Error de login</h1>

</c:if>

Page 59: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 59 de 62

Además, en este caso se está utilizando Expression Language (EL) dentro del atributo test para acceder al atributo error (un atributo definido por el usuario) del objeto request sin necesidad de utilizar un Scriptlet. JNDI El Java Naming & Directory Interface es el framework de Java que, similarmente a la función de JDBC, proporciona una interfaz estándar para los servicios de nombres y de directorios existentes. Estos servicios son muchos y muy variados, pero en general se caracterizan por publicar un nombre como referencia a un recurso. Este recurso podría ser desde una simple variable hasta un archivo o una impresora, pasando por una clase o ben una interfaz. Los nombres JNDI se registran generalmente en la confiuración del servidor, aunque podrían añadirse nombres JNDI desde el código. Desde la aplicación, para buscar un nombre primero debemos partir de un contexto. Si ese contexto es el del servidor actual, simplemente podemos crear un objeto InitialContext llamando al constructor por defecto:

InitialContext ctx = new InitialContext();

Si no fuera así, debemos construir el objeto utilizando un objeto Properties (que contiene un HashMap por debajo) con las propiedades del contexto que deseamos instanciar. Por ejemplo, para indicar la clase factory del contexto, y la URL del proveedor (esto es, del servidor donde se buscarán los nombres), se podría utilizar:

Properties props = new Properties();

props.put(Context.INITIAL_CONTEXT_FACTORY,

"org.jnp.interfaces.NamingContextFactory");

props.put(Context.PROVIDER_URL, "192.168.1.73:1099");

InitialContext ctx = new InitialContext(props);

Desde la aplicación, buscar un nombre es sumamante simple. Basta con utilizar el método lookup del contexto. Este método devuelve un Object, por lo que será necesarío un casting hacia la clase adecuada:

DataSource ds = (DataSource) ctx.lookup("jdbc/mysqlDS");

DataSources Un DataSource es un objeto capaz de devolver conexiones a una base de datos puntual. Las conexiones que devuelve son más bien "lógicas", pues en realidad establecerá sólo UNA conexión real con el servidor de datos, y sobre ella hará un "pool" o lista de conexiones. Un DataSource se configura en el servidor, sea a través de una interfaz gráfica, o bien directamente modificando los archivos de configuración del servidor puntual. Aparte de la mejora de rendimiento, la idea era lograr que la configuración de la base de datos a la que debe comunicarse una aplicación sea independiente de la aplicación en sí; de esta forma, se puede cambiar de un motor a otro sin necesidad de tocar (y recompilar y re-desplegar) una sóla línea de código Java. EJBs (2.0)

Page 60: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 60 de 62

Un Enterprise Java Bean es un componente de servidor, generalmente encargado de la lógica de negocio de una aplicación. A partir de aquí, utilizaré la palabra "bean" para referirme a los EJBs, que nada tienen que ver con JavaBeans, unos componentes reutilizables, no de servidor, y pensados principalmente para el uso de "reflection", la técnica de deducir la estructura de un objeto en tiempo de ejecución. Los EJBs deben ser implementados en un proyecto EJB y ejecutarse en un contenedor EJB apropiado. Existen tres tipos básicos de beans, los de sesión, los de entidad, y los manejados por mensajes. Los dos primeros son síncronos, o sea que al realizarse una petición (una llamada a un método) el cliente espera la respuesta para continuar con su código. Los manejados por mensajes son asíncronos, el cliente envía un "mensaje" al bean y continúa con su código sin esperar la respuesta. Los beans de entidad representan las clases de entidad del negocio. Podría parecer una redundancia pero no lo es: el concepto de entidad es anterior a los beans, y excede los lineamientos de este curso. Simplemente podemos decir que si necesitamos guardar la información de una clase en la base de datos, entonces estamos tratando con una entidad. A esto de guardar la información de un objeto en la base de datos se denomina "persistencia" del objeto. La persistencia puede estar a cargo del contenedor (container managed persistance) o bien a cargo del mismo bean (bean managed persistance). Por otro lado, las clases pueden ser gestores. En este caso no contienen información que interese directamente al negocio como las entidades, sino que estas clases tienen como fin cordinar el trabajo de otras clases, sean estas de entidad o bien otros gestores. Estas clases se modelan a través de los Session Beans. Existen dos tipos de SB: los que guardan información de las peticiones que antendieron en el pasado (son los beans "con estado", o stateful) y los que no ("sin estado", o stateless).

Si bien la forma de trabajo varía según estemos utilizando un tipo de bean u otro, para la instanciación de un bean según la especificación 2.0 se utilizan dos interfaces y una clase. Las interfaces son la "inicial" o home, y la remota. Cuando se desea utilizar un bean, lo primero a hacer es obtener una instancia de la interfaz Home utilizando JNDI. Esto de una "instancia de la interfaz" es la forma simplificada de decir que en el proveedor se crea un objeto y al cliente se devuelve la interfaz, similar al trabajo que se realiza en JDBC o con WebServices. La obtención de la referencia no puede

EJB

Session Beans

Stateless

Stateful

Entity Beans

BMP

CMP

Message Driven Beans

Page 61: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 61 de 62

recurrir a un casting explícito como se vió con DataSources, sino que es necesario utilizar el método estático narrow de la clase PortableRemoteObject:

InitialContext ctx = new InitialContext();

Object ref = ctx.lookup("ejb/com/ini/ValidateHome");

ValidateHome valHome = (ValidateHome) PortableRemoteObject.narrow(ref,

ValidateHome.class);

Una vez que tenemos la interfaz Home, llamando al método create() obtenemos una interfaz Remote, ligada a una instancia de la clase del bean en el servidor. Desde esta interfaz Remote podemos llamar a los métodos de negocio del bean. Validate val = valHome.create();

Integer empId = val.validarLogin(usr, pwd);

El ciclo comentado se muestra en la siguiente figura:

En general, los nombres de las clases y las interfaces siguen una nomenclatura fija. Si el bean se llamase Empleado, la interfaz remota se llamará simplemente Empleado, la clase de implementación del bean se llamará EmpleadoBean, y la interfaz Home se llamará EmpleadoHome. Cuando el bean se encuentra en el mismo contenedor desde donde se lo quiere utilizar, se utilizan interfaces locales (EmpleadoLocalHome sería la interfaz Home y EmpleadoLocal sería la interfaz remota). Recordemos que los EJBs fueron pensados para ser consumidos no sólo desde programas Java, sino desde cualquier programa que pueda adaptarse al estándar CORBA. Otra particularidad de los beans es que están pensados para ser consumidos remotamente. Para ello, el cliente debe contar con las interfaces, que se empaquetan en un archivo JAR de cliente EJB. La seguridad para el acceso al bean suele quedar a cargo del contenedor, especificándosela en el descriptor de despliegue (ejb-jar.xml, en este caso).

WebServices

Page 62: Manual JAVA

[email protected]

Manual de Java + J2EE

Página 62 de 62

Un WebService es un método que puede ser invocado remotamente. El concepto viene de la arquitectura SOA (Service Oriented Architecture), implementada por Microsoft a través de estos "WebServices". O sea que esta tecnología no tiene relación con J2EE, salvo porque puede ser implementada en aplicaciones que siguen el modelo J2EE. Un WebService puede estar implementado en una variedad de lenguajes, así como el cliente que lo consume, siempre que en esta comunicación se utilice el protocolo SOAP (Simple Object Access Protocol). Si bien con RMI primero, y con los EJB después, ya era posible llamar a métodos de objetos remotos en Java, con los WebServices se maximiza la simplicidad y la independencia entre las aplicaciones proveedor y cliente, puesto que la interfaz que debe conocer el cliente es sólo un archivo XML, puntualmente el WSDL, o WebService Description Language (nótese que el acrónimo no designaba en principio al archivo, sino al lenguaje; pero en el uso corriente, con ese nombre se designa al archivo de descripción puntual de un WebService). En cambio, cuando se trabaja con EJBs, el cliente debe conocer dos interfaces Java, aún cuando no es obligatorio que el cliente en sí esté programado en este lenguaje. El WebService puede concebirse para ser consumido desde una aplicación local, desde una intranet, o bien desde la internet. Al ser utilizado remotamente, desde la intranet o desde internet, se debe utilizar un "proxy" que ruteará las llamadas que se dirigen a la interfaz del cliente hacia el objeto remoto. Además, cuando se lo desea publicar hacia la internet, es necesario utilizar algún servicio de directorio como bien puede ser UDDI.