Tutorial Java
Tutorial Java
Programación en
Java
Introducción al lenguaje
Freddy RT
Página
Programación en Java
ÍNDICE
2.3 Operadores............................................................................................ 7
2.3.3 Condicionales...................................................................................... 9
Pág. I
Programación en Java
Pág. II
Programación en Java
Pág. III
Programación en Java
5 Excepciones ............................................................................................... 52
Pág. IV
Programación en Java
Pág. V
Programación en Java
Pág. VI
Programación en Java
Pág. VII
Programación en Java
1 INTRODUCCIÓN A JAVA
La Máquina Virtual Java o JVM es un entorno de ejecución para aplicaciones Java, cuya
principal finalidad es la de adaptar los programas Java compilados a las características
del sistema operativo donde se van a ejecutar.
Pág. 1
Programación en Java
Todo programa Java está organizado en clases, estás se codifican en archivos de texto
con extensión .java. Cada archivo de código fuente puede contener una o varias
clases, aunque lo normal es que haya un archivo por clase.
Cuando se compila un archivo .java se generan uno o varios archivos .class de código
binario (uno por cada clase), denominados bytecodes, que son independientes de la
arquitectura.
Esta independencia supone que los bytecodes no pueden ser ejecutados directamente
por ningún sistema operativo. Es durante la fase de ejecución cuando los archivos
.class se someten a un proceso de interpretación, consistente en traducir los bytecodes
a código ejecutable por el sistema operativo. Esta operación es realizada por el
software conocido como JVM.
1.3.1 OBJETOS
Un objeto es un elemento que expone una serie de operaciones (métodos) que pueden
ser utilizados por otros programas para la realización de tareas mayores (figura 1.2).
Pág. 2
Programación en Java
Para poder invocar a los métodos de un objeto desde fuera del mismo es necesario
disponer de la referencia al objeto. Normalmente esta se guarda en una variable que a
través del operador “.” permite hacer la llamada a los métodos del objeto de la forma
indicada en la figura 1.3.
Algunos métodos necesitan que se les proporcione una serie de datos (argumentos)
para poder realizar su función. Los argumentos deben ser suministrados en la llamada
al método, tal y como se aprecia en la figura 1.3.
1.3.2 CLASES
Las clases contienen la definición de los objetos, dicho de otra manera, una clase es el
lugar en el que se codifican los métodos que van a exponer los objetos de esa clase.
En Java una clase se define de la forma indicada en la figura 1.4.
Una vez definida la clase con sus métodos, los programadores podrán crear objetos de
la misma (instancias) para poder hacer uso de los métodos. Las instancias de una
clase se crean con el operador new; este operador crea la instancia, la almacena en
memoria y devuelve una referencia a la misma que normalmente se guarda en una
variable (figura 1.5).
Los métodos definen el comportamiento de los objetos de una clase, estos métodos
pueden hacer uso de campos o atributos para almacenar información sobre el objeto,
información que puede ser utilizada posteriormente por cualquiera de los métodos del
objeto. En Java, los métodos de una clase se implementan mediante funciones y los
atributos mediante variables, la sintaxis se especifica en la figura 1.6, donde la palabra
Pág. 3
Programación en Java
tipo representa un tipo de dato valido en Java y puede ser un tipo de dato primitivo o
un tipo objeto y debe ser utilizado para indicar el tipo de dato de los atributos, los
parámetros de métodos y el valor de devolución de éstos.
Toda aplicación Java está compuesta por al menos una clase. En alguna de esas
clases, que ha de estar declarada con el modificador de acceso public, debe existir un
método estático llamado main(), cuyo formato debe ser el indicado en la siguiente
figura.
Pág. 4
Programación en Java
Numéricos enteros. Son los tipos byte, short, int y long. Los cuatro representan
números enteros con signo.
Carácter. El tipo char representa un carácter codificado en el sistema Unicode.
Numérico decimal. Los tipos float y doble representan números decimales en
coma flotante.
Lógicos. El tipo boolean es el tipo de dato lógico, y los dos únicos valores que
puede tomar son true y false.
2.2 VARIABLES
Las variables pueden utilizarse para tratar los dos tipos de datos indicados
anteriormente:
Tipos primitivos. Estas variables almacenan el dato en si, tal cual se indique en
una expresión.
Tipo objeto. Los objetos en Java también se tratan a través de variables, sólo
que, a diferencia de los tipos primitivos, una variable de tipo objeto no contiene al
objeto como tal sino una referencia al mismo. No debe importarnos el valor exacto
contenido en la variable, tan sólo tenemos que saber que a través del operador “.”
podemos acceder con ella a los métodos del objeto referenciado, utilizando la
expresión:
variableObjeto.metodo();
Pág. 5
Programación en Java
Campo o atributo. Se les llama así a las variables que se declaran al principio de la
clase (figura 2.2), fuera de los métodos. Estas variables son compartidas por todos los
métodos de la clase. Las variables atributo pueden ser utilizadas sin haber sido
inicializadas de manera explícita.
Variable local. Son variables que se declaran dentro de un método y su uso está
restringido al interior del método (figura 2.2). Una variable local se crea en el
momento en que se hace la llamada al método, destruyéndose cuando finaliza la
ejecución de este. Toda variable local tiene que ser inicializada explícitamente antes de
ser utilizada.
Figura 2.2 Lugares en los cuales se puede declarar una variable en Java.
En Java es posible realizar conversiones entre todos los tipos básicos, con excepción de
boolean, que es incompatible con el resto de los tipos.
Conversiones implícitas
Para que una conversión pueda realizarse de forma automática, el tipo de la variable
destino debe ser de tamaño igual o superior al tipo de origen.
int k = 5, p;
Pág. 6
Programación en Java
short s = 10;
char c = “ñ”;
float h;
p = c; //conversión implícita char a int
h = k; //conversión implícita int a float
k = s; //conversión implícita short a int
Conversiones explícitas
Cuando no se cumplan las condiciones para una conversión implícita, esta podrá
realizarse explícitamente utilizando la expresión:
char c;
byte k;
int p = 400;
double d = 34.6;
c = (char) d; //se elimina la parte decimal
k = (byte) p; //se produce una pérdida de datos, pero la conversión es posible
2.2.4 CONSTANTES
Una constante es una variable cuyo valor no puede ser modificado. Para definir una
constante en Java se utiliza la palabra final, delante de la declaración de tipo,
siguiendo la expresión:
Una constante se define en los mismos lugares en los que se puede declarar una
variable: al principio de la clase y en el interior de un método.
2.3 OPERADORES
2.3.1 ARITMÉTICOS
Pág. 7
Programación en Java
La tabla 2.1 muestra los operadores aritméticos más importantes, incluyendo una
breve descripción de su función.
Operador Descripción
+ Suma dos valores numéricos
- Resta dos valores numéricos
/ Divide dos números. El tipo de resultado depende de los operandos,
pues en el caso de que ambos sean enteros, el resultado de la
división también lo será.
% Calcula el residuo de la división entre dos números.
++ Incrementa una variable numérica en una unidad y deposita el
resultado en la variable.
-- Decrementa una variable en una unidad y deposita el resultado en la
variable.
Sobre el operador +, hay que decir que, además de sumar números, se puede utilizar
para la concatenación o unión de cadenas:
También puede utilizarse para unir texto con el contenido de una variable que no sea
de tipo texto, puesto que Java convierte automáticamente a texto los operandos que
no sean de tipo cadena antes de realizar la concatenación.
2.3.2 ASIGNACIÓN
La tabla 2.2 muestra estos operadores y la función que realiza cada uno.
Operador Descripción
= Asigna la expresión de la derecha al operando situado a la izquierda del
operador.
+= Suma la expresión de la derecha a la variable situada a la izquierda del
operador. (b+=5 equivale a b=b+5)
-= Resta la expresión de la derecha a la variable situada a la izquierda del
operador. (c-=3 equivale a c=c-3)
*= Multiplica la expresión de la derecha con la variable y deposita el
resultado en la variable. (b*=2 equivale a b=b*2)
/= Divide la variable situada a la izquierda entre la expresión de la derecha
depositando el resultado en la variable. (c/=7 equivale a c=c/7)
%= Calcula el residuo de la división entre la variable situada a la izquierda y
la expresión de la derecha, depositando el resultado en la variable
Pág. 8
Programación en Java
2.3.3 CONDICIONALES
Operador Descripción
== Compara dos valores, en caso de que sean iguales el resultado de la
operación será true.
< Si el operando de la izquierda es menor que el de la derecha, el
resultado es true.
> Si el operando de la izquierda es mayor que el de la derecha, el
resultado es true.
<= Si el operando de la izquierda es menor o igual que el de la derecha, el
resultado es true.
>= Si el operando de la izquierda es mayor o igual que el de la derecha, el
resultado es true.
!= Si el valor de los operandos es diferente, el resultado será true.
Los operadores de comparación (<, >, <= y >=) únicamente podrán utilizarse para
comparar enteros, puntos flotantes y caracteres.
Igualdad de objetos
Los operadores de igualdad “==” y desigualdad “!=” pueden utilizarse para comparar
cualquier tipo de dato compatible.
Ahora bien, si los utilizamos para comparar variables de tipo objeto debemos recordar
que lo que contienen estas variables son referencias a objetos, no los objetos en sí,
por tanto, estamos comparando referencias y no objetos. Esto implica que podemos
tener dos variables referenciando a dos objetos iguales y que la condición de igualdad
de las variables resulte falsa.
Pág. 9
Programación en Java
2.3.4 LÓGICOS
Operan con valores de tipo boolean, siendo el resultado también de tipo boolean.
Operador Descripción
&& Operador lógico AND. El resultado será true si y sólo si los dos operandos
son true.
|| Operador lógico OR. El resultado será true si alguno de los operandos es
true.
! Operador lógico NOT. Actúa sobre un único operando, dando como
resultado el valor contrario al que tenga el operando.
2.4.1 INSTRUCCIÓN IF
Pág. 10
Programación en Java
Los únicos valores que puede evaluar switch son números enteros de tipo int.
Pág. 11
Programación en Java
Un switch puede contener cualquier número de case, aunque no puede haber dos
case con el mismo valor.
La sentencia break es opcional y se emplea para provocar la finalización del switch
al terminar la ejecución de un case.
El uso del bloque default es opcional.
La ejecución del bucle for comienza con la instrucción de inicialización, que suele
realizar la inicialización de una variable de control, incluyendo su declaración. A
continuación, se comprueba la condición, cuyo resultado debe ser del tipo boolean, en
cuyo caso, las instrucciones dentro del bloque de llaves {} se ejecutarán mientras
dicha condición no sea false.
Pág. 12
Programación en Java
condición, lo que garantiza que el bloque de instrucciones se ejecute por lo menos una
vez.
Las instrucciones repetitivas for y while, cuentan con dos instrucciones que permiten
abandonar la ejecución del bloque de instrucciones antes de su finalización. Estas
instrucciones son:
2.5 ARRAYS
2.5.1 DECLARACIÓN
Los arrays pueden declarase en los mismos sitios que las variables estándar: como
atributos de una clase o como variables locales de un método.
variable_array[índice]
Pág. 13
Programación en Java
Donde índice representa la posición a la que se quiere tener acceso, y cuyo valor debe
estar comprendido entre 0 y tamaño-1.
Todos los objetos array exponen un atributo público, llamado length que permite
conocer el tamaño al que ha sido dimensionado un array. El siguiente bloque de código
utiliza length para recorrer un array y rellenarlo con números enteros pares
consecutivos, empezando por el 0.
A partir de la versión 1.5 de Java, se cuenta con una nueva variante de la instrucción
for que facilita el recorrido de arrays y colecciones, eliminando la necesidad de utilizar
una variable de control que sirva de índice para recorrer las distintas posiciones de
estos.
Donde, tipo variable representa la declaración de una variable auxiliar del mismo tipo
que el array, variable que irá tomando cada uno de los valores almacenados en éste
con cada iteración del for, siendo var_array la variable que apunta al array.
Obsérvese como, sin acceder explícitamente a las posiciones del array, cada una de
estas es copiada automáticamente a la variable auxiliar n al principio de cada iteración.
Pág. 14
Programación en Java
Los arrays Java pueden tener más de una dimensión, por ejemplo, un array de enteros
de dos dimensiones se declararía:
int [][] k;
k = new int[3][5];
Igualmente, para acceder a cada una de las posiciones del array se utilizaría un índice
por dimensión:
k[1][3] = 28;
Pág. 15
Programación en Java
En Java las cadenas de caracteres no corresponden a ningún tipo básico de datos, sino
que son objetos pertenecientes a la clase String
Esta clase, proporciona una amplia variedad de métodos que permiten realizar las
operaciones de manipulación y tratamiento de cadenas de caracteres, habituales en un
programa.
Para crear un objeto String se puede seguir el procedimiento general para la creación
de objetos en Java, utilizando el operador new. La siguiente instrucción crea un objeto
String a partir de la cadena indicada y lo referencia con la variable s:
Una vez creado el objeto String, se puede utilizar para acceder a los métodos definidos
en la clase. Además, las variables de tipo String pueden utilizarse en una expresión
que haga uso del operador “+” para concatenar cadenas.
Pág. 16
Programación en Java
//El siguiente ejemplo muestra el número de veces que aparece el carácter //“e” en
una cadena
String s = “texto de prueba”;
int cont = 0;
for (int i = 0; i < s.length; i++){
if (s.charAt(i) == „e‟) {
cont++;
}
}
System.out.println(“La letra e aparece ” + cont + ”veces.”);
String s = “lunes,martes,miércoles”;
String[] cads = s.split(“,”);
for (int i = 0; i < cads.length; i++) {
System.out.println(“Día: ” + cads[i]);
}
Día: lunes
Día: martes
Día: miércoles
Pág. 17
Programación en Java
La clase Math dispone de dos atributos públicos estáticos que contienen dos de las
constantes públicas más utilizadas:
static double PI. Contiene el valor doble más cercano al número pi.
3.2.2 MÉTODOS
numero max(numero n1, numero n2). Devuelve el mayor de dos números. Existen
cuatro versiones de este método, pudiendo ser los números (tanto los parámetros
como los tipos de devolución) de tipo int, long, float o double.
numero min(numero n1, numero n2). Devuelve el menor de dos números. Existen
las mismas cuatro versiones que las del método max().
double ceil(double n). Devuelve el entero mayor más cercano al número indicado
en el parámetro:
double floor(double n). Devuelve el entero menor más cercano al número indicado
en el parámetro:
Pág. 18
Programación en Java
Para cada uno de los tipos de datos básicos, Java proporciona una clase que lo
representa. A estas clases se les conoce como clases de envoltorio, y se utilizan
fundamentalmente para:
Existen ocho clases de envoltorio: Byte, Short, Character, Integer, Long, Float,
Double y Boolean.
Todas las clases de envoltorio permiten crear un objeto de la clase a partir del tipo
básico. Por ejemplo, podríamos crear un objeto Integer a partir de un int de la
siguiente forma:
int k = 23;
Integer num = new Integer(k);
String s = “4.65”;
Float ft = new Float(s);
De cara a recuperar el valor a partir del objeto, las ocho clases de envoltorio
proporcionan un método con el formato xxxValue() que devuelve el dato encapsulado
en el objeto, donde xxx representa el nombre del tipo en el que se quiere obtener el
dato. Por ejemplo, para obtener el valor de los datos envueltos en los objetos de las
instrucciones anteriores utilizaríamos:
Pág. 19
Programación en Java
tipo numérico, donde xxx es el nombre del tipo al que se va a convertir la cadena de
caracteres, en función de la clase que se utilice. Por ejemplo, en la clase Integer
tenemos el método parseInt(), mientras que en Double encontramos parseDouble():
Una de las operaciones más habituales que tiene que realizar un programa es
intercambiar datos con el exterior. Para ello, el paquete java.io incluye una serie de
clases que permiten gestionar la entrada y la salida de datos en un programa.
El método printf()
Por otro lado, datos representa la información que va a ser enviada a la salida y sobre
la que se va a aplicar el formato, siendo el número de estos datos variable.
Pág. 20
Programación en Java
Sin embargo, el método read() proporcionado por la clase InputStream para la lectura
de datos, únicamente devuelve el ultimo carácter introducido a través del dispositivo,
lo que haría bastante ineficiente el código. Por ello, para realizar la lectura de cadenas
de caracteres desde el exterior es preferible utilizar la clase BufferedReader.
String s = bf.readLine();
import java.io.*;
public class tutorial {
public static void main(String args[]) throws IOException {
// 1. Crea objeto InputStreamReader
InputStreamReader ir = new InputStreamReader(System.in);
// 2. Crea objeto BufferedReader
Pág. 21
Programación en Java
Hay que tener en cuenta un punto importante cuando se utilizan ciertos métodos de
determinadas clases, se trata del hecho de que al invocar estos métodos en el
programa puede lanzar una excepción. Este es el caso del método readLine() de
BufferedReader, cuya llamada puede lanzar la excepción IOException.
Cuando se utiliza readLine() para leer datos numéricos, hay que tener en cuenta que el
método devuelve los caracteres introducidos como tipo String, por lo que deberemos
recurrir a los métodos de las clases de envoltorio (los métodos estáticos
parseXxx(String)) para convertir el dato a número y poder operar con él.
3.4.4 SCANNERS
Como acabamos de comprobar, la lectura de datos por teclado desde una aplicación
Java resulta bastante engorrosa. A fin de simplificar este proceso, se ha incorporado la
clase Scanner. Esta clase proporciona una serie de métodos para realizar la lectura de
datos desde un dispositivo de entrada o fichero, tanto en forma de cadena de
caracteres como en cualquier tipo básico.
Pág. 22
Programación en Java
import java.util.Scanner;
public class tutorial {
public static void main(String args[]) {
String nom;
int cod;
Scanner sc = new Scanner(System.in);
System.out.println("Introduzca su nombre: ");
nom = sc.next(); //lee el nombre
System.out.println("Introduzca su número de personal: ");
cod = sc.nextInt(); //lee el número de personal como int
System.out.println(nom + ":" + cod);
}
}
Como se ha indicado antes, hay que tener en cuenta que el delimitador de token es el
espacio en blanco. En el programa anterior esto significa que si el nombre introducido
contiene un espacio en blanco, por ejemplo, “Juan Perez”, al ejecutar la instrucción:
nom = sc.next();
la cadena recuperada en la variable nom será “Juan”, pero como la cadena aún no ha
finalizado y hay más tokens para leer la siguiente llamada a next() (en el ejemplo
nextInt()) intentará devolver el siguiente token (en el ejemplo será la cadena “Perez”),
dado que el contenido de este no puede ser convertido a int se producirá una
excepción de tipo InputMismatchException.
Para evitar este problema, deberá asignarse como delimitador de token el salto de
línea, de esta forma la primera llamada a next() devolverá todo el texto introducido
hasta la pulsación de “enter”. El establecimiento de un nuevo delimitador se lleva a
cabo mediante el método useDelimiter(). En el ejemplo habría que añadir, después de
la creación del objeto Scanner, la instrucción:
sc.useDelimiter(“\n”);
Pág. 23
Programación en Java
Además de hacer más sencilla la tarea de lectura de datos de usuario a través del
teclado, la utilización de Scanner permite simplificar el proceso de recuperación de
datos de un archivo externo mediante el recorrido secuencial del mismo.
siendo source un objeto de la clase File asociado al fichero que contiene los datos a
leer y sc la variable que almacenará la referencia al objeto Scanner. Un objeto File es
una representación abstracta de un fichero o archivo del disco, para su creación
podemos recurrir a la expresión:
import java.io.*;
import java.util.Scanner;
public class tutorial {
// La creación de un objeto File requiere declarar o capturar la excepción
// java.io.FileNotFoundException
public static void main(String args[]) throws FileNotFoundException {
int suma = 0;
File f = new File("datos.txt");
Scanner sc = new Scanner(f);
sc.useDelimiter(",");
// mientras haya tokens numéricos que leer
// recupera el siguiente token como un int
while (sc.hasNextInt()) {
suma += sc.nextInt();
}
System.out.println("La suma total es: " + suma);
}
}
3.5 COLECCIONES
Pág. 24
Programación en Java
de los arrays, las colecciones son dinámicas, en el sentido de que no tienen un tamaño
fijo y permiten añadir y eliminar objetos en tiempo de ejecución.
Creación de un ArrayList
Una vez creado el objeto, podemos hacer uso de los métodos de la clase ArrayList para
realizar las operaciones habituales con una colección.
Pág. 25
Programación en Java
referencia al objeto devuelto en una variable de su tipo será necesario realizar una
conversión explicita:
La clase Hashtbale representa un tipo de colección basada en claves, donde los objetos
almacenados en la misma no tienen asociado un índice numérico basado en su
Pág. 26
Programación en Java
posición, sino una clave que lo identifica de forma única dentro de la colección. Una
clave puede ser cualquier tipo de objeto.
Creación de un Hashtable
Los principales métodos expuestos por la clase Hashtable para manipular la colección
son los siguientes:
Mostrará en pantalla
Pág. 27
Programación en Java
Con estos dos métodos, utilizando un bucle while, se puede acceder a todos los
elementos de la colección asociada al Enumeration.
3.5.3 GENÉRICOS
Pág. 28
Programación en Java
Una vez creado el objeto de colección, la variable podrá utilizarse normalmente para
realizar las operaciones habituales, con la ventaja de que ya no será necesario realizar
conversiones explicitas para recuperar los objetos:
Pág. 29
Programación en Java
Pág. 30
Programación en Java
A lo largo de este capítulo se estudiarán estos conceptos clave en los que se basa la
POO y su aplicación en Java. Se trata de conceptos cruciales, no sólo por su aplicación
directa en el desarrollo de las aplicaciones, sino también porque están presentes en
todo el conjunto de clases de Java.
La organización de las clases en paquetes facilita el uso de las mismas desde otras
clases. Por ello, resulta también recomendable utilizar esta técnica en el desarrollo de
las clases propias.
package nombre_paquete;
Esta sentencia debe ser la primera instrucción del archivo fuente, incluso antes que
las sentencias import, y afectará a todas las clases existentes en el archivo.
Pág. 31
Programación en Java
4.3 ENCAPSULACIÓN
Como ya se ha mencionado, una clase está compuesta por métodos que determinan el
comportamiento de los objetos de la clase y de atributos que representan las
características de los objetos de la clase.
Los métodos que se quieren exponer al exterior llevan el modificador de acceso public,
mientras que los atributos suelen tener acceso privado, de modo que solamente
pueden ser accesibles desde el interior de la clase.
Imaginemos que tenemos que crear una clase que representa una figura geométrica,
por ejemplo, un rectángulo. Dicha clase podría proporcionar diversos métodos para
realizar cálculos sobre la figura, además de disponer de los atributos que la
caracterizarían, como pueden ser alto y ancho.
Pág. 32
Programación en Java
Al utilizar esta clase desde cualquier otro programa e intentar asignar valores a los
atributos, nada impediría al programador que va a realizar esa tarea hacer algo como
esto:
A este hecho se le conoce también como corrupción de datos, y una forma de evitarlo
sería proteger los atributos del acceso directo desde el exterior mediante la
encapsulación, forzando a que el acceso a dichos atributos se realice siempre de forma
controlada a través de métodos de acceso.
Pág. 33
Programación en Java
r.setAncho(6);
r.setAlto(-5);
provocaría que la variable alto permaneciese invariable, impidiendo que pueda tomar
un valor negativo.
Obsérvese el uso de la palabra this para acceder a los atributos. La palabra reservada
this se utiliza en el interior de una clase para invocar a los métodos y atributos del
propio objeto:
this.metodo();
Sin embargo, su uso es redundante ya que los métodos y atributos propios pueden ser
llamados directamente por su nombre sin necesidad de utilizar this. Tan sólo será
necesario utilizar esta palabra reservada para invocar a un miembro del propio objeto
cuando nos encontremos una variable local y un atributo con el mismo nombre, en
cuyo caso, la manera de referirse al atributo será:
this.variable_local;
Si una vez creada la clase queremos cambiar el criterio sobre los posibles valores que
pueden tomar los atributos, podríamos modificar el código de los métodos de acceso y
distribuir la nueva versión de la clase, sin que los programadores que la utilizan tengan
que cambiar una sola línea de sus programas.
Por ejemplo, si se decide que el valor del atributo alto no puede ser inferior a 2, el
método setAlto() quedaría:
Pág. 34
Programación en Java
En muchos tipos de aplicaciones, puede resultar útil la creación de clases cuya única
finalidad sea la encapsulación de una serie de datos dentro de la misma, datos que
están asociados a una entidad (información de un empleado, de un libro, de un
producto, etc.) y que conviene tenerlos agrupados en un mismo objeto de cara a
facilitar su tratamiento.
A estas clases se les conoce como clases de encapsulación y, aparte de los campos y
constructores, solamente dispone de métodos set/get.
Otra de las ventajas que ofrece la POO es poder tener en una misma clase varios
métodos con el mismo nombre, a esto se le conoce como sobrecarga de métodos.
4.5 CONSTRUCTORES
Para entender la utilidad de los constructores, se utilizará como ejemplo la clase Punto.
Esta clase representa puntos geométricos caracterizados por dos coordenadas, x e y,
donde a modo de ejemplo se ha añadido un método llamado dibujar() y cuya única
función es mostrar en pantalla los valores de las coordenadas del punto. La
implementación de esta clase seria la siguiente:
Pág. 35
Programación en Java
return x;
}
public int getY() {
return y;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void dibujar() {
System.out.println(“Las coordenadas son ”+x+”, ”+y);
}
}
Como se ve, cada vez que se quiere crear un punto para hacer posteriormente una
operación con el mismo, es necesario primero llamar explícitamente a los métodos
setX() y setY() para asignar valores a las coordenadas del punto. Esto, además de
resultar pesado en caso de tener muchos atributos, puede dar lugar a olvidos y, por lo
tanto, a que ciertos atributos queden sin ser inicializados de manera explícita. Para
evitar estos problemas, tenemos los constructores.
A la hora de crear un constructor, hay que tener en cuenta las siguientes reglas:
Pág. 36
Programación en Java
this.y = y;
}
public Punto(int v) {
this.x = v;
this.y = v;
}
//resto del código de la clase
}
Ahora podemos hacer uso de estos constructores para hacer más cómoda la creación
de un objeto Punto, pasándole las coordenadas del punto al constructor en la
instrucción de creación del objeto:
Dado que el constructor almacena los números en los datos miembro x e y, la llamada
a dibujar() en la instrucción anterior provocará que se muestre en pantalla el texto:
Si las dos coordenadas fuesen a tomar el mismo valor, podríamos haber optado por el
segundo constructor:
Según la cuarta de las reglas que se han dado para la creación de constructores, toda
clase debe tener al menos un constructor, pero, ¿qué sucede si se crea una clase sin
constructores?
public Nombre_clase {
:
}
Es decir, será un constructor sin parámetros y sin código, pero necesario para que la
clase pueda compilar. Hay que tener en cuenta que este constructor por defecto será
añadido por el compilador Java solamente si la clase carece de constructores.
Cuando una clase cuenta con constructores, no se añadirá ningún constructor de forma
implícita. En este caso, si la clase no dispone de un constructor sin parámetros, como
sucede con la última versión de la clase Punto, una instrucción como esta:
Pág. 37
Programación en Java
4.6 HERENCIA
Concepto de herencia
Ventajas de la herencia
Pág. 38
Programación en Java
Por ejemplo, dada la clase Punto podríamos crear a través de la herencia una
nueva clase, llamada PuntoColor, que adquiriese las coordenadas x e y como
atributos propios y además pudiera añadir algunos adicionales, como el color.
Antes de ver como se crean las clases en Java utilizando la herencia, se definirá la
nomenclatura básica y se conocerán ciertas reglas a tener en cuenta sobre la herencia.
En POO, a la clase que va a ser heredada se le llama superclase o clase base, mientras
que a la hereda se la conoce como subclase o clase derivada. Gráficamente, la
herencia entre dos clases se representa con una flecha saliendo de la subclase hacia la
superclase (figura 4.1).
Hay reglas básicas sobre la herencia en Java que hay que tener presentes y quedan
ilustradas en la figura 4.2:
Pág. 39
Programación en Java
La herencia entre dos clases establece una relación entre las mismas de tipo "es un",
lo que significa que un objeto de una subclase también "es un" objeto de la superclase.
Por ejemplo, Vehículo es la superclase de Coche, por lo que un coche "es un" vehículo.
De la misma forma, Animal es la superclase de Mamífero y esta a su vez es la
superclase de León, esto nos lleva a que un León "es un" mamífero y "es un" animal.
Así pues, una forma de saber si una relación de herencia entre dos clases está bien
planteada es comprobar si se cumple la relación "es un" entre la subclase y la
superclase. Por ejemplo, para crear una clase Línea podríamos intentar heredar Punto
pensando que es una clase de esta, sin embargo, ¿una línea "es un" punto?, la
respuesta es NO, por lo que la herencia está mal planteada.
A la hora de definir una clase que va a heredar otra clase, se utiliza la palabra extends,
seguida del nombre de la superclase en la cabecera de la declaración:
La nueva clase, podrá incluir atributos y métodos propios para completar su función.
Por ejemplo, la clase PuntoColor hereda Punto para adquirir las coordenadas x e y, y
además incluiría el atributo color:
Todas las clases de Java heredan alguna clase. En caso de que no se especifique
mediante extends la clase que se va a heredar, implícitamente se heredará Object.
Esta clase se encuentra en el paquete java.lang y proporciona el soporte básico para
cualquier clase Java. Así pues, la definición de una clase que no herede explícitamente
a otra, equivale a:
La clase Object es, por tanto, la superclase de todas las clases de Java. Por otro lado,
para evitar que una clase sea heredada, deberá ser declarada con el modificador final
delante de class.
Pág. 40
Programación en Java
Aunque una subclase hereda todos los miembros de la superclase, incluido los
privados, no tiene acceso directo a estos, puesto que private significa privado a la
clase o lo que es lo mismo, solamente accesible desde el interior de la clase.
Así pues, ¿cómo se puede acceder desde el interior de la subclase a los atributos
privados de la superclase? En el caso de que la superclase disponga de métodos
set/get, se pueden utilizar directamente desde la subclase al ser heredados por esta.
No obstante, de cara a la inicialización de atributos, los constructores representan la
mejor opción, a continuación veremos cómo podemos hacer uso de los constructores
de la superclase desde la subclase.
Como norma universal, cada vez que en Java se crea un objeto de una clase, antes de
ejecutarse el constructor de dicha clase se ejecutará primero el de la superclase.
Según esto, tras la ejecución del método main() del siguiente programa:
class Primera {
public Primera() {
System.out. println(“Constructor de la superclase”);
}
}
class Segunda extends Primera{
public Segunda() {
System.out.println(“Constructor de la subclase”);
}
}
public class Principal {
public static void main(String[] args) {
Segunda s = new Segunda();
}
}
Constructor de la superclase
Constructor de la subclase
super();
Pág. 41
Programación en Java
hereden a ninguna otra también incluirán esta instrucción como primera línea de
código en sus constructores, puesto que, toda clase que no herede explícitamente otra
clase, heredara implícitamente la clase Object.
super(argumentos);
Los argumentos son los parámetros que necesita el constructor de la superclase que se
desea invocar. Esto permite, entre otras cosas, que el constructor de la subclase pueda
pasarle al constructor de la superclase los datos necesarios para la inicialización de los
atributos privados y que no son accesibles desde la subclase, tal y como se muestra en
el siguiente ejemplo de la clase PuntoColor:
Pág. 42
Programación en Java
Un miembro de una clase (atributo o método) definido como protected, será accesible
desde cualquier subclase de esta, independientemente de los paquetes en que estas
clases se encuentren.
package basico;
public class Persona{
private String fecha_nacimiento;
public Persona(String f){
fecha_nacimiento = f;
}
protected int getEdad(){
//implementacion del metodo
}
}
Una subclase de Persona definida en otro paquete podrá hacer uso del método
getEdad():
package varios;
import basico.Persona;
public class Empleado extends Persona {
private int edad;
private long nseg;
public Empleado(String fecha_nacimiento, long nseg) {
super(fecha_nacimiento);
this.nseg = nseg;
}
public void muestraDatos() {
//Acceso al metodo protegido
System.out.println("Edad: " + this.getEdad());
System.out.println("NISS: " + nseg);
}
}
Es importante subrayar que las subclases acceden a los miembros protegidos a través
de la herencia, no pudiendo utilizar una referencia a un objeto de la superclase para
acceder al miembro protegido.
Cuando una clase hereda a otra, el comportamiento de los métodos que hereda no
siempre se ajusta a las necesidades de la nueva clase. Por ejemplo, el método
dibujar() que hereda la clase PuntoColor no se ajusta del todo a sus necesidades, ya
que no tiene en cuenta el color a la hora de dibujar el punto.
En estos casos, la subclase puede optar por volver a reescribir el método heredado, es
lo que se conoce como sobrescritura de un método.
Pág. 43
Programación en Java
A la hora de sobrescribir un método hay que tener en cuenta las siguientes reglas:
class Primera {
public void imprimir(String mensaje) {
System.out.println(mensaje);
}
}
class Segunda extends Primera {
//se mantiene el formato original de imprimir()
public void imprimir(String mensaje) {
//nuevo código de imprimir()
System.out.println("El mensaje es: " + mensaje);
}
}
super.nombre_metodo(argumentos);
Por otro lado, si se quiere evitar que un método pueda ser sobrescrito deberá
declararse con el modificador final:
4.7.1 DEFINICIÓN
Una clase abstracta es una clase en la que alguno de sus métodos está declarado pero
no está definido, es decir, se especifica su nombre, parámetros y tipo de devolución
Pág. 44
Programación en Java
pero no incluye código. A este tipo de métodos se les conoce como métodos
abstractos.
Sobre la creación y utilización de clases abstractas hay que tener en cuenta los
siguientes aspectos:
Para entender mejor este concepto, se presenta el ejemplo de la clase Figura, que
como se puede suponer, debe disponer de un método para poder calcular el área, sin
embargo, en la definición de la clase no es posible codificar este método al depender el
cálculo de cada tipo concreto de Figura. En este caso se define el método como
abstracto en la clase Figura, dejando a las subclases de esta (Triangulo y Circulo) los
detalles de implementación.
//Clase Figura
public abstract class Figura {
private String color;
public Figura(String c) {
color = c;
}
public String getColor() {
return color;
}
public abstract double area();
}
//Clase Triangulo
Pág. 45
Programación en Java
4.8 POLIMORFISMO
Figura f;
Pág. 46
Programación en Java
f = new Triangulo(…);
A partir de aquí, puede utilizarse esta variable para invocar a aquellos métodos del
objeto que también estén definidos o declarados en la superclase, pero no a aquellos
que sólo existan en la clase a la que pertenece el objeto.
Por ejemplo, puede utilizarse la variable f para invocar a los métodos area() y
getColor() del objeto Triangulo, pero no podrá llamar a getBase() y getAltura().
Pero, ¿qué utilidad puede tener asignar un objeto a una variable de su superclase para
llamar a sus métodos, cuando eso mismo podemos hacerlo si le asignamos a una
variable de su propia clase?
Para responder esta pregunta, volvamos al ejemplo anterior de las clases Figura,
Triangulo y Circulo. Según lo comentado en el apartado anterior, sería posible
almacenar en una variable Figura cualquier objeto de sus subclases, es decir, objetos
Triangulo o Circulo:
Figura f;
f = new Triangulo(…);
f.area(); //Método área de Triangulo
f = new Circulo(…);
f.area(); //Método área de Circulo
Pág. 47
Programación en Java
Las clases Triangulo y Circulo, al heredar Figura, dispondrían también de este método
y por tanto estarán obligados a sobrescribirlo. En el caso de Circulo, por ejemplo, si se
estuviera trabajando con versiones anteriores de Java la sobrescritura del método
podría ser:
Pág. 48
Programación en Java
De esta manera, suponiendo que cir es una variable que contiene una referencia a un
objeto Circulo en un determinado programa, para obtener una copia de este objeto
utilizando el método anterior se debería escribir:
4.9 INTERFACES
Por ejemplo, para gestionar eventos en una aplicación basada en entorno gráfico, las
clases donde se capturan estos eventos deben codificar una serie de métodos que se
ejecutarán al producirse estos eventos. Cada tipo de evento tendra su propio método
de respuesta, cuyo formato estará definido en una interfaz. Así, aquellas clases que
deseen responder a un determinado evento, deberán implementar el método de
respuesta de acuerdo al formato definido en la interfaz.
Hay que insistir en el hecho de que una interfaz no establece lo que un método tiene
que hacer y cómo hacerlo, sino el formato que este debe tener.
Pág. 49
Programación en Java
Al igual que las clases, las interfaces se definen en archivos .java y, como sucede
aquellas, si la interfaz utiliza el modificador de acceso public, el nombre de la interfaz
deberá coincidir con el del fichero Java donde se almacena. Como resultado de la
compilación de una interfaz, se genera un archivo .class.
A la hora de crear una interfaz hay que tener en cuenta las siguientes consideraciones:
Todos los métodos definidos en una interfaz son públicos y abstractos, aunque
no se indique explícitamente.
En una interfaz es posible definir constantes.
Una interfaz no es una clase.
En la definición de una clase, se utiliza la palabra implements para indicar que interfaz
se ha de implementar:
Por ejemplo,
Al igual que sucede al heredar una clase abstracta, cuando una clase
implementa una interfaz, esta obligada a definir el código (implementar) de
Pág. 50
Programación en Java
todos los métodos existentes en la misma. De no ser así, la clase deberá ser
declarada como abstracta.
Una clase puede implementar más de una interfaz, en cuyo caso deberá
implementar los métodos existentes en todas las interfaces. El formato utilizado
en la definición será:
Una clase puede heredar otra clase e implementar al mismo tiempo una o
varias interfaces. La sintaxis para heredar una clase e implementar interfaces
es:
Como ya ocurriera con las clases abstractas, el principal objetivo que persiguen las
interfaces con la definición de un formato común de métodos es el polimorfismo.
Una variable de tipo interfaz puede almacenar cualquier objeto de las clases que la
implementan, pudiendo utilizar esta variable para invocar a los métodos del objeto que
han sido declarados en la interfaz e implementados en la clase:
Pág. 51
Programación en Java
5 EXCEPCIONES
Ya hemos tenido algún contacto con las excepciones en algunos de los ejemplos
aparecidos en capítulos anteriores. Por ejemplo, cuando vimos el método readLine() de
la clase BufferedReader para la lectura de cadenas por teclado, tuvimos que declarar la
excepción IOException en el método main() para poder compilar el programa.
Una excepción es una situación anómala que puede producirse durante la ejecución de
un programa, como puede decir un intento de división entera entre cero, un acceso a
posiciones de un array fuera de los límites del mismo o un fallo durante la lectura de
datos en la entrada / salida de los mismos.
Cada tipo de excepción está representada por una subclase de Exception mientras que
los errores son subclases de Error. Ambas clases, Exception y Error, son subclases de
Throwable.
En la figura 5.1 se muerta la jerarquía de clases con algunas de las excepciones más
habituales que podemos encontrar en un programa.
Pág. 52
Programación en Java
Desde el punto de vista del tratamiento de una excepción dentro de un programa, hay
que tener en cuenta que todas estas clases de excepción se dividen en dos grandes
grupos:
Excepciones marcadas
Excepciones no marcadas
Pág. 53
Programación en Java
Los métodos que pueden provocar excepciones marcadas, deben declarar estas en la
definición del método.
Así, siempre que vayamos a utilizar algún método que tenga declaradas excepciones,
hemos de tener presente que estamos obligados a capturar dichas excepciones.
A diferencia de las excepciones, los errores representan fallos de sistema de los cuales
el programa no se puede recuperar. Esto implica que no es obligatorio tratar un error
en una aplicación Java, de hecho, aunque se pueda capturar al igual que las
excepciones con los mecanismos que vamos a ver a continuación, lo recomendable es
no hacerlo.
Pág. 54
Programación en Java
Las instrucciones try, catch y finally, proporcionan una forma elegante y estructurada
de capturar excepciones dentro de un programa Java, evitando la utilización de
instrucciones de control que dificultarían la lectura del código y lo harían más propenso
a errores.
try {
//instrucciones donde se pueden producir excepciones
} catch (tipoExcepcion1 arg) {
//tratamiento excepcion1
} catch (tipoExcepcion2 arg) {
//tratamiento excepcion2
}
:
finally {
//instrucciones de última ejecución
}
Try
El bloque try delimita aquella o aquellas instrucciones donde se puede producir una
excepción. Cuando esto sucede, el control del programa se transfiere al bloque catch
definido para el tipo de excepción que se ha producido, pasándole como parámetro la
excepción lanzada. Opcionalmente, se puede disponer de un bloque finally en el que
definir un grupo de instrucciones de obligada ejecución.
Catch
Un bloque catch define las instrucciones que deberían ejecutarse en caso de que se
produzca un determinado tipo de excepción.
Pág. 55
Programación en Java
Finally
Si se produce una excepción en try, el bloque finally se ejecutará después del catch
para tratamiento de la excepción. En caso de que no hubiese ningún catch para el
tratamiento de la excepción producida, el bloque finally se ejecutará antes de propagar
la excepción.
En el caso de las excepciones marcadas, hemos visto cómo estas deben ser capturadas
obligatoriamente en un programa. Sin embargo, en el caso de que no se tenga
previsto ninguna acción particular para el tratamiento de una determinada excepción
de este tipo, es posible propagar la excepción sin necesidad de capturarla, dejando que
sean otras partes del programa las encargadas de definir las acciones para su
tratamiento.
Para propagar una excepción sin capturarla, basta con declararla en la cabecera del
método en cuyo interior puede producirse (figura 5.2).
Pág. 56
Programación en Java
En determinados casos puede ser útil generar y lanzar una excepción desde un
determinado método. Esto puede utilizarse como un medio para enviar un aviso a otra
parte del programa, indicándole que algo está sucediendo y no es posible continuar
con la ejecución normal del método.
throw objeto_excepcion;
class Cajero {
public static void main(String[] args) {
Cuenta c = new Cuenta();
try {
c.ingresar(100);
c.extraer(20);
} catch(Exception e) {
System.out.println(“La cuenta no puede quedar en números rojos”);
}
}
Pág. 57
Programación en Java
}
class Cuenta {
double saldo;
public Cuenta() {
saldo = 0;
}
public void ingresar(double c) {
saldo += c;
}
public void extraer (double c) throws Exception {
if(saldo < c) {
//creación y lanzamiento de la excepción
throw new Exception();
} else {
saldo -= c;
}
}
public double getSaldo() {
return saldo;
}
}
Como se puede apreciar en el código del ejemplo, cuando se lanza una excepción
marcada desde un método esta debe ser declarada en la cabecera del método para
que se pueda propagar al punto de llamada del mismo.
catch (IOException e) {
throw e;
}
En este caso, a pesar de estar capturada por un catch y dado que vuelve a ser
lanzada, la excepción IOException también deberá declararse en la cabecera del
método.
Todas las clases de excepción heredan una serie de métodos de Throwable que pueden
ser utilizadas en el interior de los catch para completar las acciones de tratamiento de
la excepción.
Pág. 58
Programación en Java
Cuando un método necesita lanzar una excepción como forma de notificar una
situación anómala, puede suceder que las clases de excepción existentes no se
adecuen a las características de la situación que quiere notificar.
Por ejemplo, en el caso anterior de la cuenta bancaria no tendría mucho sentido lanzar
una excepción de tipo NullPointerException o IOException cuando se produce una
situación de saldo insuficiente.
En estos casos resulta más práctico definir una clase de excepción personalizada,
subclase de Exception, que se ajuste más a las características de la excepción que se
va a tratar.
El siguiente listado contiene una nueva versión del programa del cajero presentado
anteriormente. En este caso, se utiliza una clase de excepción personalizada,
SaldoInsuficienteException, para representar la situación de saldo negativo en la
cuenta:
class Cajero {
public static void main(String[] args) {
Cuenta c = new Cuenta();
try {
c.ingresar(100);
c.extraer(20);
} catch (SaldoInsuficienteException e) {
System.out.println(e.getMessage());
}
}
}
class Cuenta {
double saldo;
public Cuenta() {
Pág. 59
Programación en Java
saldo = 0;
}
public void ingresar(double c) {
saldo += c;
}
public void extraer(double c) throws SaldoInsuficienteException {
if(saldo < c) {
throw new SaldoInsuficienteException(“números rojos”);
} else {
saldo -= c;
}
}
public double getSaldo() {
return saldo;
}
}
Pág. 60
Programación en Java
6 ACCESO AL DISCO
Durante este capítulo veremos cómo almacenar información de forma permanente en
ficheros de disco, así como su posterior recuperación.
Java dispone de un gran número de clases en el paquete java.io específicamente
creadas para tratar con ficheros de diferentes tipos y manipular información en
diferentes formatos.
Durante este capítulo estudiaremos algunas de estas clases, centrando nuestro análisis
en el almacenamiento y recuperación de tipos básicos y objetos Java en ficheros de
texto.
La clase File no se utiliza para transferir información entre la aplicación y el disco, sino
para obtener información sobre los ficheros y directorios de este.
La creación del objeto no implica que exista el fichero o directorio indicado en la ruta.
Pág. 61
Programación en Java
La siguiente instrucción crearía un objeto FileReader a partir del objeto File de ejemplo
creado en el apartado anterior:
Ya se vio anteriormente cómo utilizar esta clase para leer cadenas de caracteres desde el
teclado. Para leer de un fichero el procedimiento es el mismo, sólo que en este caso el objeto
Reader que hay que proporcionar al constructor será el objeto FileReader asociado al fichero:
Una vez creado el objeto, puede utilizarse el método readLine() para leer líneas del fichero de
forma similar a como se leía del teclado. En el caso de un fichero, y dado que éste puede estar
formado por más de una línea, será necesario utilizar un bucle while para recuperar todas las
líneas de texto del mismo de forma secuencial.
import java.io.*;
public class tutorial {
public static void main(String[] args) throws IOException {
File f = new File(“datos.txt”);
if (f.exists()) {
FileReader fr = new FileReader(“datos.txt”);
BufferedReader br = new BufferedReader(fr);
String cad;
while ((cad = br.readLine()) != null) {
System.out.println(cad);
}
} else {
System.out.println(“El fichero no existe.”);
}
}
}
Pág. 62
Programación en Java
Según se desprende del programa anterior, el método readLine() apunta a la siguiente línea de
texto después de recuperar la línea actual. Cuando no existían más líneas para leer, la llamada a
readLine() devolverá null.
Si lo que se quiere es leer carácter a carácter en vez de línea a línea, deberíamos utilizar el
método read() en lugar de readLine(). El método read() devuelve un entero que representa el
código Unicode del carácter leído, siendo -1 si no hay carácter para leer.
Donde el parámetro append permite indicar si los datos que se van a escribir se añadirán a los
ya existentes (true) o sobrescribirán a estos (false).
Una vez creado el objeto PrintWriter podemos utilizar los métodos print(), println() y
printf() para escribir en el fichero.
Pág. 63
Programación en Java
import java.io.*;
public class tutorial {
public static void main(String[] args) throws IOException {
FileOutputStream fw = new FileOutputStream(“datos.mbd”, false);
DataOutputStream ds = new DataOutputStream(fw);
int[] m = {5, 10, 3, 6};
for (int i = 0; i < m.length; i++) {
ds.writeInt(m[i]);
}
ds.close();
}
}
Pág. 64
Programación en Java
Esta clase dispone de los métodos readXxx() para recuperar los datos almacenados en
el tipo indicado, siendo xxx el nombre del tipo primitivo.
El siguiente programa recupera los datos almacenados en el fichero del programa
anterior y los muestra por pantalla:
import java.io.*;
public class tutorial {
public static void main(String[] args) throws IOException {
DataInputStream ds = new DataInputStream(new
FileInputStream(“datos.mbd”));
try {
for (;;){
System.out.println(ds.readInt());
}
} catch(EOFException e){}
}
}
En esta última parte veremos también cómo intercambiar cualquier objeto Java entre
una aplicación y el disco.
El siguiente listado corresponde a una clase Persona de tipo JavaBean, cuyos objetos
son el nombre y la edad de una persona. Estos objetos podrán ser transferidos a disco
pues Persona implementa la interfaz Serializable:
import java.io.Serializable;
public class Persona implements Serializable {
String name;
int edad;
public Persona(String n, int e) {
name = n;
Pág. 65
Programación en Java
edad = e;
}
public void setName(String n) {
name = n;
}
public String getName() {
return name;
}
public void setEdad(int e) {
edad = e;
}
public int getEdad() {
return edad;
}
}
import java.io.*;
public class Almacenamiento {
public static void main(String[] args) throws IOException {
FileOutputStream fs = new FileOutputStream("datos.obj");
ObjectOutputStream os = new ObjectOutputStream(fs);
os.writeObject(new Persona("Paco", 40));
os.close();
}
}
Pág. 66
Programación en Java
Durante este proceso, los datos miembro no serializables (aquellos que han sido
heredados de una clase no serializable) serán inicializados utilizando el constructor por
defecto de la clase. Por el contrario, los datos miembro de la clase del objeto
serializado serán restaurados con los valores almacenados.
Pág. 67
Programación en Java
Para tratar con bases de datos existen unos estándares que facilitan a las aplicaciones
informáticos manipular la información contenida en ellas. En Java disponemos de un
API especial basado en estos estándares que permite desarrollar aplicaciones Java para
acceder a bases de datos relacionales, se trata del API JDBC.
Durante este capítulo analizaremos este API, sin duda alguna, uno de los más
importantes que incluye la plataforma J2SE.
La principal ventaja que ofrece este sistema es que la aplicación se independiza del
tipo de base de datos utilizado para almacenar la información. En otras palabras, no
hay que escribir un programa para acceder a Oracle, otro para Sybase, etc., dado que
el API JDBC utiliza una serie de clases e interfaces genéricas que actúan sobre el
driver, no sobre la base de datos.
Tan sólo es necesario disponer de un driver específico para el tipo de base de datos
con el que se va a trabajar.
Pág. 68
Programación en Java
Capa de aplicación. Es la parte del driver que interactúa con la aplicación, todos
los driver JDBC, independientemente del tipo de base de datos para la que se
hayan diseñado, proporcionan la misma interfaz de aplicación.
Capa de base de datos. Esta capa interactúa con la base de datos, por lo que es
específica para cada tipo de base de datos.
Driver nativo
Driver intermedio
Fueron los primeros tipos de driver JDBC que se lanzaron al mercado, utilizan como
intermediario un driver ODBC (figura 7.3). Dada la gran expansión que en aquellos
momentos tenía la tecnología ODBC y el amplio número de drivers ODBC existentes, el
driver puente JDBC – ODBC puede utilizarse para acceder a la mayoría de los tipos de
bases de datos que hay en el mercado.
Pág. 69
Programación en Java
Figura 7.3 Acceso a la base de datos a través del driver intermediario ODBC.
La principal desventaja que tiene este driver es que es necesario configurar un DSN
(Data Source Name) utilizado por ODBC, en el equipo donde se va a ejecutar la
aplicación.
Driver nativo
El driver nativo convierte las llamadas JDBC en llamadas al API nativo del gestor de
base de datos (figura 7.4).
Driver intermedio
Este tipo de driver convierte las llamadas JDBC en un protocolo específico del
middleware, a su vez, el servidor del middleware se encarga de transformar estas
llamadas en el API nativo de la base de datos, proporcionando conectividad para
diferentes tipos de bases de datos (figura 7.5).
Pág. 70
Programación en Java
El lenguaje SQL surge ante la necesidad de disponer de un mecanismo para operar con
la información almacenada en bases de datos relacionales de diferentes fabricantes.
7.3.1 CONSULTAS
Pág. 71
Programación en Java
Una consulta es cualquier expresión en SQL que defina una operación a realizar sobre
la base de datos. Una consulta está compuesta por los siguientes elementos:
Las consultas SQL se expresan mediante sentencias de texto (sentencias SQL), que
pueden ser incluidas dentro de un programa Java como parámetro de alguno de los
métodos de las clases/interfaces del API JDBC, tal y como veremos en el siguiente
apartado.
Estas son las sentencias que se utilizan en las aplicaciones que acceden a bases de
datos, por tanto, vamos a comentar más detenidamente el funcionamiento de cada
una de ellas.
Sentencia SELECT
Pág. 72
Programación en Java
Las clausulas WHERE y ORDER BY son opcionales, por lo que en caso de que no se
utilicen se devolverán todos los registros de la tabla en el orden definido en el interior
de la misma.
Tanto en este ejemplo como en los demás que irán apareciendo. Cada cláusula se
escribe en una línea diferente a efectos de una mayor claridad. Además, SQL no es
case sensitive por lo que las cláusulas pueden escribirse indiferentemente en
mayúsculas o minúsculas.
Condiciones de la selección
Ordenación de registros
La cláusula ORDER BY determina cómo se van a ordenar los registros según la forma:
Pág. 73
Programación en Java
Para obtener datos incluidos en distintas tablas basta con indicar los nombres de éstas
en la cláusula FROM, separadas por comas. Si alguno de los campos seleccionados se
encuentra en más de una tabla , hay que preceder a su nombre el de la tabla en la que
se encuentra, separado por un punto.
Cuando se realiza una consulta sobre varias tablas, se debe añadir una condición en la
cláusula WHERE que ligue los datos de ambas tablas, de lo contrario la consulta
devolverá el producto cartesiano de las filas de ambas tablas. Para poder ligar dos
tablas éstas deben tener un campo común, la unión entre ellas se lleva a cabo
añadiendo la siguiente expresión en la cláusula WHERE:
Operadores
Además de los operadores simples (<, >, =, …) una cláusula WHERE puede incluir
otros tipos de operadores:
Pág. 74
Programación en Java
_ carácter no nulo
Sentencia INSERT
Permite añadir una o más filas a una tabla. Puede utilizarse tanto para añadir una
única fila como para copiar en una tabla un subconjunto de registros proveniente de
otra.
Sentencia DELETE
Pág. 75
Programación en Java
Sentencia UPDATE
UPDATE tabla
SET campo1 = expr1, campo2 = expr2, …
WHERE condiciones
Mediante la cláusula SET se indican los valores que se van a asignar a los campos.
Las clases e interfaces que forman parte de este API se encuentran en el paquete
java.sql. La tabla 7.1 contiene los elementos más importantes de este paquete.
En toda aplicación que utilice JDBC para acceder a una base de datos, se distinguen
cuatro fases o pasos a realizar:
Pág. 76
Programación en Java
2. Ejecución de consultas
3. Manipulación de registros
4. Cierre de la conexión
Cargar el driver
Creación de la conexión
Mediante esta acción, se prepara el driver JDBC para que pueda ser utilizado. Esto se
realiza mediante el método estático forName() de la clase java.lang.Class, cuyo
formato es:
Class.forName(“sun.ldbc.odbc.JdbcObdcDriver”);
Creación de la conexión
Una vez cargado el driver se debe proceder a la conexión con la base de datos,
operación que se lleva a cabo con el método estático getConnection() de la clase
DriverManager del API JDBC. El formato de este método es:
jdbc:subprotocolo:base_datos
Pág. 77
Programación en Java
La siguiente instrucción utiliza el driver JDBC – ODBC para establecer una conexión con
una base de datos, cuyo nombre de fuente de datos es “empresa”:
Connection cn = DriveManager.getConnection(“jdbc:odbc:empresa”);
Class.forName(“Oracle.jdbc.driver.OracleDriver”);
Connection cn = DriverManager.getConnection(
“jdbc:Oracle:thin:@miservidor:1521:info”, “scott”, “tiger”);
Debe tenerse en cuenta que, al igual que sucede con la mayoría de los métodos del
API JDBC, getConnection() puede provocar una excepción del tipo SQLException que
habrá que capturar.
Una vez establecida la conexión con la base de datos, se empleará esta para enviar
consultas SQL a la base de datos.
Statement st = cn.createStatement();
La interfaz Statement proporciona diversos métodos para enviar una consulta SQL a
través de la conexión. Los más importantes son:
Pág. 78
Programación en Java
cn.close();
A fin de aclarar todo lo visto hasta ahora, el siguiente listado representa el método
main() de una clase en el que se reflejan todos los pasos para realizar la inserción de
un registro en una base de datos. La información es solicitada al usuario a través del
teclado:
import java.sql.*;
import java.io.*;
public class tutorial {
public static void main(String[] args) throws IOException {
BufferedReader bf = new BufferedReader(new
InputStreamReader(System.in));
String pwd = bf.readLine();
String user = bf.readLine();
String dir = bf.readLine();
String tel = bf.readLine();
Pág. 79
Programación en Java
try {
Connection cn;
Statement st;
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
cn = DriverManager.getConnection("jdbc:odbc:tienda");
st = cn.createStatement();
String tsql = "Insert into clientes values ('" + pwd + "','" +
user + "','" + dir + "','" + tel + "')";
st.execute(tsql);
cn.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
System.out.println("estado: " + e.getSQLState());
System.out.println("código de error: " + e.getErrorCode());
e.printStackTrace();
}
}
}
La interfaz ResultSet del API SQL, proporciona métodos para desplazarse por el
conjunto de registros afectados por la consulta y manipular sus contenidos.
Una vez obtenido el ResultSet, su cursor se encuentra situado en la posición que está
antes del primer registro.
Pág. 80
Programación en Java
Utilizando este método y una instrucción while, es posible recorrer todos los registros
desde el primero hasta el último:
while (rs.next()) {
// instrucciones
}
La interfaz ResultSet proporciona dos grupos de métodos para acceder a los campos
del registro actual, el que está siendo apuntado por el cursor (registro actual). Estos
métodos se ajustan a los formatos:
Donde xxx puede ser el nombre de cualquiera de los tipos básicos Java más Date,
String y Object, debiéndose utilizar aquel método que se corresponda con el tipo
almacenado en el campo.
import java.sql.*;
import java.io.*;
public class tutorial {
public static void main(String[] args) throws IOException {
BufferedReader bf = new BufferedReader(new
InputStreamReader(System.in));
System.out.println("Introduzca el usuario: ");
Pág. 81
Programación en Java
Cierre de un ResultSet
El método close() de la interfaz ResultSet permite cerrar el objeto y liberar los recursos
utilizados por éste, algo que resulta bastante adecuado para reducir el consumo de
recursos y mejorar así el rendimiento de las aplicaciones.
Pág. 82
Programación en Java
desea tener dos ResultSet abiertos al mismo tiempo, habrá que crearlos con dos
Statements diferentes.
Además de los datos en sí, el API SQL proporciona una interfaz, ResultSetMetaData,
que permite obtener información sobre las características de los datos referenciados
por un ResultSet.
Acceso a la información
Pág. 83
Programación en Java
PreparedStatement ps;
String tsql = “Update empleados set salario = ? where dni = ?”;
ps = cn.prepareStatement(tsql);
Como se puede apreciar, los valores de los campos se indican mediante “?” en la
instrucción para su posterior sustitución. Nótese, que la “?” hace referencia a un valor,
independientemente de su tipo, por ello, no es necesario encerrar este símbolo entre
comillas simples cuando se trate de valores de texto.
Asignación de parámetros
Una vez creada, las consulta está, como su nombre indica, preparada para ser
ejecutada tantas veces como se requiera. Pero antes, es necesario asignar valores a
los parámetros definidos en la instrucción.
Pudiendo ser xxx el nombre de cualquiera de los tipos básicos de Java más Date,
Object y String. Por otro lado, índice_parametro es la posición que ocupa el parámetro
dentro de la instrucción, siendo “1” la posición del primero.
ps.setInt(1,4000);
ps.setString(2, “50069368R”);
Ejecución de la consulta
Pág. 84
Programación en Java
ps.execute();
Como ya hemos visto, los ResultSet que hemos creado hasta el momento únicamente
permiten realizar desplazamientos hacia adelante y el acceso a los datos es de sólo
lectura.
Los ResultSet desplazables pueden utilizar, además de los estudiados, los siguientes
métodos de la interfaz ResultSet:
Pág. 85
Programación en Java
Pág. 86
Programación en Java
Todos los ejemplos que se han presentado hasta ahora utilizaban el teclado y la
consola como interfaces de entrada y salida.
Aunque esto puede resultar útil desde un punto de vista didáctico, en la práctica
ninguna aplicación utiliza estas interfaces para interaccionar con el usuario. Todas las
aplicaciones modernas, ya sean de escritorio o basadas en Web, utilizan un entorno
gráfico basado en ventanas, botones, listas desplegables, etc., para presentar y
solicitar datos al usuario.
AWT
Swing
Como se verá más adelante, Swing es una extensión de AWT, sus clases heredan
alguna de las clases de éste, proporcionando mayores capacidades de presentación y
la posibilidad de adaptarse a diferentes sistemas operativos. Por lo demás, la creación
de aplicaciones Swing sigue exactamente las mismas pautas que las AWT,
compartiendo incluso el mismo modelo para la gestión de eventos.
8.1 AWT
AWT son las siglas de Abstrac Windows Toolkit: conjunto de herramientas para la
construcción de interfaces gráficas multiplataforma.
A pesar de que los elementos proporcionados por AWT permiten crear aplicaciones que
se ejecuten en diferentes plataformas, estos están implementados utilizando
herramientas nativas de ésta, lo que proporciona una apariencia semejante a todas las
aplicaciones que se creen para esa plataforma pero también tiene la desventaja de que
una interfaz gráfica diseñada para una plataforma puede no visualizarse correctamente
en otra diferente.
Pág. 87
Programación en Java
8.1.2 CONTENEDORES
Lo primero que hay que hacer cuando se va a desarrollar una aplicación gráfica es
crear el contenedor que va a representar la vista de la aplicación. La clase Window del
Pág. 88
Programación en Java
paquete AWT proporciona el soporte básico para la creación de ventanas, sin embargo,
son las subclases de esta, Frame y Dialog, las que disponen de todas las características
necesarias para crear una ventana funcional completa.
Frame representa una ventana estándar, mientras que Dialog implementa las
características de un cuadro de dialogo.
Independientemente del tipo elegido, los pasos que hay que seguir para crear una
ventana son los siguientes:
import java.awt.Frame;
public class CreaVentana {
public static void main(String[] args) {
Frame f = new Frame("Primera ventana");
f.setBounds(100, 100, 400, 250);
f.setVisible(true);
}
}
Pág. 89
Programación en Java
Un detalle a mencionar sobre las ventanas AWT es que el botón de cierre de la ventana
se encuentra inhabilitado, más adelante veremos cómo se puede programar el cierre
de la ventana utilizando la captura y gestión de eventos.
El siguiente programa para la creación de una ventana ilustra esta nueva filosofía:
Pág. 90
Programación en Java
1. Creación del objeto. Lo primero será crear una instancia de la clase de control
que se quiere utilizar. Por ejemplo, si queremos crear un botón de pulsación
deberíamos instanciar la clase Button, utilizando alguno de los constructores
que se muestran a continuación:
Button(). Crea un botón sin etiquetar.
Button(String s). Crea un botón que muestra en su interior el texto
especificado en el argumento.
2. Definir tamaño y posición del control. Después de crear el control, es necesario
establecer el tamaño que va a tener el mismo y su posición dentro del
contenedor. Estos parámetros se pueden definir de manera absoluta, utilizando
los mismos métodos de la clase Component que empleamos para la colocación
de una ventana, o de forma relativa a través de los llamados Gestores de
Organización (Layouts). Todo contenedor tiene un Layout asociado para la
disposición relativa de controles en su interior, dicho Layout tendrá que ser
anulado si se opta por disponer los controles de forma manual. Para modificar o
anular el gestor de organización asociado a un contenedor, será necesario
utilizar el siguiente método heredado de la clase Container:
void setLayout(LayoutManager lm). Establece el gestor de organización
indicado, siendo LayoutManager la interfaz que implementa todo gestor
de organización. Para eliminar el gestor de organización existente se
invocará a este método con el valor null como argumento.
3. Añadir el objeto al contenedor. Para que el control se visualice en el interior del
contenedor, con el tamaño y posición especificados, es necesario agregarlo
explícitamente al mismo. Esta operación se lleva a cabo mediante el método
add() heredado de la clase Container:
void add(Component obj). Añade al contenedor el componente
especificado en el argumento.
import java.awt.*;
class Ventana extends Frame {
Button bt;
public Ventana(String titulo, int x, int y, int ancho, int alto) {
super(titulo);
this.setBounds(x, y, ancho, alto);
this.setLayout(null);
bt = new Button("Pulsar aquí");
bt.setBounds(50, 50, 100, 30);
this.add(bt);
this.setVisible(true);
Pág. 91
Programación en Java
}
}
public class CreaVentana {
public static void main(String[] args) {
Ventana v = new Ventana("Nueva con botón", 100, 100, 400,
250);
}
}
Las aplicaciones graficas se dice que son conducidas por eventos. Los eventos son
sucesos que pueden tener lugar sobre la interfaz gráfica de una aplicación, la mayor
parte de los cuales son provocados por alguna acción llevada a cabo por el usuario, tal
como la pulsación de un botón, la selección de un elemento de una lista o la activación
del botón de cierre de la ventana.
En Java, los métodos de respuesta a los diferentes eventos que pueden tener lugar en
una aplicación gráfica se encuentran definidos en unas interfaces, conocidas como
interfaces de escucha.
Pág. 92
Programación en Java
Otras interfaces de escucha, como FocusListener, contiene los métodos para la gestión
de los eventos del foco (llegada de foco y pérdida de foco), mientras que
ActionListener contiene el método para la gestión del evento de acción, el cual es
provocado por un suceso diferente en cada tipo de control (clic en Button, pulsación de
“Enter” en un TextComponent, etc.).
Pág. 93
Programación en Java
void addXxxListener(XxxListener l)
Pág. 94
Programación en Java
que responde a la pulsación del botón de cierre de la ventana. El código de esta clase
se muestra a continuación:
import java.awt.event.*;
public class GestionVentana implements WindowListener {
public void windowClosing(WindowEvent e) {
System.exit(0); //Finalización de la aplicación
}
public void windowOpened(WindowEvent e) { }
public void windowClosed(WindowEvent e) { }
public void windowIconified(WindowEvent e) { }
public void windowDeiconified(WindowEvent e) { }
public void windowActivated(WindowEvent e) { }
public void windowDeactivated(WindowEvent e) { }
}
import java.awt.*;
class Ventana extends Frame {
public Ventana(String titulo, int x, int y, int ancho, int alto) {
super(titulo);
this.setBounds(x, y, ancho, alto);
GestionVentana gv = new GestionVentana();
this.addWindowListener(gv);
this.setVisible(true);
}
}
Cada interfaz de escucha tiene asociada una clase de evento, estas clases
proporcionan una serie de métodos relacionados con el grupo o tipo de evento que
puede ser gestionado con la interfaz. Por ejemplo, la interfaz WindowListener tiene
asociada la clase WindowEvent, que proporciona, entre otros, métodos para obtener
información sobre el estado de la ventana antes y después del evento. O la clase
Pág. 95
Programación en Java
El problema que se presenta en esta situación es cómo poder obtener una referencia al
objeto ventana desde la clase de escucha. La solución la proporciona el objeto de la
clase evento, el cual proporciona el método getSource(), heredado de ActionEvent, que
devuelve una referencia al objeto fuente del evento, en este caso, la ventana:
import java.awt.event.*;
public class GestionVentana implements WindowListener {
public void windowClosing(WindowEvent e) {
//Obtiene el objeto donde se originó el evento
Ventana v = (Ventana) e.getSource();
v.dispose();
}
public void windowOpened(WindowEvent e) { }
public void windowClosed(WindowEvent e) { }
public void windowIconified(WindowEvent e) { }
public void windowDeiconified(WindowEvent e) { }
public void windowActivated(WindowEvent e) { }
public void windowDeactivated(WindowEvent e) { }
}
Pág. 96
Programación en Java
8.2.4 ADAPTADORES
Un adaptador es una clase que implementa una interfaz de escucha, dejando vacíos
(sin instrucciones) cada uno de los métodos de la interfaz. Cada interfaz de escucha
tiene su clase de adaptador asociada, siendo el nombre de ésta igual al de la interfaz,
sustituyendo la palabra “Listener” por “Adapter”. Por ejemplo, el adaptador de la
interfaz WindowListener será WindowAdapter, mientras que el de FocusListener será
FocusAdapter. Las clases adaptadoras del AWT se encuentran también en el paquete
java.awt.event.
import java.awt.event.*;
public class GestionVentana extends WindowAdapter {
//Sólo sobrescribe el método que interesa
public void windowClosing(WindowEvent e) {
Ventana v = (Ventana) e.getSource();
v.dispose();
}
}
Uno de los problemas más habituales con los que nos vamos a encontrar a la hora de
desarrollar una aplicación AWT o Swing, será acceder a los objetos de la interfaz
gráfica definidos en la clase contenedor, desde el interior de los métodos de la clase de
escucha.
Por ejemplo, imaginemos que queremos hacer un programa que recopile los datos
introducidos en una caja de texto y los añada a un control del tipo lista en el momento
en el que se presiona un botón de pulsación. Durante la codificación del método de
respuesta al evento, nos encontramos en la necesidad de tener que acceder a los
objetos caja de texto y lista definidos en la clase Ventana, pero, ¿cómo se accede
desde una clase a un objeto que está definido en otra clase?
Pág. 97
Programación en Java
Pero la que quizá resulte la solución más elegante de todas consiste en pasar el objeto
contenedor como parámetro del constructor de la clase de escucha, de modo que si la
clase contenedor utiliza variables de ámbito por defecto para referenciar a los controles
de la interfaz, cualquiera de los métodos de la clase de escucha podrá acceder a los
controles utilizando la expresión:
referencia_contenedor.variable_control
import java.awt.*;
public class Ventana extends Frame {
Button bt;
TextField tf;
Label lb;
public Ventana(String titulo, int x, int y, int ancho, int alto) {
super(titulo);
this.setBounds(x, y, ancho, alto);
this.setLayout(null);
bt = new Button("Mostrar texto");
bt.setBounds(150, 200, 100, 30);
tf = new TextField();
Pág. 98
Programación en Java
import java.awt.event.*;
public class GestionBoton implements ActionListener {
Ventana vent;
public GestionBoton(Ventana v) {
vent = v;
}
public void actionPerformed(ActionEvent e) {
//Recupera el contenido del TextField
String s = vent.tf.getText();
//Asigna el valor al control Label
vent.lb.setText(s);
}
}
Pág. 99
Programación en Java
Los gestores de organización o layouts son objetos que proporcionan una colocación y
tamaño automáticos de los controles dentro de un contenedor, siguiendo los criterios
definidos por la clase a la que pertenece un layout.
En todos los casos, los layouts establecen una disposición relativa de los controles
dentro del contenedor, así, si se modifica el tamaño de éste se reconfigurarán las
dimensiones de los controles para preservar la misma organización. Esto se ve
claramente en el ejemplo presentado en la figura 8.8, donde se puede observar como
los cuatro botones mantienen la misma distribución dentro de la ventana,
independientemente del tamaño de ésta.
Pág. 100
Programación en Java
objeto_contenedor.setLayout(objeto_layout);
Una vez realizada esta operación, los controles que se agreguen al contenedor se
dispondrán según las reglas definidas por el tipo de gestor establecido. El método
utilizado para agregar los controles será el ya conocido método add() de la clase
Container, aunque es posible que, dependiendo del gestor utilizado, haya que utilizar
la versión sobrecargada del método:
FlowLayout
Pág. 101
Programación en Java
import java.awt.*;
public class Ventana extends Frame {
public Ventana() {
//tamaño de la ventana
this.setBounds(10, 40, 350, 280);
//define el gestor de organización
FlowLayout fl = new FlowLayout();
//añade el gestor de organización al contenedor
this.setLayout(fl);
//crea los controles
Button b1 = new Button("Botón 1");
TextField t1 = new TextField("Campo de texto");
Choice c1 = new Choice();
Pág. 102
Programación en Java
BorderLayout
donde los posibles valores que puede tomar el argumento constraints están definidos
en las siguientes constantes de tipo String, incluidas en la clase BorderLayout:
Pág. 103
Programación en Java
import java.awt.*;
public class Ventana extends Frame {
public Ventana() {
//tamaño de la ventana
this.setBounds(10, 40, 350, 280);
//creación y establecimiento del gestor de organización
BorderLayout bl = new BorderLayout();
this.setLayout(bl);
//crea los controles
Button b1 = new Button("Norte");
Button b2 = new Button("Sur");
Button b3 = new Button("Este");
Button b4 = new Button("Oeste");
Button b5 = new Button("Centro");
//añade los controles al contenedor
this.add(b1, BorderLayout.NORTH);
this.add(b2, BorderLayout.SOUTH);
this.add(b3, BorderLayout.EAST);
this.add(b4, BorderLayout.WEST);
this.add(b5, BorderLayout.CENTER);
this.setVisible(true);
}
}
GridLayout
Pág. 104
Programación en Java
GridLayout(int filas, int columnas). Crea una rejilla con las filas y columnas
especificadas, sin separación entre las celdas.
GridLayout(int filas, int columnas, int hgap, int vgap). Crea una rejilla con la
distribución y separación entre celdas especificadas.
import java.awt.*;
public class Ventana extends Frame {
public Ventana() {
//tamaño de la ventana
this.setBounds(10, 40, 350, 280);
//creación y establecimiento del gestor de organización
GridLayout gl = new GridLayout(3, 2);
this.setLayout(gl);
//crea y añade los controles al contenedor
Button b1 = new Button("Botón 1");
Button b2 = new Button("Botón 2");
TextField t1 = new TextField("Campo de texto");
Button b3 = new Button("Botón 3");
Choice c1 = new Choice();
Button b4 = new Button("Botón 4");
this.add(b1);
this.add(c1);
this.add(b2);
this.add(b3);
this.add(b4);
this.add(t1);
this.setVisible(true);
}
Pág. 105
Programación en Java
CardLayout
Cada componente es apilado encima del anterior ocupando completamente el área del
contenedor, de modo que solamente es visible un componente en cada momento. Éste
es el gestor de organización que por defecto tiene establecido la clase Frame (figura
8.13).
Un panel es un contenedor sin borde y sin barra de título. Cada panel puede tener su
propio gestor de organización con su grupo de controles dispuestos en si interior, a su
vez, estos paneles podrán situarse en las diferentes zonas en que se divide el
contenedor principal. De esta forma podemos conseguir realizar la disposición de
controles que más se ajuste a nuestras necesidades.
8.3 SWING
Swing se presenta como una mejora de AWT, entre estas mejoras cabe destacar:
Pág. 106
Programación en Java
Por lo demás, no hay que olvidar que swing es una extensión de AWT, por lo que los
principios de funcionamiento son los mismos. La mecánica para la construcción de la
interfaz gráfica y el modelo de gestión de eventos, son idénticos a los empleados en
AWT.
La mayoría de las clases swing heredan JComponent, la cual, como se puede apreciar
en el gráfico, hereda la clase Container de AWT. Otras, como por ejemplo JFrame,
deerivan directamente de alguna de las clases funcionales de AWT.
Los pasos que tenemos que seguir para crear una interfaz gráfica con swing son
exactamente los mismos qie se han seguido con AWT, incluido el proceso de gestión de
eventos.
Tan sólo tenemos que tener en cuenta un par de matices a la hora de trabajar con la
clase JFrame:
Pág. 107
Programación en Java
Container getContentPane()
import javax.swing.*;
public class JVentana extends JFrame {
JButton jbt;
JTextField jtf;
JLabel jlb;
public JVentana(String titulo, int x, int y, int ancho, int alto) {
super(titulo);
this.setBounds(x, y, ancho, alto);
//se elimina el gestor de organización en el panel de contenido
this.getContentPane().setLayout(null);
Pág. 108
Programación en Java
import java.awt.event.*;
public class GestionBoton implements ActionListener {
JVentana vent;
public GestionBoton(JVentana v) {
vent = v;
}
public void actionPerformed(ActionEvent e) {
//Recupera el contenido del TextField
String s = vent.jtf.getText();
//Asigna el valor al control Label
vent.jlb.setText(s);
}
}
8.4 APPLETS
Uno de los ejemplos más interesantes de aplicaciones Java basadas en interfaz gráfica
son las applets.
Pág. 109
Programación en Java
Un applet es una aplicación Java que, una vez compilada, es referenciada dentro de
una página web para que sea descargada y ejecutada por un navegador cuando este
acceda a la página (figura 8.16).
Pág. 110
Programación en Java
Como ya sabemos, la creación de una aplicación basada en applets sigue los patrones
definidos para cualquier aplicación gráfica. El siguiente código corresponde a un applet
que realiza la misma función que la aplicación swing de la figura 22:
import javax.swing.*;
public class MiApplet extends JApplet {
JButton jbt;
JTextField jtf;
JLabel jlb;
public void init() {
this.getContentPane().setLayout(null);
jbt = new JButton("Mostrar texto");
jbt.setBounds(130, 200, 150, 30);
jtf = new JTextField();
jtf.setBounds(130, 70, 150, 30);
jlb = new JLabel(". . . . . . . . . .");
jlb.setBounds(130, 130, 150, 30);
GestionBoton gb = new GestionBoton(this);
jbt.addActionListener(gb);
this.getContentPane().add(jbt);
this.getContentPane().add(jtf);
this.getContentPane().add(jlb);
}
}
Pág. 111
Programación en Java
import java.awt.event.*;
public class GestionBoton implements ActionListener {
MiApplet ap;
public GestionBoton(MiApplet v) {
ap = v;
}
public void actionPerformed(ActionEvent e) {
//Recupera el contenido del TextField
String s = ap.jtf.getText();
//Asigna el valor al control Label
ap.jlb.setText(s);
}
}
<APPLET
code = “archivo_class”
codebase = “dirección_base”
width = “ancho”
height = “alto”
name = “nombre”>
</APPLET>
Pág. 112
Programación en Java
code. Indica la dirección relativa del archivo .class que incluye el código del
applet. Su utilización es imprescindible para la localización del applet.
codebase. Representa la dirección base, respecto de la cual se toma la dirección
indicada en code. Si no se especifica, por defecto, se tomará como dirección
base la de la página HTML, lo cual es equivalente a codebase=”.”.
width. Indica el ancho en pixeles del applet.
height. Indica el alto en pixeles del applet.
name. Nombre del applet, utilizado para referirse a él desde otro applet.
El siguiente código HTML corresponde a una página Web en la que se incluye una
referencia al applet anterior:
<html>
<head>
<title>Página con applet</title>
</head>
<body>
<h2>Applet para lectura y presentación de datos</h2>
<applet
codebase = "."
code = "MiApplet.class"
name = "prueba"
width = "600"
height = "400">
</applet>
</body>
</html>
Pág. 113
Programación en Java
<APPLET …>
<PARAM name=”nombre_parametro”
value=”valor_parametro”>
<PARAM name=”nombre_parametro”
value=”valor_parametro”>
</APPLET>
Por otro lado, para obtener el valor de un determinado parámetro desde el interior de
un applet debemos recurrir al método getParameter() de la clase Applet, método que
permite obtener el valor de un parámetro a partir de su nombre. Su formato es el
siguiente:
El siguiente código corresponde a un applet cuyo color del fondo se establece a partir
de las tres tonalidades de colores primarios suministrados desde el archivo HTML:
import java.awt.Color;
import javax.swing.JApplet;
public class Colores extends JApplet {
//se define el metodo init() para que se ejecute cada vez
// que se cargue el applet
public void init() {
// Utilizando el método getParameter() de la clase JApplet
// se obtienen los valores de las tonalidades de color
int r = Integer.parseInt(this.getParameter("red"));
int g = Integer.parseInt(this.getParameter("green"));
int b = Integer.parseInt(this.getParameter("blue"));
// El color está representado por la clase Color, por lo que
// se crea un objeto de ésta a partir de los tres colores
// primarios suministrados desde la página HTML
this.getContentPane().setBackground(new Color(r, g, b));
}
}
Pág. 114
Programación en Java
<html>
<head>
<title>Página del Applet</title>
</head>
<body>
<h2>Applet de color</h2>
<applet
codebase="."
code="appletcolores.Colores.class"
name="prueba"
width=400
height=300>
<!--paso de parametros-->
<param name="red" value="250">
<param name="green" value="0">
<param name="blue" value="10">
</applet>
</body>
</html>
Pág. 115
Programación en Java
9 APLICACIONES MULTITAREA
Se conoce como tarea o thread a un proceso individual que se ejecuta dentro del
sistema. En principio, podríamos considerar que cada bloque de código incluido en un
método es un thread. El método main() representa, de hecho, el conocido como thread
principal.
Normalmente, los programas son de flujo único, esto significa que las tareas se
ejecutan de una en una y la siguiente tarea no comienza hasta que la anterior haya
terminado.
Este modo de funcionamiento puede ser adecuado en parte de las aplicaciones que se
desarrollan, sin embargo, en algunos casos resulta útil, e incluso necesario, que dos o
más tareas se ejecuten “en paralelo”. Por ejemplo, supongamos que estamos
desarrollando una aplicación gráfica en la que tenemos que programar una serie de
métodos de respuesta a eventos, al mismo tiempo que remos que en alguna parte de
la interfaz haya un reloj que nos muestre la hora en tiempo real. Siguiendo el esquema
de ejecución de flujo único sucedería que, mientras se está ejecutando el método de
respuesta a un evento, la rutina encargada de obtener la hora del sistema no se
ejecutaría, lo cual provocaría una especie de “saltos” en la presentación de este dato.
Cuando hablamos de ejecución “en paralelo” esto no significa que las tareas se estén
ejecutando simultáneamente, pues para ello sería necesario disponer de más de un
procesador. En equipos monoprocesador la ejecución en paralelo, más conocida como
ejecución en modo multitarea, significa que no es necesario que una tarea termine
para que otra pueda ser ejecutada, pudiendo la JVM repartir el tiempo de uso de la
CPU entre las tareas que tiene que ejecutar (figura 9.1), siguiendo algún criterio
definido por la propia implementación de la JVM.
Hay que tener en cuenta que no se puede hacer suposición alguna sobre el tiempo que
la CPU dedicará a cada tarea, así como el orden de ejecución de éstas, pues todas
estas variables dependen de la implementación de la Máquina Virtual.
Figura 9.1 Ejecución en modo multitarea. El tiempo de CPU se distribuye entre las tareas.
A lo largo de este capítulo estudiaremos cómo crear aplicaciones Java para que se
ejecuten en modo multitarea.
Pág. 116
Programación en Java
La flexibilidad que ofrece la segunda de las opciones, dando libertad a la clase que
implementa la interfaz para que pueda heredar otra clase, hace que ésta sea preferible
sobre la primera. Además, la herencia es algo que debería quedar reservado para la
especialización de las clases.
El método run() de la clase Thread proporciona la base para la definición de las tareas,
de modo que el código asociado a cada una de ellas tendrá que estar definido en el
interior de este método.
Si las tareas que vamos a implementar van a realizar funciones diferentes, deberemos
crear tantas subclases de Thread como tareas, codificando en el método run() de cada
una las instrucciones a ejecutar por cada tarea.
Si todas las tareas van a realizar el mismo proceso, como por ejemplo, atender las
solicitudes que realizan los usuarios de Internet a un sitio Web, bastará con definir una
única clase con un método run() común.
Como ejemplo ilustrativo de esta técnica, vamos a desarrollar un programa que realice
dos tareas de forma concurrente, por un lado, una de las tareas se encargará de
imprimir en pantalla los números naturales del 1 al 100, mientras que la otra realizará
lo propio con los números enteros negativos del -1 al -100.
El código de las dos subclases de Thread donde se definen las tareas es el siguiente:
Pág. 117
Programación en Java
}
}
}
public class Tarea2 extends Thread {
public void run() {
for (int i = -1; i > -101; i--) {
System.out.println(i);
}
}
}
Cada tarea en ejecución está representada por un objeto Thread, por tanto, una vez se
haya definido la clase o clases Thread, se deberán crear tantas instancias de estas
clases como tareas se desee tener en ejecución concurrente y hacer que se ejecuten.
Cuando se crea un objeto Thread éste pasa al estado “nuevo”, pero aún no se produce
la ejecución de la tarea asociada. Para que esto suceda, habrá que invocar
explícitamente al método start() de cada objeto Thread.
Todas las tareas que estén en el estado “preparado”, estarán listas para su ejecución,
siendo responsabilidad del gestor de tareas el reparto del tiempo de CPU entre cada
una de ellas y el orden de ejecución de las mismas.
Pág. 118
Programación en Java
Además de los ya conocidos run() y start(), la clase Thread incluye otros métodos de
interés que vamos a analizar en los siguientes apartados.
El método sleep()
Su formato es el siguiente:
Hay que tener en cuenta que la llamada al método sleep() sobre un thread puede
provocar una excepción de tipo IterruptedException, que habrá que capturar. El
siguiente código muestra el mismo ejemplo de antes, utilizando el método sleep para
dormir las tareas durante 100 milisegundos después de imprimir el número en
pantalla:
Pág. 119
Programación en Java
}
}
}
En este ejemplo, el hecho de poner a dormir un tiempo las tareas provoca que los
mensajes generados por ellas estén más sincronizados.
Nombre de un Thread
La clase Thread dispone del método estático currentThread() que permite obtener una
referencia al thread actual en ejecución, su formato es:
Pág. 120
Programación en Java
nombre: juan
nombre: pepe
nombre: ana
nombre: juan
nombre: pepe
nombre: ana
nombre: juan
nombre: pepe
nombre: ana
:
Prioridad de un Thread
donde el argumento del método representa la prioridad establecida y cuyo valor puede
ser cualquier número entero comprendido entre 1 y 10.
Pág. 121
Programación en Java
El método yield()
Su formato es:
El método join()
Se utiliza para unir un thread con el final de otro thread, de modo que el thread
primero quedará bloqueado en el estado “preparado” hasta que finalice la ejecución del
thread con el que se ha enlazado.
A.join();
pasará al estado “preparado” hasta que finalice la tarea “A”. Al igual que sucede con
sleep(), la llamada a join() puede provocar una excepción InterruptedException que
habrá que capturar.
Pág. 122
Programación en Java
Si optamos por esta vía, las acciones que tenemos que llevar a cabo serán las
siguientes:
La interfaz Runnable incluye un único método, run(), que como sucede con la clase
Thread, será el que deba incluir las acciones a realizar por las tareas.
Pág. 123
Programación en Java
Para crear objetos Thread con estas características, debemos utilizar alguno de los
siguientes constructores de la clase Thread:
Thread(Runnable obj)
Thread(Runnable obj, String nombre)
En ambos casos, la creación del objeto Thread requiere de un objeto Runnable que
disponga de la implementación del método run(). Si utilizamos el segundo constructor,
podemos además asignar un nombre a cada tarea.
Pág. 124
Programación en Java
Cuando se trabaja con aplicaciones multitarea, pueden darse situaciones en las cuales
la utilización de un objeto en modo concurrente de lugar a una situación de
incoherencia de datos.
Por ejemplo, supongamos que tenemos una clase llamada Accesos que utilizamos para
llevar la cuenta del número de veces que se ha pasado por un determinado bloque de
código. Dicha clase, cuenta con dos métodos: getValor(), que devuelve un número
almacenado en el objeto en donde se lleva la cuenta de los accesos realizados y
setValor(), utilizado para actualizar el contador de accesos:
class Accesos {
private int cont;
public int getValor() {
return cont;
}
public void setValor(int v) {
cont = v;
}
}
Ahora, supongamos que desde otra clase se crean tres objetos Thread asociados a la
clase Tarea:
class Ejecucion {
Pág. 125
Programación en Java
Para paliar este problema, la solución pasa por hacer algo que impida a un thread
utilizar el objeto Accesos mientras el último thread que lo ha utilizado no haya
completado la llamada al método setValor(). Esto se consigue mediante la
sincronización.
:
synchronized(ref objeto) {
//bloque de instrucciones a sincronizar
}
:
Pág. 126
Programación en Java
Hay que tener en cuenta que cuando un thread se va a dormir, se lleva el monitor del
objeto consigo.
Existen tres métodos en la clase Object que permiten enviar señales de unos threads a
otros, cuando éstos están ejecutando bloques de código synchronized en donde la
ejecución de uno de estos bloques depende de la ejecución de otro.
wait(). Una vez invocado este método, el thread libera el monitor del objeto y
pasa al estado de “esperando”, estado en el que estará hasta que reciba una
señal por parte del objeto para poder continuar su ejecución. Existe una versión
sobrecargada del método wait() en la que se puede establecer un tiempo de
espera máximo:
Pág. 127
Programación en Java
Si transcurrido ese tiempo el thread no ha recibido una señal para salir del
estado de espera, automáticamente pasa a “preparado”.
La llamada a cualquiera de las versiones del método wait() puede provocar una
excepción InterruptedException.
notify(). Al invocarlo se envía una señal al thread que hizo la llamada a wait()
sobre el objeto. A partir de ese momento, el thread podrá continuar su
ejecucioin, pasando al estado de “preparado”.
notifyAll(). Si hay varios threads esperando sobre el objeto, la llamada a
notify() afectará solamente a uno de ellos. Para notificar a todos los threads
que están a la espera se utiliza notifyAll().
class Principal {
public static void main(String[] args) {
Proceso p = new Proceso();
Thread t = new Thread(p);
t.start();
synchronized (t) {
try {
//Espera a que finalice el cálculo
t.wait();
} catch (InterruptedException e) {
}
System.out.println("La suma es " + p.getTotal());
}
}
}
class Proceso implements Runnable {
//Dato miembro que almacena los resultados del cálculo
private int valor;
public int getTotal() {
//Devuelve el cálculo realizado
return valor;
}
public void run() {
//Bloque de código sincronizado donde se obtiene el
//monitor del propio objeto Proceso
synchronized (this) {
for (int i = 1; i < 101; i++) {
valor += i;
}
//Avisa de que ha finalizado el cálculo al thread que tenía el
Pág. 128
Programación en Java
Pág. 129