0% encontró este documento útil (0 votos)
10 vistas

Tutorial Java

Cargado por

Arturo Montiel
Derechos de autor
© © All Rights Reserved
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
10 vistas

Tutorial Java

Cargado por

Arturo Montiel
Derechos de autor
© © All Rights Reserved
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 137

Programación en Java

Unidad Profesional Interdisciplinaria en


Ingeniería y Tecnologías Avanzadas

Programación en
Java
Introducción al lenguaje
Freddy RT

Página
Programación en Java

ÍNDICE

1 Introducción a Java ....................................................................................... 1

1.1 Características de Java ............................................................................ 1

1.2 La máquina virtual (JVM)......................................................................... 1

1.3 Conceptos básicos de programación en Java .............................................. 2

1.3.1 Objetos .............................................................................................. 2

1.3.2 Clases ................................................................................................ 3

1.3.3 Métodos y Atributos ............................................................................. 3

1.3.4 El método main() ................................................................................ 4

2 Sintaxis del Lenguaje .................................................................................... 5

2.1 Tipos de datos primitivos ......................................................................... 5

2.2 Variables ............................................................................................... 5

2.2.1 Tipos de datos de una variable .............................................................. 5

2.2.2 Ámbito de las variables ........................................................................ 6

2.2.3 Conversiones de tipo ........................................................................... 6

2.2.4 Constantes ......................................................................................... 7

2.3 Operadores............................................................................................ 7

2.3.1 Aritméticos ......................................................................................... 7

2.3.2 Asignación .......................................................................................... 8

2.3.3 Condicionales...................................................................................... 9

2.3.4 Lógicos ............................................................................................ 10

2.3.5 Operador condicional ......................................................................... 10

2.4 Instrucciones de Control ........................................................................ 10

2.4.1 Instrucción if .................................................................................... 10

2.4.2 La instrucción switch.......................................................................... 11

2.4.3 La instrucción for .............................................................................. 12

Pág. I
Programación en Java

2.4.4 La instrucción while ........................................................................... 12

2.4.5 Salida forzada de un bucle.................................................................. 13

2.5 Arrays ................................................................................................. 13

2.5.1 Declaración ...................................................................................... 13

2.5.2 Acceso a los elementos de un array ..................................................... 13

2.5.3 Recorrido de arrays con for-each ......................................................... 14

2.5.4 Arrays multidimensionales .................................................................. 15

3 Clases de Uso General ................................................................................. 16

3.1 La clase String ..................................................................................... 16

3.1.1 Creación de objetos String.................................................................. 16

3.1.2 Principales métodos de la clase String .................................................. 16

3.2 La clase Math ....................................................................................... 17

3.2.1 Constantes públicas ........................................................................... 18

3.2.2 Métodos ........................................................................................... 18

3.3 Clases de Envoltorio ............................................................................. 19

3.3.1 Encapsulamiento de un tipo básico ...................................................... 19

3.3.2 Conversión de cadena a tipo numérico ................................................. 19

3.4 Entrada y Salida en Java ....................................................................... 20

3.4.1 Salida de datos ................................................................................. 20

3.4.2 Salida con formato ............................................................................ 20

3.4.3 Entrada de datos ............................................................................... 21

3.4.4 Scanners .......................................................................................... 22

3.5 Colecciones ......................................................................................... 24

3.5.1 La clase ArrayList .............................................................................. 25

3.5.2 La clase Hashtable............................................................................. 26

3.5.3 Genéricos ......................................................................................... 28

3.6 Organización de programas en clases ..................................................... 30

Pág. II
Programación en Java

4 Programación Orientada a Objetos con Java ................................................... 31

4.1 Empaquetado de clases ......................................................................... 31

4.2 Modificadores de acceso ........................................................................ 31

4.3 Encapsulación ...................................................................................... 32

4.3.1 Protección de datos ........................................................................... 32

4.3.2 Facilidad en el mantenimiento de la clase ............................................. 34

4.3.3 Clases de encapsulación ..................................................................... 34

4.4 Sobrecarga de métodos ........................................................................ 35

4.5 Constructores ...................................................................................... 35

4.5.1 Definición y utilidad ........................................................................... 35

4.5.2 Constructores por defecto .................................................................. 37

4.6 Herencia ............................................................................................. 38

4.6.1 Nomenclatura y reglas ....................................................................... 39

4.6.2 Relación "es un" ................................................................................ 40

4.6.3 Métodos y atributos protegidos ........................................................... 42

4.6.4 Sobrescritura de métodos ................................................................... 43

4.7 Clases abstractas ................................................................................. 44

4.7.1 Definición ......................................................................................... 44

4.7.2 Sintaxis y características .................................................................... 45

4.8 Polimorfismo ........................................................................................ 46

4.8.1 Asignación de objetos a variables de su superclase ................................ 46

4.8.2 Definición de polimorfismo ................................................................. 47

4.8.3 Ventajas de la utilización del polimorfismo ............................................ 47

4.8.4 Tipos de retorno covariantes ............................................................... 48

4.9 Interfaces ............................................................................................ 49

4.9.1 Definición de interfaz ......................................................................... 49

4.9.2 Definición de una interfaz ................................................................... 50

Pág. III
Programación en Java

4.9.3 Implementación de una interfaz .......................................................... 50

4.9.4 Interfaces y polimorfismo ................................................................... 51

5 Excepciones ............................................................................................... 52

5.1 Excepciones y errores ........................................................................... 52

5.2 Clases de excepción .............................................................................. 52

5.3 Tipos de excepciones ............................................................................ 53

5.3.1 Excepciones marcadas ....................................................................... 53

5.3.2 Excepciones no marcadas ................................................................... 54

5.4 Captura de excepciones ........................................................................ 54

5.4.1 Los bloques try, catch y finally ............................................................ 54

5.4.2 Propagación de una excepción ............................................................ 56

5.5 Lanzamiento de una excepción ............................................................... 57

5.6 Métodos para el control de una excepción................................................ 58

5.7 Clases de excepción personalizadas ........................................................ 59

6 Acceso al disco ........................................................................................... 61

6.1 Información sobre ficheros y directorios. La clase File ............................... 61

6.1.1 Creación de un objeto File .................................................................. 61

6.1.2 Métodos de la clase File ..................................................................... 61

6.2 Lectura de un fichero de texto ............................................................... 61

6.2.1 Creación de un objeto FileReader ........................................................ 62

6.2.2 Creación de un objeto BufferedReader ................................................. 62

6.3 Escritura en ficheros de texto ................................................................. 63

6.3.1 Creación de un objeto FileWriter.......................................................... 63

6.3.2 Creación del objeto PrintWriter............................................................ 63

6.4 Escritura de datos primitivos Java en un fichero ....................................... 63

6.4.1 Creación de un objeto FileOutputStream .............................................. 63

6.4.2 Creación de un objeto DataOutputStream ............................................ 64

Pág. IV
Programación en Java

6.5 Lectura de datos primitivos de un fichero ................................................ 64

6.5.1 Creación de un objeto FileInputStream ................................................ 64

6.5.2 Creación de un objeto DataInputStream ............................................... 64

6.6 Escritura de objetos en un fichero .......................................................... 65

6.6.1 Serialización de objetos ...................................................................... 65

6.6.2 Creación de un objeto ObjectOutputStream .......................................... 66

6.7 Lectura de objetos de un fichero ............................................................ 66

6.7.1 Creación de un objeto ObjectInputStream ............................................ 66

6.7.2 Deserialización de objetos .................................................................. 67

7 Acceso a datos en Java ................................................................................ 68

7.1 La tecnología Java Database Conectivity (JDBC) ....................................... 68

7.2 El Driver JDBC ..................................................................................... 68

7.2.1 Estructura y funcionamiento ............................................................... 69

7.2.2 Tipos de driver JDBC.......................................................................... 69

7.3 El lenguaje SQL .................................................................................... 71

7.3.1 Consultas ......................................................................................... 71

7.3.2 Tipos de sentencias SQL ..................................................................... 72

7.3.3 Sentencias para manipulación de datos (DML) ...................................... 72

7.4 El API JDBC ......................................................................................... 76

7.5 Utilización de JDBC para acceder a datos ................................................. 76

7.5.1 Conexión con la base de datos ............................................................ 77

7.5.2 Ejecución de consultas ....................................................................... 78

7.5.3 Cierre de la conexión ......................................................................... 79

7.5.4 Manipulación de registros ................................................................... 80

7.5.5 Información sobre los datos ................................................................ 83

7.5.6 Consultas preparadas ........................................................................ 84

7.5.7 ResultSet desplazables ....................................................................... 85

Pág. V
Programación en Java

8 Aplicaciones basadas en entorno gráfico ........................................................ 87

8.1 AWT ................................................................................................... 87

8.1.1 Principales clases de AWT ................................................................... 87

8.1.2 Contenedores ................................................................................... 88

8.1.3 Creación de una ventana .................................................................... 88

8.1.4 Personalización de ventanas ............................................................... 90

8.1.5 Agregar controles a un contenedor ...................................................... 90

8.2 El modelo de gestión de eventos en Java ................................................. 92

8.2.1 Interfaces de escucha y escuchadores .................................................. 92

8.2.2 El proceso de gestión de eventos ......................................................... 93

8.2.3 Clases de evento ............................................................................... 95

8.2.4 Adaptadores ..................................................................................... 97

8.2.5 Referencia a los objetos de la interfaz desde la clase de escucha ............. 97

8.2.6 Gestores de organización AWT .......................................................... 100

8.3 SWING .............................................................................................. 106

8.3.1 Principales clases de Swing ............................................................... 107

8.3.2 Creación de una interfaz gráfica Swing ............................................... 107

8.4 Applets ............................................................................................. 109

8.4.1 La clase Applet ................................................................................ 110

8.4.2 Métodos del ciclo de vida de un applet ............................................... 110

8.4.3 Creación de un applet ...................................................................... 111

8.4.4 Inclusión de un applet en un documento HTML .................................... 112

8.4.5 Paso de parámetros a un applet ........................................................ 114

9 Aplicaciones multitarea .............................................................................. 116

9.1 Aplicaciones multitarea en java ............................................................ 116

9.2 Extensión de la clase Thread ................................................................ 117

9.2.1 Sobrescritura del método run() ......................................................... 117

Pág. VI
Programación en Java

9.2.2 Creación y ejecución de las tareas ..................................................... 118

9.2.3 Métodos para el control de threads .................................................... 118

9.2.4 Estados de un thread ....................................................................... 122

9.3 Implementación de la interfaz Runnable ................................................ 123

9.3.1 Implementación del método run() ..................................................... 123

9.3.2 Creación y ejecución de tareas .......................................................... 124

9.4 Sincronización de Threads ................................................................... 124

9.4.1 Acceso concurrente a objetos ............................................................ 125

9.4.2 Sincronización y monitores ............................................................... 126

9.5 Comunicación entre threads ................................................................ 127

Pág. VII
Programación en Java

1 INTRODUCCIÓN A JAVA

1.1 CARACTERÍSTICAS DE JAVA

He aquí los principales puntos en los que se apoya la tecnología Java:

 Lenguaje totalmente orientado a objetos. Todos los conceptos en los que se


apoya esta técnica, encapsulación, herencia, polimorfismo, etc., están presentes en
Java.
 Disponibilidad de un amplio conjunto de librerías. La programación de
aplicaciones con Java se basa no sólo en el empleo del juego de instrucciones que
componen el lenguaje, sino en la posibilidad de utilizar el amplio conjunto de clases
que los desarrolladores ponen a disposición del programador y con los cuales es
posible realizar, prácticamente, cualquier tipo de aplicación.
 Aplicaciones multiplataforma. El que una aplicación sea multiplataforma,
implica que después de ser compilado el programa, puede ser ejecutado en
diferentes sistemas operativos sin necesidad de realizar cambios en el código
fuente y sin que haya que volver a compilar el programa.
 Ejecución segura de aplicaciones. La seguridad de las aplicaciones Java se
manifiesta en varios aspectos. Por un lado, el lenguaje carece de instrucciones que
puedan provocar accesos descontrolados a la memoria. Por otro lado, la máquina
virtual, que es el entorno en el que se ejecutan las aplicaciones Java, impone
ciertas restricciones a las aplicaciones para garantizar una ejecución segura.

1.2 LA MÁQUINA VIRTUAL (JVM)

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.

En la figura 1.1 tenemos un esquema en el que se ilustra todo el proceso de


compilación y ejecución de aplicaciones.

Figura 1.1 Proceso de compilación y ejecución de aplicaciones Java.

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.

Cada sistema operativo proporciona su propia implementación de la JVM. Todas ellas


ofrecen el mismo tratamiento a los bytecodes, sin embargo, cada una realiza la
interpretación de acuerdo a las características del sistema operativo para el cual ha
sido diseñada.

1.3 CONCEPTOS BÁSICOS DE PROGRAMACIÓN EN JAVA

Una de las características de Java, es que es un lenguaje totalmente orientado a


objetos. Como tal, todo programa Java debe estar escrito en una o varias clases,
dentro de las cuales se podrá hacer uso además del amplio conjunto de paquetes de
clases prediseñadas.

A continuación se aclararán algunos conceptos básicos necesarios para poder avanzar


en el aprendizaje del lenguaje.

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).

Figura 1.2 Representación lógica de un objeto con sus métodos.

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.

Figura 1.3 Llamada a los métodos de un objeto.

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.

Figura 1.4 Definición de una clase Java.

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).

Figura 1.5 Creación de un objeto o instancia y posterior llamada a sus métodos.

1.3.3 MÉTODOS Y ATRIBUTOS

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.

Figura 1.6 Definición de una clase Java con métodos y atributos.

1.3.4 EL MÉTODO MAIN()

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.

Figura 1.7 Formato del método main().

El método main() es el punto de arranque de un programa Java, cuando se invoca al


comando java.exe desde la línea de comandos, la JVM busca en la clase indicada un
método estático llamado main() con el formato indicado en la figura 1.7. Dentro del
código de main() pueden crearse objetos de otras clases e invocar a sus métodos.

Es posible suministrar parámetros al método main() a través de la línea de comandos.


Para ello, los valores a pasar deberán especificarse a continuación del nombre de la
clase, separados por un espacio:

java nombreClase arg1 arg2 arg3

Los datos llegarán al método main() en forma de un array de cadenas de caracteres.

Pág. 4
Programación en Java

2 SINTAXIS DEL LENGUAJE

2.1 TIPOS DE DATOS PRIMITIVOS

Toda la información que se maneja en un programa Java puede estar representada,


bien por un objeto o bien por un dato básico o de tipo primitivo. Java soporta ocho
tipos de datos primitivos, que pueden clasificarse en cuatro grupos:

 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

Una variable es un espacio físico de memoria donde un programa puede almacenar un


dato para su posterior utilización.

2.2.1 TIPOS DE DATOS DE UNA VARIABLE

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.

Figura 2.1 Ejemplos de declaración y asignación de variables de tipo primitivo.

 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

2.2.2 ÁMBITO DE LAS VARIABLES

Según donde este declarada una variable, ésta puede ser:

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.

2.2.3 CONVERSIONES DE TIPO

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.

Las conversiones de tipo pueden realizarse de dos maneras: implícita o explícitamente.

Conversiones implícitas

Las conversiones implícitas se realizan de manera automática, es decir, el valor o


expresión que va a asignar a la variable es convertido automáticamente al tipo de ésta
por el compilador.

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.

El siguiente listado tiene ejemplos de conversiones implícitas:

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:

variable_destino = (tipo_destino) dato_origen;

Con esta expresión le estamos diciendo al compilador que convierta dato_origen a


tipo_destino para que pueda ser almacenado en variable_destino.

A esta operación se le conoce como casting ya que al convertir un dato de un tipo en


otro de tamaño inferior se realiza un estrechamiento que puede provocar una pérdida
de datos, aunque ello no provocará errores de compilación. Los siguientes son
ejemplos de conversiones explicitas:

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:

final tipo nombre_cte = valor;

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

Java dispone de una amplia variedad de operadores, a continuación estudiaremos los


más importantes.

2.3.1 ARITMÉTICOS

Se utilizan para realizar operaciones aritméticas dentro de un programa, actuando


sobre valores de tipo numérico.

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.

Tabla 2.1 Operadores aritméticos.

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:

System.out.println(“Hola ”+”adios”); //mostraría Hola adiós

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

Además del clásico operador de asignación (=), Java dispone de un conjunto de


operadores que permiten simplificar el proceso de operar con una variable y asignar el
resultado de la operación a la misma variable.

La tabla 2.2 muestra estos operadores y la función que realiza cada uno.

Tabla 2.2 Operadores de asignación.

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

(b%=4 equivale a b=b%4)

2.3.3 CONDICIONALES

Se utilizan para establecer una condición dentro de un programa, el resultado de esta


será de tipo boolean (true o false). Estos operadores se utilizan en instrucciones de
control de flujo.

La tabla 2.3 contiene el listado de los operadores condicionales.

Tabla 2.3 Operadores 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.

Comparación de tipos básicos

Los operadores de comparación (<, >, <= y >=) únicamente podrán utilizarse para
comparar enteros, puntos flotantes y caracteres.

Si estos operadores se utilizan con referencias a objetos, se produce un error de


compilación.

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.

Para comprobar la igualdad de objetos, las clases proporcionan un método llamado


equals, en el que cada clase implementa su propio criterio de igualdad. Por tanto, si
queremos saber si dos objetos de una determinada clase son iguales, se debe utilizar
el método equals, no el operador de comparación “==”, el cual solamente daría un
resultado positivo si las dos variables apuntan al mismo objeto.

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.

La tabla 2.4 muestra los tres operadores lógicos de Java.

Tabla 2.4 Operadores lógicos.

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.3.5 OPERADOR CONDICIONAL

Se trata de un operador ternario (consta de tres operandos) cuya función es asignar


un valor entre dos posibles a una variable, en función del cumplimiento o no de una
condición. Su formato es:

tipo variable = (condición)? valor_si_true : valor_si_false

Si la condición resulta verdadera, se almacenará en la variable el resultado de la


expresión valor_si_true, en caso contrario se almacenará valor_si_false.

2.4 INSTRUCCIONES DE CONTROL

Como cualquier otro lenguaje de programación, Java dispone de un juego de


instrucciones para controlar el flujo de ejecución de un programa. A continuación
estudiaremos cada una de ellas.

2.4.1 INSTRUCCIÓN IF

La instrucción if es una sentencia de tipo alternativa simple que permite comprobar


una condición dentro de un programa. En caso de que la condición se cumpla se
ejecutará un determinado conjunto de instrucciones, mientras que si no se cumple, se
podrá optar por ejecutar otro conjunto diferente de instrucciones o por no ejecutar
ninguna.

En la figura 2.3 se muestra el formato de esta instrucción.

Pág. 10
Programación en Java

Figura 2.3 Formato de la instrucción if.

A la hora de utilizar esta instrucción hay que tener en cuenta lo siguiente:

 La expresión de comprobación puede ser cualquier expresión cuyo resultado sea de


tipo boolean.
 El bloque else es opcional.
 Cuando un bloque de sentencias, ya sea de if o de else, está formado únicamente
por una instrucción, la utilización de las llaves delimitadoras es opcional.
 Las instrucciones if se pueden anidar.

2.4.2 LA INSTRUCCIÓN SWITCH

Se trata de una instrucción de tipo alternativa múltiple. Permite ejecutar diferentes


bloques de instrucciones en función del resultado de una expresión.

La figura 2.4 muestra el formato de la instrucción.

Figura 2.4 Formato de la instrucción switch.

En caso de que el resultado de la expresión coincida con el valor representado por


valor1, se ejecutarán las sentencias definidas en este bloque, si no coincide se
comparará con valor2, y así sucesivamente. Si el resultado no coincide con ninguno de
los valores indicados en los case, se ejecutará el bloque de instrucciones indicado en
default.

Sobre el uso de la instrucción switch hay que tener en cuenta lo siguiente:

 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.

2.4.3 LA INSTRUCCIÓN FOR

La instrucción repetitiva for permite ejecutar un conjunto de instrucciones un número


determinado de veces. Su formato se muestra en la figura 2.5.

Figura 2.5 Utilización de la instrucción for.

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.

Sobre el uso de la instrucción for hay que tener en cuenta lo siguiente:

 Las instrucciones de control del bucle for son opcionales.


 Si se declara una variable en la instrucción de inicialización, esta será accesible
únicamente desde el interior del for.
 Las llaves delimitadoras de bloque solamente son obligatorias si el for está
compuesto por más de una instrucción.

2.4.4 LA INSTRUCCIÓN WHILE

Permite ejecutar un bloque de instrucciones mientras se cumpla una determinada


condición dentro del programa. Los dos posibles formatos que admite esta instrucción
se muestran en la figura 2.6.

Figura 2.6 Instrucción while y formato de utilización.

En ambos casos, el bloque de instrucciones se ejecuta mientras la condición se cumple.


En el segundo formato se ejecutan las instrucciones y luego se comprueba la

Pág. 12
Programación en Java

condición, lo que garantiza que el bloque de instrucciones se ejecute por lo menos una
vez.

2.4.5 SALIDA FORZADA DE UN BUCLE

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:

 Break. Ya se ha visto la utilidad de break en una sentencia switch, sin embargo, su


uso se puede extender a las instrucciones repetitivas. En éstas, la utilización de
break provoca una salida forzada del bucle.
 Continue. La instrucción continue provoca que el bucle detenga la iteración actual
y pase, en el caso de for, a ejecutar la instrucción de incremento o, en el caso de
while, a comprobar la condición de entrada.

2.5 ARRAYS

Un array es un objeto en el que se puede almacenar un conjunto de datos del mismo


tipo. Cada uno de los elementos del array tiene asignado un índice numérico según su
posición, siendo 0 el índice del primero.

2.5.1 DECLARACIÓN

Un array debe declararse utilizando la expresión

tipo[] variable_array = new tipo[tamaño];

tipo variable_array[] = new tipo[tamaño];

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.

Cuando un array se dimensiona, todos sus elementos son iniciados explícitamente al


valor por defecto del tipo correspondiente.

Existe una forma de declarar, dimensionar e inicializar un array en una misma


sentencia. La siguiente instrucción crea un array de cuatro enteros y los inicializa a los
valores indicados entre llaves:

int[] nums = {10, 20, 30, 40};

2.5.2 ACCESO A LOS ELEMENTOS DE UN ARRAY

El acceso a los elementos de un array se realiza utilizando la expresión:

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.

int [] nums = new int[10];


for (int i = 0; i < nums.length; i++) {
nums[i] = i*2;
}

2.5.3 RECORRIDO DE ARRAYS CON FOR-EACH

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.

En el caso de un array, el formato de la nueva instrucción sería:

for (tipo variable : var_array) {


//instrucciones
}

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.

Por ejemplo, supongamos que tenemos el siguiente array:

int[] nums = {10, 20, 30, 40};

y queremos mostrar en pantalla cada uno de los números almacenados en el mismo.


Utilizando la versión tradicional de for, la forma de hacerlo sería:

for (int i = 0; i < nums.length; i++) {


System.out.println(nums[i]);
}

Utilizando la nueva instrucción for-each, la misma operación se realizaría de la


siguiente forma:

for (int n : nums) {


System.out.println(n);
}

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

2.5.4 ARRAYS MULTIDIMENSIONALES

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;

A la hora de asignarle tamaño se procedería como en los de una dimensión, indicando


en los corchetes el tamaño de cada dimensión:

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;

Si imaginamos un array de dos dimensiones como una tabla organizada en filas y


columnas, el array anterior tendría el aspecto indicado en la figura 2.7.

Figura 2.7 Array bidimensional.

No obstante, es posible definir un array bidimensional en el que el número de


elementos de la segunda dimensión sea variable, indicando únicamente el tamaño de
la primera dimensión:

int [][] p = new int[2][];

Un array de estas características equivale a un array de arrays, donde cada elemento


de la primera dimensión almacenará su propio array:

p[0] = new int[4];

p[1] = new int[6];

La figura 2.8 ilustra gráficamente la situación.

Figura 2.8 Array bidimensional con dimensiones de diferente valor.

Pág. 15
Programación en Java

3 CLASES DE USO GENERAL

3.1 LA CLASE STRING

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.

3.1.1 CREACIÓN DE OBJETOS STRING

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:

String s = new String(“Texto de prueba”);

Sin embargo, dada la amplia utilización de estos objetos dentro de un programa, se


puede crear y asignar un objeto String de la misma forma que se hace con cualquier
tipo de dato básico. Así, la sentencia anterior es equivalente a:

String s = “Texto de prueba”;

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.

3.1.2 PRINCIPALES MÉTODOS DE LA CLASE STRING

La clase String cuenta con un amplio número de métodos para la manipulación de


cadenas de texto. A continuación se presentan las más comunes:

 int length(). Devuelve el número de caracteres que conforman la cadena.


 boolean equals(String otra). Compara la cadena con otra, haciendo distinción
entre mayúsculas y minúsculas. Se debe utilizar este método en vez del operador
“==” cuando se desee comparar dos cadenas de texto.

String s1, s2;


//Suponiendo que las variables adquieren algún valor:
if(s1 == s2) //El resultado puede ser falso aunque las cadenas sean iguales
if(s1.equals(s2)) //El resultado será verdadero si las cadenas son iguales

 boolean equalsIgnoreCase(String otra). Similar al anterior pero sin hacer


distinción entre mayúsculas y minúsculas.
 char charAt(int pos). Devuelve el carácter ubicado en la posición indicada como
parámetro.

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 substring(int inicio, int final). Devuelve un fragmento de la cadena sobre la


que se aplicará, iniciando en el carácter posicionado en inicio y terminando en final-
1.
 int indexOf(String cad). Devuelve la posición de la cadena indicada como
parámetro dentro de la cadena principal, y en caso de que no exista devuelve -1.
Otra versión de este método acepta un carácter como parámetro.
 String replace(char old, char new). Devuelve la cadena resultante de sustituir
todas las apariciones del primer carácter por el segundo.
 static String valueOf(tipo_basico dato). Método que devuelve como cadena el
valor pasado como parámetro.
 String toUpperCase(). Devuelve la cadena en formato de mayúsculas.
 String toLowerCase(). Devuelve la cadena en formato de minúsculas.
 String[] split(String regex). Devuelve un array de String resultado de
descomponer la cadena de texto en subcadenas, utilizando como separador el
elemento especificado como parámetro. Por ejemplo, dado el siguiente programa:

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]);
}

Tras su ejecución, se tendrá en pantalla lo siguiente:

Día: lunes

Día: martes

Día: miércoles

3.2 LA CLASE MATH

Esta clase proporciona métodos para la realización de las operaciones matemáticas


más habituales en un programa. Todos los métodos de la clase Math son estáticos, por
lo que no se necesita crear una instancia para su uso.

Pág. 17
Programación en Java

3.2.1 CONSTANTES PÚBLICAS

La clase Math dispone de dos atributos públicos estáticos que contienen dos de las
constantes públicas más utilizadas:

static double E. Contiene el valor doble más cercano al número e.

static double PI. Contiene el valor doble más cercano al número pi.

3.2.2 MÉTODOS

A continuación se exponen algunos de los métodos más usados de esta clase:

 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.

int num = Math.max(5, 3); //almacena el valor 5

 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 d = Math.ceil(5.3); //almacena el valor 6.0

 double floor(double n). Devuelve el entero menor más cercano al número indicado
en el parámetro:

double d = Math.floor(5.3); //almacena el valor 5.0

 double round(double n). Devuelve el entero más cercano al número indicado en el


parámetro:

double d = Math.floor(5.3); //almacena el valor 5

 double pow(double n1, double n2). Devuelve el resultado de elevar n1 a n2.


 double random(). Devuelve un número aleatorio entre 0 y 1. El siguiente ejemplo
utiliza este método para generar 10 números aleatorios y luego almacenarlos en un
array.

public class tutorial {


public static void main(String args[]) {
int[] m = new int[10];
for (int i = 0; i < m.length; i++) {
m[i] = (int) (Math.random() * 50 + 1);
}
}
}

Pág. 18
Programación en Java

3.3 CLASES DE ENVOLTORIO

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:

 Encapsular un dato básico en un objeto. En el amplio conjunto de clases Java,


existen determinados métodos que realizan algún tipo de manipulación con datos
de tipo objeto, por lo que para que puedan almacenarse números es necesario
encapsularlos en un objeto.
 Conversión de cadena a tipo básico. En la mayoría de las operaciones de
entrada de datos, éstos llegan a la aplicación en forma de cadena de caracteres.
Las clases de envoltorio proporcionan métodos estáticos que permiten convertir
una cadena de texto, formada por caracteres numéricos, en un tipo numérico.

Existen ocho clases de envoltorio: Byte, Short, Character, Integer, Long, Float,
Double y Boolean.

3.3.1 ENCAPSULAMIENTO DE UN TIPO BÁSICO

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);

A excepción de Character, las clases de envoltorio también permiten crear objetos


partiendo de la representación como cadena del dato. La siguiente instrucción crea un
objeto Float a partir de la cadena numérica:

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:

float dato = ft.floatValue();


int n = intValue();

3.3.2 CONVERSIÓN DE CADENA A TIPO NUMÉRICO

Las clases numéricas proporcionan un método estático parseXxx(String) que permite


convertir la representación en forma de cadena de un número en el correspondiente

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():

String s1= “25”, s2= “89.2”;


int n= Integer.parseInt(s1);
double d= Double.parseDouble(s2);

3.4 ENTRADA Y SALIDA EN JAVA

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.

3.4.1 SALIDA DE DATOS

El envío de datos al exterior gestiona a través de la clase PrintStream, utilizándose un


objeto de la misma para acceder al dispositivo de salida.

El proceso de envío de datos a la salida debe realizarse siguiendo dos pasos:

1. Obtención del objeto PrintStream. Se debe crear un objeto PrintStream


asociado al dispositivo de salida, la forma de hacerlo dependerá del dispositivo
en cuestión. La clase System proporciona el atributo estático out que contiene
una referencia a la salida estándar, que es la consola.
2. Envío de datos al stream. La clase PrintStream dispone de los métodos
print(cadena) y println(cadena) para enviar una cadena de caracteres al
dispositivo de salida, diferenciándose uno de otro en que el segundo añade un
salto de línea al final de la cadena.

3.4.2 SALIDA CON FORMATO

El método printf()

La clase PrintStream proporciona un método de escritura que permite aplicar un


formato a la cadena de caracteres que se va enviar a la salida. Se trata del método
printf(), cuyo formato es el siguiente:

printf(String formato, Object datos);

El argumento formato consiste en una cadena de caracteres con las opciones de


formato que van a ser aplicadas sobre los datos a imprimir.

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.

A modo de ejemplo, dadas las siguientes instrucciones:

double cuad = Math.PI * Math.PI;

Pág. 20
Programación en Java

System.out.printf(“El cuadrado de %1$.4f es %2$.2f”, Math.PI, cuad);

La salida producida en pantalla será:

El cuadrado de 3.1416 es 9.87

3.4.3 ENTRADA DE DATOS

La lectura de datos del exterior se gestiona a través de la clase InputStream. Un


objeto InputStream está asociado a un dispositivo de entrada, que en el caso de la
entrada estándar (el teclado) podemos acceder al mismo a través del atributo estático
in de la clase System.

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.

La lectura de datos mediante BufferedReader requiere seguir los siguientes pasos en el


programa:

1. Crear objeto InputStreamReader. Este objeto permite convertir los bytes


recuperados del stream de entrada en caracteres. Para crear un objeto de esta
clase, es necesario indicar el objeto InputStream de entrada, si la entrada es el
teclado este objeto lo tenemos referenciado en el atributo estático in de la clase
System:

InputStreamReader rd = new InputStreamReader(System.in);

2. Crear objeto BufferedReader. A partir del objeto anterior se puede construir un


BufferedReader que permita realizar la lectura de cadenas:

BufferedReader bf = new BufferedReader(rd);

3. Invocar al método readLine(). Este método devuelve todos los caracteres


introducidos hasta el salto de línea.

String s = bf.readLine();

En el siguiente ejemplo se pueden apreciar los pasos comentados anteriormente. Se


trata de un programa que solicita por teclado la introducción de una cadena de texto
para, posteriormente, mostrarla por consola.

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

BufferedReader bf = new BufferedReader(ir);


System.out.println("Introduzca su nombre: ");
// 3. Lee la línea de texto introducida
String s = bf.readLine();
System.out.println("Su nombre es: " + s);
}
}

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.

Creación de un objeto Scanner

Para tener acceso a estos datos de entrada, primeramente necesitamos crear un


objeto Scanner asociado al InputStream del dispositivo de entrada. En el caso del
teclado se utilizaría la instrucción:

Scanner sc = new Scanner(System.in);

La cadena de caracteres introducida por el teclado hasta la pulsación de la tecla “enter”


es dividida por el objeto scanner en un conjunto de bloques de caracteres de longitud
variable, denominados tokens (figura 3.1). De forma predeterminada, el carácter
utilizado como separador de token es el espacio en blanco.

Figura 3.1 División de una cadena de entrada en tokens.

Utilizando los métodos de la clase Scanner es posible recuperar secuencialmente cada


uno de estos tokens, e incluso convertirlos implícitamente a un determinado tipo de
datos.

Pág. 22
Programación en Java

Métodos de la clase Scanner

Entre los métodos más destacables de la clase Scanner se encuentran:

 String next(). Devuelve el siguiente token.


 boolean hasNext(). Indica si existe o no un nuevo token para leer.
 xxx nextXxx(). Devuelve el siguiente token como un tipo básico, siendo xxx el
nombre de ese tipo básico. Por ejemplo, nextInt() para la lectura de un entero.
 boolean hasXxxNext(). Indica si existe o no un siguiente token del tipo
especificado, siendo xxx el nombre del tipo.
 void useDelimiter(String cad). Establece un nuevo delimitador de token.

El siguiente programa solicita al usuario su nombre y número de personal, mostrando


a continuación dichos datos en pantalla.

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

Recuperación de datos de un fichero externo

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.

En este caso, para crear el objeto Scanner deberíamos utilizar la expresión:

Scanner sc = new Scanner(File source);

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:

File f = new File(String pathname);

donde pathname representa la ruta del archivo y f la variable que almacena la


referencia al objeto File.

Por ejemplo, suponiendo un conjunto de números enteros almacenados en un archivo


datos.txt, donde cada número se encuentra separado del siguiente por una coma
(carácter “,”), el siguiente programa realizaría la suma de todos esos números y
mostraría el resultado en pantalla:

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

Una colección es un objeto que almacena un conjunto de referencias a otros objetos,


dicho de otra manera, es una especie de array de objetos. Sin embargo, a diferencia

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.

Java incluye en el paquete java.util un amplio conjunto de clases para la creación y


tratamiento de colecciones. Todas ellas proporcionan una serie de métodos para
realizar las operaciones básicas sobre una colección, como son:

 Añadir objetos a la colección.


 Eliminar objetos de la colección.
 Obtener un objeto de la colección.
 Localizar un objeto en la colección.
 Iterar a través de una colección.

A continuación, se estudiarán algunas de las clases de colección más significativas.

3.5.1 LA CLASE ARRAYLIST

Un ArrayList es una colección basada en índices en la que cada objeto de la misma


tiene asociado un número (índice) según la posición que ocupa dentro de la colección,
siendo 0 la posición del primer objeto añadido.

Creación de un ArrayList

Para crear un objeto ArrayList utilizamos la expresión:

ArrayList var_array = new ArrayList();

Donde, var_array es la variable que contendrá la referencia al objeto ArrayList creado.

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.

Métodos de la clase ArrayList

Los principales métodos expuestos por esta clase son:

 boolean add(Object o). Añade un nuevo objeto a la colección y lo sitúa al final de


la misma, devolviendo el valor true. Los objetos añadidos pueden ser de cualquier
tipo, no siendo necesario que todos pertenezcan a la misma clase:

ArrayList v = new ArrayList();


v.add(“hola”); //añade la cadena en la primera posición del array
v.add(6); //añade el número en la segunda posición

 void add(int índice, Object o). Añade un objeto al ArrayList en la posición


especificada por índice, desplazando hacia adelante el resto de los elementos de la
colección.
 Object get(int indice). Devuelve el objeto que ocupa la posición indicada. Hay que
tener en cuenta que el tipo de devolución es Object, por tanto, para guardar la

Pág. 25
Programación en Java

referencia al objeto devuelto en una variable de su tipo será necesario realizar una
conversión explicita:

ArrayList v = new ArrayList();


v.add(“texto”);
//Conversión explicita a String
String s = (String) v.get(0);

En el caso de que se almacenen objetos numéricos, es necesario recordar que la


llamada a get() devuelve el objeto de envoltorio, y no el número:

ArrayList v = new ArrayList();


v.add(6);
//Recupera el objeto numérico
int i = (Integer) v.get(0);

 Object remove(int indice). Elimina de la colección el elemento que ocupa la


posición indicada, desplazando hacia atrás los elementos de las posiciones
siguientes. Devuelve el objeto eliminado.
 void clear(). Elimina todos los elementos de la colección.
 int indexOf(Object o). Localiza en el ArrayList el objeto indicado como parámetro,
devolviendo su posición. En caso de que el objeto no se encuentre dentro de la
colección, la llamada a este método devolverá el valor -1.
 int size(). Devuelve el número de elementos almacenados en la colección.
Utilizando este método conjuntamente con get(), se puede recorrer la colección
completa. El siguiente método realiza el recorrido de un ArrayList de cadenas para
mostrar su contenido en pantalla:

public void muestraArray(ArrayList v){


for(int i = 0; i < v.size(); i++) {
System.out.println((String)v.get(i));
}
}

Además, se puede utilizar la variante for-each para simplificar el recorrido de


colecciones, así el método anterior quedaría:

public void muestraArray(ArrayList v){


for(Object ob : v) {
System.out.println((String)ob);
}
}

3.5.2 LA CLASE HASHTABLE

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.

La utilización de colecciones basadas en claves resulta útil en aquellas aplicaciones en


las que se requiera realizar búsquedas de objetos a partir de un dato que lo identifica.
Por ejemplo, si se va a gestionar una colección de objetos de tipo “Empleado”, puede
resultar más practico almacenarlos en un hashtable, asociándoles como clave el “DNI”,
que guardarlos en un ArrayList en el que a cada empleado se le asigna un índice según
el orden de almacenamiento.

Creación de un Hashtable

La creación de un objeto hashtable se realiza utilizando el constructor sin parámetros


de la clase:

Hashtable var_objeto = new Hashtable();

Métodos de la clase Hashtable

Los principales métodos expuestos por la clase Hashtable para manipular la colección
son los siguientes:

 Object put(Object key, Object valor). Añade a la colección el objeto valor,


asignándole la clave especificada por key. En caso de que exista esa clave en la
colección, el objeto que tenía asignada esa clave se sustituye por el nuevo objeto
valor, devolviendo el objeto sustituido. Por ejemplo, el siguiente código:

Hashtable hs = new Hashtable();


hs.put(“a21”,”pepito”);
System.out.println(“Antes se llamaba ” + hs.put(“a21”,”luis”));

Mostrará en pantalla

Antes se llamaba pepito

 boolean containsKey(Object key). Indica si la clave especificada existe o no en la


colección.
 Object get(Object key). Devuelve el valor que tiene asociada la clave que se indica
en el parámetro. En caso de que no exista ningún objeto con esa clave asociada,
devolverá null.
 Object remove(Object key). Elimina de la colección el valor cuya clave se
especifica en el parámetro. En caso de que no exista ningún objeto con esa clave,
no hará nada y devolverá null, si existe, eliminará el objeto y el mismo será
devuelto por el método.
 int size(). Devuelve el número de objetos almacenados en la colección.
 Enumeration keys(). Devuelve un objeto enumeration que permite iterar sobre el
conjunto de claves.

Pág. 27
Programación en Java

Iteración de un Hashtable: la interfaz Enumeration

Al no estar basado en índices, un Hashtable no se puede recorrer utilizando una


instrucción for con una variable que recorra las posiciones de los objetos. Esto no
significa que no se pueda iterar sobre un Hashtable, pues esto se logra a través de un
objeto Enumeration.

Enumeration es un objeto que implementa la interfaz java.util.Enumeration. Una


interfaz dispone de una serie de métodos que pueden ser aplicados sobre los objetos
que las implementan. Los métodos proporcionados por la interfaz Enumeration
permiten recorrer una colección de objetos asociada y acceder a cada uno de sus
elementos. En el caso concreto del método keys() de la clase Hashtable, el objeto
Enumeration devuelto nos permite recorrer la colección de claves del Hashtable.

La interfaz Enumeration dispone de los siguientes métodos:

 Object nextElement(). La llamada a este método sobre un objeto Enumeration,


provoca que éste pase a apuntar al siguiente objeto de la colección, devolviendo el
nuevo objeto apuntado.
 boolean hasMoreElements(). Indica si hay más elementos por recorrer en la
colección. Cuando el objeto Enumeration esté apuntando al último elemento, la
llamada a este método devolverá false.

Con estos dos métodos, utilizando un bucle while, se puede acceder a todos los
elementos de la colección asociada al Enumeration.

El siguiente método recibe como parámetro un objeto Hashtable en el que se han


almacenado objetos String con claves asociadas de tipo String, su misión consiste en
mostrar en pantalla todos los valores almacenados, para lo que tendríamos que
escribir el siguiente código:

public void muestraDatos(Hashtable tb) {


String valor, clave;
Enumeration e = tb.keys();
while (e.hasMoreElements()) {
clave = (String) e.nextElement();
//obtiene el objeto a partir de la clave
valor = (String) e.get(clave);
System.out.println(valor);
}
}

3.5.3 GENÉRICOS

Después de haber analizado el funcionamiento de las colecciones y de haber estudiado


algunas de las clases más significativas en este terreno, es hora de abordar una de las
mejoras más significativas del lenguaje Java y que simplifica algunos de los aspectos
relativos al tratamiento de colecciones en los programas. Se trata de los genéricos.

Pág. 28
Programación en Java

El problema de las colecciones de tipo Object

Para comprender en que consiste esta característica, analicemos de nuevo el


comportamiento de las colecciones a la hora de almacenar y recuperar los objetos.
Como ya se explicó, debido a que las colecciones gestionan los objetos a través del
tipo Object, cada vez que se intenta recuperar un objeto de la colección es necesario
realizar una conversión explicita al tipo específico para poderlo manipular
posteriormente.

Aparte de la incomodidad de las conversiones explicitas en la recuperación de objetos,


la utilización de Object como tipo común tiene como consecuencia que el compilador
no realice ninguna comprobación de tipo, ni al agregar el objeto a la colección, ni al
recuperarlo, haciendo que el código sea inseguro.

Utilización de genéricos con colecciones

La utilización de genéricos proporciona un mecanismo para notificar al compilador el


tipo de objeto que va a ser almacenado en la colección, esto supone dos mejoras
respecto al funcionamiento tradicional de las colecciones:

 Cualquier instrucción que intente almacenar en la colección un objeto de un tipo


que no sea el especificado provocará un error de compilación.
 Dado que se conoce el tipo de objeto almacenado en la colección, no será necesario
realizar una conversión explicita al realizar su recuperación.

Para especificar el tipo de objetos de una colección utilizando genéricos, se debe


declarar la variable colección utilizando la siguiente expresión:

tipo_coleccion <tipo_objeto> variable = new tipo_coleccion <tipo_objeto>();

Siendo tipo_coleccion la clase de colección utilizada y tipo_objeto la clase de los


objetos. Por ejemplo, para declarar un ArrayList de cadenas de caracteres seria:

ArrayList <String> lista = new ArrayList <String> ();

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:

ArrayList <String> lista = new ArrayList <String>();


lista.add(“cadena de prueba”);
lista.add(“segunda cadena”);
:
for (int i = 0; i < lista.size(); i++) {
String s1 = lista.get(i); //ausencia del casting
System.out.println(s1);
}

Pág. 29
Programación en Java

Si en el código anterior se hubiese intentado añadir un objeto no String a la colección,


el compilador habría generado un error de compilación en la instrucción de llamada al
método add().

En el caso de una colección de tipo Hashtable, donde además de los elementos de la


colección se almacenan claves asociadas a cada elemento, la utilización de genéricos
permite especificar tanto el tipo de elemento como el de la clave:

Hashtable <tipo_clave, tipo_elemento> variable;

3.6 ORGANIZACIÓN DE PROGRAMAS EN CLASES

Como ya se ha mencionado, un programa Java puede estar constituido por varias


clases. De hecho, esta es la línea que debe seguirse normalmente en el desarrollo de
una aplicación, siguiendo una organización modular en la que la “lógica de la
aplicación” (cálculos y procesado de la información, acceso a bases de datos, gestión
de eventos en aplicaciones gráficas, etc.) se distribuya en una o varias clases mientras
que la clase que contiene al método main() se limitara a tareas de entrada/salida, esto
es, captura de datos y presentación de resultados.

La anterior organización presenta diversas ventajas a la hora de desarrollar una


aplicación, entre ellas:

 Reutilización de código. La especialización de las clases de lógica en la


resolución de un determinado problema, abstrayéndose de los detalles relativos a
la entrada/salida de datos, facilita la reutilización de estas clases en otros
programas.
 Desarrollo modular independiente. La separación de tareas entre las distintas
clases permite acometer los desarrollos de las mismas de manera independiente,
sin que la creación de una clase esté condicionada por las otras.
 Escalabilidad. Esta se refiere a la facilidad para ampliar la funcionalidad de una
aplicación.

Pág. 30
Programación en Java

4 PROGRAMACIÓN ORIENTADA A OBJETOS CON JAVA

Como ya se ha mencionado, el lenguaje Java es totalmente orientado a objetos, esto


significa que se puede implementar en una aplicación Java todas las características y
beneficios que proporciona este modelo de programación, entre ellas, la encapsulación,
la herencia y el polimorfismo.

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.

4.1 EMPAQUETADO DE CLASES

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.

Si se desea crear una estructura de paquetes, debe procederse como a continuación se


indica:

 Creación de los directorios. Un paquete no es más que un directorio en el que


estarán ubicados los archivos .class con las clases compiladas, así pues, lo primero
que habrá que hacer es crear esos directorios dentro del directorio de trabajo.
Dado que todas las clases, organizadas en sus correspondientes paquetes, van a
estar localizadas en este directorio de trabajo, su dirección deberá ser incluida en la
variable de entorno CLASSPATH.
 Empaquetado de las clases. Una vez creados los directorios, se procederá al
empaquetado de las clases, para ello se hará uso de la sentencia package en el
archivo de código fuente de la clase. La utilización de esta sentencia es la
siguiente:

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.

 Compilación. Finalmente, se situara cada uno de los archivos de código fuente en


su subdirectorio correspondiente, según el paquete al que vayan a pertenecer sus
clases.

4.2 MODIFICADORES DE ACCESO

Los modificadores de acceso se utilizan para definir la visibilidad de los miembros de


una clase (atributos y métodos) y de la propia clase.

En Java existen cuatro modificadores de acceso que, ordenados de menor a mayor


visibilidad, son:

Pág. 31
Programación en Java

 private. Cuando un atributo o método es definido como private, su uso está


restringido al interior de la clase, lo que significa que solamente puede ser utilizado
en el interior de su misma clase. Este modificador puede ser aplicado a métodos y
atributos, pero no a la clase.
 (ninguno). La no utilización de un modificador de acceso, proporciona al elemento
lo que se conoce como acceso por defecto. Si un elemento tiene acceso por
defecto, únicamente las clases de su mismo paquete tendrán acceso al mismo.
 protected. Se trata de un modificador de acceso empleado en la herencia. Un
método o atributo definido como protected en una clase puede ser utilizado por
cualquier otra clase de su mismo paquete y además, por cualquier subclase de ella,
independientemente del paquete en que esta se encuentre. Una clase no puede ser
protected, solo sus miembros.
 public. El modificador public ofrece el máximo nivel de visibilidad. Un elemento
public será visible desde cualquier clase, independientemente del paquete en que
se encuentre.

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.

Esa es precisamente la idea de la encapsulación: mantener los atributos de los objetos


como privados y proporcionar acceso a los mismos a través de métodos públicos
(métodos de acceso). Esta filosofía de programación proporciona grandes beneficios,
entre los que cabría destacar:

 Protección de datos “sensibles”.


 Facilidad y flexibilidad en el mantenimiento de las aplicaciones.

4.3.1 PROTECCIÓN DE DATOS

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.

Supongamos que desarrollamos la clase sin aplicar el concepto de encapsulación,


proporcionando acceso público a los atributos:

public class Rectangulo {


public int alto, ancho;
//Métodos de la clase
:

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:

Rectangulo r = new Rectangulo();


r.alto = -5;

Lógicamente, dimensiones con valores negativos no tienen sentido en una figura


geométrica, además de provocar resultados incoherentes en la ejecución de los
métodos de la clase.

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.

En el ejemplo de la clase Rectangulo, la encapsulación de los atributos podría


realizarse de la siguiente forma:

public class Rectangulo {


private int alto, ancho;
public void setAlto(int alto) {
if (alto > 0)
this.alto = alto;
}
public int getAlto() {
return this.alto;
}
public void setAncho(int ancho) {
if (ancho > 0)
this.ancho = ancho;
}
public int getAncho() {
return this.ancho;
}
//Otros métodos de la clase
:
}

Se sigue el convenio setNombre_atributo para nombrar al método de acceso que


permite la escritura del atributo y getNombre_atributo para el método de lectura.

La creación de objetos Rectangulo y asignación de valores a los atributos sería de la


siguiente forma:

Rectangulo r = new Rectangulo();


r.setAlto(3);

Pág. 33
Programación en Java

r.setAncho(6);

Así, una instrucción como la siguiente:

r.setAlto(-5);

provocaría que la variable alto permaneciese invariable, impidiendo que pueda tomar
un valor negativo.

Gracias al control que se hace en el método de acceso antes de almacenar un valor en


el atributo, se evita que dicho atributo se corrompa.

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;

4.3.2 FACILIDAD EN EL MANTENIMIENTO DE LA CLASE

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:

public void setAlto(int alto) {


if(alto > 1)
this.alto = alto;
}

Los detalles de implementación quedan ocultos, manteniendo la interfaz (el formato y


utilización del método no cambian) por lo que el código que hace uso de esta clase no
tendrá que modificarse.

4.3.3 CLASES DE ENCAPSULACIÓN

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.

4.4 SOBRECARGA DE MÉTODOS

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.

La gran ventaja de la sobrecarga de métodos es que, si tenemos varios métodos que


van a realizar la misma operación (por ejemplo, convertir un tipo básico en una
cadena), no es necesario asignarle un nombre diferente a cada uno, sino que es
posible llamarlos igual a todos ellos.

Para que un método pueda sobrecargarse es imprescindible que se dé la siguiente


condición: cada versión del método debe distinguirse de las otras en el número o tipo
de parámetros. El tipo de devolución puede ser o no el mismo.

Los siguientes son ejemplos validos de sobrecarga:

public void calculo(int k) {…}

public void calculo(String s) {…}

public long calculo(int k, Boolean b) {…}

La sobrecarga de métodos nos permite, por tanto, disponer de diferentes versiones de


un método para llevar a cabo una determinada operación. A la hora de invocar a un
método sobrecargado en un objeto, el compilador identificará la versión del método
que se quiere invocar por los argumentos utilizados en la llamada.

4.5 CONSTRUCTORES

4.5.1 DEFINICIÓN Y UTILIDAD

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:

public class Punto {


private int x, y;
public int getX() {

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);
}
}

Si quisiéramos crear un punto a partir de esta clase y posteriormente dibujarlo


(llamada al método dibujar()), deberíamos hacer algo como esto:

Punto pt = new Punto();


pt.setX(6);
pt.setY(10);
pt.dibujar();

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.

Un constructor es un método especial que es ejecutado en el momento en que se crea


un objeto de la clase (cuando se llama al operador new). Podemos utilizar los
constructores para añadir aquellas tareas que deban realizarse en el momento en que
se crea un objeto de la clase, como por ejemplo, la inicialización de los atributos.

A la hora de crear un constructor, hay que tener en cuenta las siguientes reglas:

 El nombre del constructor debe ser el mismo que el de la clase.


 El constructor no puede tener tipo de devolución, ni siquiera void.
 Los constructores se pueden sobrecargar.
 Toda clase debe tener, al menos, un constructor.

En el siguiente listado se muestra la clase Punto con dos constructores para la


inicialización de las coordenadas:

public class Punto {


private int x, y;
public Punto(int x, int y) {
this.x = x;

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:

Punto pt = new Punto(4, 6); //llama al primer constructor


pt.dibujar();

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:

Las coordenadas son 4, 6

Si las dos coordenadas fuesen a tomar el mismo valor, podríamos haber optado por el
segundo constructor:

Punto pt = new Punto(5); //x e y tomarán el valor 5


pt.dibujar();

4.5.2 CONSTRUCTORES POR DEFECTO

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?

En este caso el compilador de Java añadirá un constructor a la clase, denominado


constructor por defecto, cuyo aspecto será:

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:

Punto pt = new Punto();

Pág. 37
Programación en Java

provocará un error de compilación. Este hecho es importante tenerlo en cuenta cuando


se está desarrollando una clase, ya que si además de poder inicializar atributos en la
creación de los objetos se desea ofrecer la posibilidad de crear objetos de la clase sin
realizar ningún tipo de inicialización, será necesario codificar explícitamente un
constructor por defecto en la clase. Así pues, para que la operación anterior pueda
realizarse, la clase Punto deberá ser:

public class Punto {


private int x, y;
public Punto() {
}
public Punto(int x, int y) {
this.x = x;
this.y = y;
}
public Punto(int v) {
this.x = v;
this.y = v;
}
//resto del código de la clase
}

4.6 HERENCIA

La herencia representa uno de los conceptos más importantes y potentes de la


Programación Orientada a Objetos.

Concepto de herencia

Se puede definir la herencia como la capacidad de crear clases que adquieran de


manera automática los miembros (atributos y métodos) de otras clases que ya existen,
pudiendo al mismo tiempo añadir atributos y métodos propios.

Ventajas de la herencia

Entre las principales ventajas que ofrece la herencia en el desarrollo de aplicaciones,


están:

 Reutilización de código. En aquellos casos donde se necesite crear una clase


que, además de otros propios, deba incluir lo métodos definidos en otra, la
herencia evita tener que reescribir todos esos métodos en la nueva clase.
 Mantenimiento de aplicaciones existentes. Utilizando la herencia, si tenemos
una clase con una determinada funcionalidad, no necesitamos modificar la clase
existente (la cual se puede seguir utilizando para el tipo de programa para la
que fue diseñada) sino que podemos crear una clase que herede a la primera,
adquiriendo toda su funcionalidad y añadiendo la suya propia.

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.

4.6.1 NOMENCLATURA Y REGLAS

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).

Figura 4.1 Relación de herencia.

Hay reglas básicas sobre la herencia en Java que hay que tener presentes y quedan
ilustradas en la figura 4.2:

 En Java no está permitida la herencia múltiple, es decir, una subclase no puede


heredar más de una clase.
 Si es posible una herencia multinivel, es decir, A puede ser heredada por B y C
puede heredar B.
 Una clase puede ser heredada por varias clases.

Figura 4.2 Relaciones de herencia posibles y no posibles.

Pág. 39
Programación en Java

4.6.2 RELACIÓN "ES UN"

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.

Creación de herencia en Java

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:

public class subclase extends superclase


{
//código de la subclase
}

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:

public class PuntoColor extends Punto {


private String color;
//resto de la clase
}

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:

public class NombreClase extends Object {


//código de la clase
}

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.

Ejecución de constructores con la herencia

Hay que hacer especial mención al comportamiento de los constructores de la


superclase y la subclase cuando se va a crear un objeto de esta última.

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();
}
}

Aparecerá en pantalla lo siguiente:

Constructor de la superclase

Constructor de la subclase

La explicación a esta situación al tenemos en el hecho de que el compilador Java


añade, como primera línea de código en todos los constructores de una clase, la
siguiente instrucción:

super();

Instrucción que provoca una llamada al constructor sin parámetros de la superclase.


Los constructores por defecto también incluyen esta instrucción. Aquellas clases que no

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.

Si en vez de llamar al constructor sin parámetros quisiéramos invocar a cualquier otro


constructor de la superclase, deberíamos hacerlo explícitamente, añadiendo como
primera línea del constructor de la subclase la instrucción:

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:

public class PuntoColor extends Punto {


private String color;
public PuntoColor(int x, int y, String c) {
super(x, y); //se ejecuta el constructor de Punto
color = c; //inicialización de atributos propios
}
:
}

Es necesario volver a recalcar que la llamada al constructor de la superclase, debe ser


la primera línea de código del constructor de la subclase, de no hacerse así se
producirá un error de compilación.

Otra posibilidad es que, en vez de incluir una llamada al constructor de la superclase,


se desee invocar a otro de los constructores de su clase. En este caso, se utilizará la
palabra reservada this en vez de super, siendo el constructor invocado el que incluirá
la llamada al constructor de la superclase:

public class PuntoColor extends Punto {


private String color;
public PuntoColor(int x, int y, String c) {
super(x, y); //se ejecuta el constructor de Punto
color = c; //inicialización de atributos propios
}
public PuntoColor(int cord, String c) {
this(cord, cord, c);
}
:
}

4.6.3 MÉTODOS Y ATRIBUTOS PROTEGIDOS

Existe un modificador de acceso aplicable a atributos y métodos de una clase, pensado


para ser utilizado con la herencia. Se trata del modificador protected.

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.

Por ejemplo, dada la siguiente definición de una clase Persona:

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.

4.6.4 SOBRESCRITURA DE MÉTODOS

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:

 Cuando se sobrescribe un método en una subclase, éste debe tener


exactamente el mismo formato que el método de la superclase que sobrescribe.
Esto significa que deben llamarse igual, tener los mismos parámetros y mismo
tipo de devolución:

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);
}
}

Hay que tener presente que si al intentar sobrescribir un método en una


subclase se mantiene el mismo nombre y se modifican los parámetros, el nuevo
método no sobrescribe al de la superclase pero tampoco se produce un error de
compilación, ya que estaríamos ante un caso de sobrecarga de métodos: dos
métodos con el mismo nombre y distintos parámetros.

 El método sobrescrito puede tener un modificador de acceso menos restrictivo


que el de la superclase. Por ejemplo, el método de la superclase puede ser
protected y la versión sobrescrita puede ser public, pero nunca uno más
restrictivo.
 Para llamar desde el interior de la subclase a la versión original del método de
la superclase, debe utilizarse la expresión:

super.nombre_metodo(argumentos);

Si no se utiliza super delante del nombre del método, se llamará a la versión


sobrescrita en la clase.

 Por otro lado, si se quiere evitar que un método pueda ser sobrescrito deberá
declararse con el modificador final:

public void final método(…) {…}

4.7 CLASES ABSTRACTAS

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.

Un método se define como abstracto porque en ese momento no se conoce cómo ha


de ser su implementación, serán las subclases de la clase abstracta las responsables
de darle “cuerpo” mediante la sobrescritura del mismo.

4.7.2 SINTAXIS Y CARACTERÍSTICAS

La sintaxis para la creación de una clase abstracta es la siguiente:

public abstract class nombreClase {


public abstract tipo nombreMetodo(argumentos);
//otros métodos
}

Obsérvese como tanto en la declaración de la clase como en la del método abstracto,


se debe utilizar el modificador abstract. Se puede ver también como los métodos
abstractos son métodos sin cuerpo, su declaración finaliza con “;” y, dado que no
incluyen código, no se utilizan llaves.

Sobre la creación y utilización de clases abstractas hay que tener en cuenta los
siguientes aspectos:

 Una clase abstracta puede tener métodos no abstractos.


 No es posible crear objetos de una clase abstracta.
 Las subclases de una clase abstracta están obligadas a sobrescribir todos los
métodos abstractos que heredan.
 Una clase abstracta puede tener constructores.

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

public class Triangulo extends Figura {


private int base, altura;
public Triangulo(int b, int a, String c) {
super(c);
base = b;
altura = a;
}
public double area() {
return base * altura / 2;
}
public int getBase() {
return base;
}
public int getAltura() {
return altura;
}
}
//Clase Circulo
public class Circulo extends Figura {
private int radio;
public Circulo(int r, String c) {
super(c);
radio = r;
}
public double area() {
return Math.PI * radio * radio;
}
public int getRadio() {
return radio;
}
}

4.8 POLIMORFISMO

El polimorfismo se basa en gran medida en los conceptos aprendidos anteriormente,


de hecho, es una de las principales aplicaciones de la herencia y supone el principal
motivo de la existencia de las clases abstractas.

Pero antes de definir el polimorfismo, es necesario conocer un fenómeno fundamental


sobre la asignación de objetos a variables.

4.8.1 ASIGNACIÓN DE OBJETOS A VARIABLES DE SU SUPERCLASE

En Java, es posible asignar un objeto de una clase a una variable de su superclase.


Esto es aplicable, incluso, cuando la superclase es una clase abstracta.

Por ejemplo, dada una variable de tipo Figura:

Figura f;

Pág. 46
Programación en Java

Es posible asignar a esta variable un objeto Triangulo:

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().

4.8.2 DEFINICIÓN DE POLIMORFISMO

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

De lo anterior se desprende un hecho muy interesante: la misma instrucción f.area()


permite llamar a distintos métodos area(), dependiendo del objeto almacenado en la
variable f.

En esto consiste precisamente el polimorfismo, que puede definirse de la siguiente


manera: “La posibilidad de utilizar una misma expresión para invocar a diferentes
versiones de un mismo método, determinando en tiempo de ejecución la versión del
método que se debe ejecutar”.

4.8.3 VENTAJAS DE LA UTILIZACIÓN DEL POLIMORFISMO

A partir de la definición de polimorfismo, y del ejemplo presentado con las figuras, es


evidente que la principal ventaja que éste ofrece es la reutilización de código. El hecho
de que utilizando una variable de una clase pueda escribirse una única instrucción que
sirva para invocar a diferentes versiones del mismo método, permitirá agrupar
instrucciones de este tipo en un bloque de código para que pueda ser ejecutado con
cualquier objeto de las subclases.

Pág. 47
Programación en Java

Así pues, volviendo a la pregunta planteada en el apartado anterior sobre la utilidad de


asignar un objeto a una variable de su superclase para invocar a los métodos de éste,
la respuesta es: reutilización de código.

El siguiente programa ilustra un ejemplo de utilización de polimorfismo utilizando las


clase Figura, Triangulo y Circulo definidas anteriormente. Como se puede ver, además
de main(), la clase incluye un método (mostrar()) en el que se agrupan todas las
instrucciones para visualizar por pantalla los datos de cualquier Figura que se le pase
como parámetro:

public class GestionFiguras {


public static void main(String[] args) throws IOException{
// Triangulo
mostrar(new Triangulo(5,7,"verde"));
// Circulo
mostrar(new Circulo(4,"azul"));
}
public static void mostrar(Figura f){
// Se puede utilizar con cualquier subclase de Figura
System.out.println("El color de la figura es: "+f.getColor());
System.out.println("El área de la figura es: "+f.area());
}
}

El resultado de la ejecución de este programa generará la siguiente salida por pantalla:

El color de la figura es: verde


El área de la figura es: 17.5
El color de la figura es: azul
El área de la figura es: 50.27

4.8.4 TIPOS DE RETORNO COVARIANTES

Al hablar de la sobrescritura de métodos en una subclase, dijimos que una de las


condiciones que debe cumplir la nueva versión del método era la de mantener
invariable el tipo de retorno definido por el método original.

Pero a partir de la versión 1.5 de Java, es posible modificar el tipo de retorno al


sobrescribir un método, siempre y cuando el nuevo tipo sea un subtipo (subclase) del
original. Por ejemplo, supóngase que la clase Figura utilizada en los ejemplos
anteriores tuviera declarado un método abstracto getNewFigura() que devuelve una
copia del propio objeto Figura:

abstract Figura getNewFigura();

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

public Figura getNewFigura() {


return new Circulo(radio, getColor());
}

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:

Circulo cir2 = (Circulo) cir.getNewFigura();

Como se puede observar, dado que getNewFigura() devuelve un tipo Figura, es


necesario realizar una conversión explicita al subtipo de Figura con que se está
trabajando.

Sin embargo, a partir de la versión 1.5 el método getNewFigura() puede sobrescribirse


de la siguiente manera:

public Circulo getNewFigura() {


return new Circulo(radio, getColor());
}

Esto elimina la necesidad de realizar conversiones explicitas a la hora de obtener


nuevas copias de objetos Circulo a través de este método:

Circulo cir2 = cir.getNewFigura();

4.9 INTERFACES

4.9.1 DEFINICIÓN DE INTERFAZ

Estrictamente hablando, una interfaz es un conjunto de métodos abstractos y de


constantes públicos definidos en un archivo .java. Una interfaz es similar a una clase
abstracta llevada al límite, en la que todos sus métodos son abstractos.

La finalidad de una interfaz es la definir el formato que deben de tener determinados


métodos que han de implementar ciertas clases.

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

4.9.2 DEFINICIÓN DE UNA INTERFAZ

Una interfaz se define mediante la palabra interface, utilizando la siguiente sintaxis:

[public] interface Nombre_interfaz {


tipo metodo1(argumentos);
tipo metodo2(argumentos);
:
}

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.

4.9.3 IMPLEMENTACIÓN DE UNA INTERFAZ

Como ya se ha mencionado, el objetivo de las interfaces es proporcionar un formato


común de métodos para las clases. Para forzar a que una clase defina el código para
los métodos declarados en una determinada interfaz, la clase deberá implementar la
interfaz.

En la definición de una clase, se utiliza la palabra implements para indicar que interfaz
se ha de implementar:

public class MiClase implements MiInterfaz {...}

Por ejemplo,

public class Triangulo implements Operaciones{


public void rotar() {
//implementación del método
}
public String serializar() {
//implementación del método
}
}

Sobre la implementación de interfaces, se ha de tener en cuenta lo siguiente:

 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á:

public class MiClase implements Interfaz1, Interfaz2, ... {...}

 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:

public class MiClase extends Superclase implements Interfaz1, Interfaz2,


... {...}

 Una interfaz puede heredar otras interfaces. No se trata realmente de un caso


de herencia como tal, pues lo único que adquiere la subinterfaz es el conjunto
de métodos abstractos existentes en la superinterfaz. La sintaxis utilizada es la
siguiente:

public interface MiInterfaz extends Interfaz1, Interfaz2, ... {...}

4.9.4 INTERFACES Y POLIMORFISMO

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:

Operaciones op = new Triangulo();


op.rotar();
op.serializar();

Esta capacidad, unida a su flexibilidad, hacen de las interfaces una estructura de


programación tremendamente útil.

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.

Durante este capítulo se estudiarán con detalle las excepciones. Analizaremos su


funcionamiento y se presentarán las principales clases de excepciones existentes,
además de conocer los mecanismos para su captura, propagación y creación.

5.1 EXCEPCIONES Y ERRORES

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.

Mediante la captura de excepciones, Java proporciona un mecanismo que permite al


programa sobreponerse a estas situaciones, pudiendo el programados decidir las
acciones a realizar para cada tipo de excepción que pueda ocurrir.

Además de excepciones, en un programa Java pueden producirse errores. Un error


representa una situación anormal irreversible, como por ejemplo un fallo de la
máquina virtual. Por regla general, un programa no deberá intentar recuperarse de un
error, dado que son situaciones que se escapan al control del programador.

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.

5.2 CLASES DE EXCEPCIÓN

Al producirse una excepción en un programa, se crea un objeto de la subclase de


Exception a la que pertenece la excepción. Este objeto puede ser utilizado por el
programa durante el tratamiento de la excepción para obtener información de la
misma.

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

Figura 5.1 Jerarquía de clases de excepción.

5.3 TIPOS DE EXCEPCIONES

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

5.3.1 EXCEPCIONES MARCADAS

Son aquellas cuya captura es obligatoria. Normalmente, este tipo de excepciones se


producen al invocar a ciertos métodos d determinadas clases y son generadas
(lanzadas) desde el interior de dichos métodos como consecuencia de algún fallo
durante la ejecución de los mismos.

Todas las clases de excepciones, salvo RuntimeException y sus subclases, pertenecen


a este tipo.

Un ejemplo de excepción marcada es IOException. Está excepción es lanzada por el


método readLine() de la clase BufferedReader cuando se produce un error durante la
operación de lectura, lo que obliga al programa que va a hacer uso de dicho método a
captura la excepción, tal y como veremos más adelante.

Si en un bloque de código se invoca a algún método que pueda provocar una


excepción marcada y esta no se captura, el programa no compilará.

Pág. 53
Programación en Java

Declaración de una excepción

Los métodos que pueden provocar excepciones marcadas, deben declarar estas en la
definición del método.

Para declarar una excepción se utiliza la palabra throws, seguida de la línea de


excepciones que el método puede provocar:

public String readLine() throws IOException

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.

5.3.2 EXCEPCIONES NO MARCADAS

Pertenecen a este grupo todas las excepciones de tiempo de ejecución, es decir,


RuntimeException y todas sus subclases.

No es obligatorio capturar dentro de un programa Java una excepción no marcada, el


motivo es que gran parte de ellas se producen como consecuencia de una mala
programación, por lo que la solución ni debe pasar por preparar el programa para que
sea capaz de recuperarse ante una situación como esta, sino por evitar que se
produzca. Tan sólo las excepciones de tipo ArithmeticException es recomendable
capturarlas.

Si durante la ejecución de un programa Java se produce una excepción y esta no es


capturada, la máquina virtual provoca la finalización inmediata del mismo, enviando a
la consola el volcado de pila con los datos de la excepción. Estos volcados de pila
permiten al programador detectar fallos de programación durante la depuración del
mismo.

5.4 CAPTURA DE EXCEPCIONES

Como ya se apuntó anteriormente, en el momento en que se produce una excepción


en un programa, se crea un objeto de la clase de excepción correspondiente y se lanza
a la línea de código donde la excepción tuvo lugar.

El mecanismo de captura de excepciones de Java, permite atrapar el objeto de


excepción lanzado por la instrucción e indicar las diferentes acciones a realizar según
la clase de excepción producida.

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.

5.4.1 LOS BLOQUES TRY, CATCH Y FINALLY

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.

La sintaxis para la utilización de estas instrucciones se indica a continuación:

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.

Sobre la utilización de los bloques catch, se debe tener en cuenta lo siguiente:

 Se pueden definir tantos bloques catch como se considere necesario. Cada


bloque servirá para tratar un determinado tipo de excepción, no pudiendo haber
dos o más catch que tengan declarada la misma clase de excepción.
 Un bloque catch sirve para capturar cualquier excepción que se corresponda
con el tipo declarado o cualquiera de sus subclases.
 Aunque haya varios posibles catch que puedan capturar una excepción, sólo
uno de ellos será ejecutado cuando esta se produzca. La búsqueda del bloque
catch para el tratamiento de la excepción lanzada se realiza de forma
secuencial, de modo que el primer catch coincidente será el que se ejecutará.
Una vez terminada la ejecución del mismo, el control del programa se
transferirá al bloque finally o, si no existe, a la instrucción siguiente al último
bloque catch, independientemente de que hubiera o no más catch coincidentes.
 Del listado anterior se deduce otro punto importante a tener en cuenta en el
tratamiento de excepciones: tras la ejecución de un bloque catch, el control del
programa nunca se devuelve al lugar donde se ha producido la excepción.

Pág. 55
Programación en Java

 En el caso de que existan varios catch cuyas excepciones estén relacionadas


por la herencia, los catch más específicos debes estar situados por delante de
los más genéricos.
 Si se produce una excepción no marcada para la que no se ha definido bloque
catch, esta será propagada por la pila de llamadas hasta encontrar algún punto
en el que se trate la excepción. De no existir un tratamiento para la misma, la
máquina virtual abortará la ejecución del programa y enviara un volcado de pila
a la consola.
 Los bloques catch son opcionales. Siempre que exista un bloque finally, la
creación de bloques catch después de un try es opcional. Si no se cuenta con un
bloque finally, entonces es obligatorio disponer de, al menos, un bloque catch.

Finally

Su uso es opcional. El bloque finally se ejecutará tanto si se produce una excepción


como si no, garantizando así que un determinado conjunto de instrucciones siempre
sean ejecutadas.

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.

Si no se produce excepción alguna en el interior de try, el bloque finally se ejecutará


tras la última instrucción del try.

5.4.2 PROPAGACIÓN DE UNA EXCEPCIÓN

Anteriormente indicábamos que si en el interior de un método se produce una


excepción que no es capturada, bien porque no está dentro de un try o bien porque no
existe un catch para su tratamiento, ésta se propagará por la pila de llamadas.

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

Figura 5.2 Propagación de una excepción.

En el ejemplo anterior, el método main() es el que captura la excepción producida en


imprime(). Si en main() se hubiera optado por propagar también la excepción, al ser el
último método de la pila de llamadas ésta se propagará a la máquina virtual, cuyo
comportamiento por defecto será interrumpir la ejecución del programa y generar un
volcado de pila en la consola.

5.5 LANZAMIENTO DE UNA EXCEPCIÓN

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.

Para lanzar una excepción desde código utilizamos la expresión:

throw objeto_excepcion;

donde objeto_excepcion es un objeto de alguna subclase de Exception.

El ejemplo siguiente muestra un caso práctico en el que un método, encargado de


realizar una operación de extracción de dinero en una cuenta bancaria lanza una
excepción cuando no se dispone de saldo suficiente para realizar la operación:

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.

Las excepciones también se pueden relanzar desde un catch:

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.

5.6 MÉTODOS PARA EL CONTROL DE UNA EXCEPCIÓN

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.

Los métodos más importantes son:

 void getMessage(). Devuelve un mensaje de texto asociado a la excepción,


dependiendo del tipo de objeto de excepción sobre el que se aplique.
 void printStackTrace(). Envía a la consola el volcado de pila asociado a la
excepción.

Pág. 58
Programación en Java

 void printStackTrace(PrintStream s). Esta versión de printStackTrace()


permite enviar el volcado de pila a un objeto PrintStream cualquiera, por
ejemplo, un fichero log.

5.7 CLASES DE EXCEPCIÓN PERSONALIZADAS

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.

Para el ejemplo de la cuenta bancaria, podríamos definir la siguiente clase de


excepción:

class SaldoInsuficienteException extends Exception {


public SaldoInsuficienteException(String mensaje) {
super(mensaje);
}
}

La cadena de texto recibida por el constructor permite personalizar el mensaje de error


obtenido al llamar al método getMessage(), para ello, es necesario suministrar dicha
cadena al constructor de la clase Exception.

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;
}
}

//clase que representa la excepción personalizada


class SaldoInsuficienteException extends Exception {
public SaldoInsuficienteException(String mensaje) {
super(mensaje);
}
}

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.

6.1 INFORMACIÓN SOBRE FICHEROS Y DIRECTORIOS. LA CLASE FILE


Los ficheros o archivos y directorios del disco se representan de forma lógica en las
aplicaciones Java como objetos de la clase File.

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.

6.1.1 CREACIÓN DE UN OBJETO FILE


Existen diversas formas de crear un objeto File en función de cómo se indique la
localización del fichero o archivo que va a representar. El constructor File(String path)
permite construir un objeto File a partir de una ruta absoluta o relativa al directorio
actual:

File f = new File(“datos.txt”);


File f2 = new File(“/misubdirectorio”);

La creación del objeto no implica que exista el fichero o directorio indicado en la ruta.

6.1.2 MÉTODOS DE LA CLASE FILE


Una vez creado el objeto File asociado al fichero o directorio, podemos obtener
información del mismo aplicándole los siguientes métodos de la clase File:

 boolean canRead(). Indica si se puede o no leer el fichero.


 boolean canWrite(). Indica si se puede o no escribir en el fichero.
 boolean exists(). Indica si existe o no el fichero o directorio indicado en la ruta.
 String getName(). Devuelve el nombre del fichero sin el path.
 String getAbsolutePath(). Devuelve el path absoluto completo.

6.2 LECTURA DE UN FICHERO DE TEXTO


Muchas de las operaciones realizadas con ficheros en un programa tienen que ver con
la lectura de ficheros de texto.

Para recuperar cadenas de caracteres de un fichero, el paquete java.io proporciona dos


clases: FileReader y BufferedReader. A continuación veremos cómo se utilizan estas
clases, examinando los dos pasos que hay que seguir para la lectura de cadenas de un
fichero.

Pág. 61
Programación en Java

6.2.1 CREACIÓN DE UN OBJETO FILEREADER


Para poder recuperar información de un fichero de texto, es necesario primeramente
crear un objeto FileReader asociado al mismo. Un objeto FileReader representa un
fichero de texto “abierto” para la lectura de datos, capaz de adaptar la información
recuperada de éste a las características de una aplicación Java, mediante la
transformación de los bytes almacenados en el fichero en caracteres Unicode.

Se puede construir un objeto FileReader a partir de un objeto File existente o bien


proporcionando directamente la ruta del fichero:

FileReader (String path)


FileReader (File fichero)

La siguiente instrucción crearía un objeto FileReader a partir del objeto File de ejemplo
creado en el apartado anterior:

FileReader fr = new FileReader(f);

6.2.2 CREACIÓN DE UN OBJETO BUFFEREDREADER


Una vez creado el objeto FileReader, el segundo paso consiste en crear un objeto
BufferedReader, para lo cual utilizaremos el constructor

BufferedReader (Reader entrada)

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:

BufferedReader bf = new BufferedReader(fr);

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.

El siguiente programa muestra por pantalla el contenido completo de un hipotético fichero


llamado datos.txt:

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.

6.3 ESCRITURA EN FICHEROS DE TEXTO


La realización de esta operación también se lleva a cabo en dos pasos que implican la utilización
de dos clases: FileWriter y PrintWriter.

6.3.1 CREACIÓN DE UN OBJETO FILEWRITER


Como primer paso para escribir en un fichero de texto, es necesario construir un objeto
FileWriter que nos permita tener acceso al fichero en modo “escritura”. Para ello, podemos
utilizar uno de los siguientes constructores:

FileWriter(String path, boolean append)


FileWriter(File fichero, boolean append)

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).

6.3.2 CREACIÓN DEL OBJETO PRINTWRITER


La escritura sobre un fichero se realiza de la misma forma que la escritura en pantalla,
de hecho, se utiliza la misma clase PrintWriter. La diferencia está en que en el caso de
la consola o pantalla el objeto PrintWriter asociado se encontraba en System.out,
mientras que en un fichero de texto habrá que construir el objeto PrintWriter a partir
de un objeto FileWriter:
FileWriter fw = new FileWriter(“datos.txt”);
PrintWriter out = new PrintWriter(fr);

Una vez creado el objeto PrintWriter podemos utilizar los métodos print(), println() y
printf() para escribir en el fichero.

6.4 ESCRITURA DE DATOS PRIMITIVOS JAVA EN UN FICHERO


Las clases PrintWriter y BufferedReader están pensadas para escribir y recuperar,
respectivamente, cadenas de caracteres en un fichero.
Para almacenar datos en un fichero en forma de tipos básicos Java, el paquete java.io
proporciona las clases FileOutputStream y DataOutputStream.

6.4.1 CREACIÓN DE UN OBJETO FILEOUTPUTSTREAM


Este objeto es el equivalente al FileWriter utilizado en la escritura de cadenas de
caracteres. Como en el caso de éste, se puede crear un objeto FileOutputStream que
permita añadir información al fichero o sobrescribirla haciendo uso de los
constructores:

Pág. 63
Programación en Java

FileOutputStream(File fichero, boolean append)


FileOutputStream(String path, boolean append)

La clase FileOutputStream es una subclase de OutputStream, la cual representa un


stream o flujo de salida para la escritura de bytes.

6.4.2 CREACIÓN DE UN OBJETO DATAOUTPUTSTREAM


A partir del objeto FileOutputStream se puede crear un objeto DataOutputStream para
realizar la escritura de los datos. El constructor utilizado sería:
DataOutputStream(OutputStream)

La clase DataOutputStream proporciona métodos para escribir datos en un fichero en


cada uno de los ocho datos primitivos Java. Estos métodos tienen el formato:

void writeXxx(xxx dato)

siendo xxx el nombre del tipo primitivo Java.

El siguiente programa escribe en un fichero el contenido de un array de enteros:

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();
}
}

6.5 LECTURA DE DATOS PRIMITIVOS DE UN FICHERO


Para realizar la lectura de los datos almacenados en un fichero mediante
DataOutputStream, el paquete java.io proporciona la clase DataInputStream que a su
vez se apoya en FileInputStream.

6.5.1 CREACIÓN DE UN OBJETO FILEINPUTSTREAM


Es el equivalente al FileReader utilizado en la lectura de cadenas de caracteres. Se
puede crear un objeto de esta clase a partir de un objeto File o de la cadena que
representa la ruta del fichero.

FileInputStream es una subclase de InputStream que, al igual que OutputStream, está


orientada al tratamiento de bytes. En este caso, a la lectura de bytes.

6.5.2 CREACIÓN DE UN OBJETO DATAINPUTSTREAM


A partir del objeto FileInputStream se puede crear un DataInputStream para realizar la
lectura de los datos.

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){}
}
}

Como se desprende del ejemplo anterior, el método readInt() devuelve el entero


actual y tras la lectura de este se posiciona en el siguiente dato entero del fichero.
Cuando se llegue al final del fichero se producirá una excepción EOFException al
invocar al método readInt(), produciéndose una salida del bucle infinito y finalizando la
aplicación.

6.6 ESCRITURA DE OBJETOS EN UN FICHERO


Hasta ahora hemos visto cómo leer y escribir datos en un fichero, en forma de cadenas
de caracteres y como tipos primitivos Java.

En esta última parte veremos también cómo intercambiar cualquier objeto Java entre
una aplicación y el disco.

6.6.1 SERIALIZACIÓN DE OBJETOS


Para que un objeto pueda ser almacenado en disco, es necesario que la clase a la que
pertenece sea serializable. Esta característica la poseen todas aquellas clases que
implementen la interfaz java.io.Serializable.
La interfaz Serializable no contiene ningún método, basta con que una clase la
implemente para que sus objetos puedan ser serializados por la máquina virtual y por
tanto almacenados en 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;
}
}

6.6.2 CREACIÓN DE UN OBJETO OBJECTOUTPUTSTREAM


La escritura de objetos en disco se realiza a través de ObjectOutputStream. Para crear
un objeto de esta clase es necesario disponer del FileOutputStream asociado al fichero
donde se va a realizar la escritura.
Una vez creado el objeto, la clase dispone del método writeObject(Object obj) para
realizar la escritura del objeto en el disco.

El siguiente programa almacena en un fichero de disco un objeto de la clase Persona:

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();
}
}

6.7 LECTURA DE OBJETOS DE UN FICHERO


Por último, para leer objetos de un fichero que han sido almacenados mediante
ObjectOutputStream, se deberá utilizar el objeto ObjectInputStream.

6.7.1 CREACIÓN DE UN OBJETO OBJECTINPUTSTREAM


La clase ObjectInputStream dispone del método readObject() para devolver los objetos
almacenados en el fichero.
El siguiente programa recupera el objeto Persona almacenado en el fichero datos.obj
por la clase del ejemplo anterior y muestra sus datos en pantalla:
import java.io.*;
public class Recuperacion {
public static void main(String[] args) throws IOException {
FileInputStream fs = new FileInputStream("datos.obj");
ObjectInputStream os = new ObjectInputStream(fs);

Pág. 66
Programación en Java

Persona p = (Persona) os.readObject();


System.out.println(p.getName());
System.out.println(p.getEdad());
os.close();
}
}

El método readObject(), además de IOException, declara también una excepción de


tipo ClassNotFoundException que habrá que capturar.

6.7.2 DESERIALIZACIÓN DE OBJETOS


Cuando se recupera un objeto del disco mediante la llamada a readObject(), se
produce la Deserialización del objeto que básicamente consiste en la reconstrucción de
éste a partir de la información recuperada.

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

7 ACCESO A DATOS EN JAVA


En el capítulo anterior estudiamos la forma en que una aplicación Java puede
almacenar y recuperar información en ficheros de disco. No obstante, la mayoría de los
programas Java que manipulan información del disco lo hacen con datos almacenados
en una base de datos.

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.

7.1 LA TECNOLOGÍA JAVA DATABASE CONECTIVITY (JDBC)


La mayoría de las aplicaciones necesitan acceder a los datos existentes en un
Enterprise Information System, para ello necesitan disponer de alguna tecnología,
implementada en una librería de clases, que posibilite el envío de instrucciones SQL a
la base de datos y la manipulación de resultados.

JDBC proporciona a las aplicaciones Java un mecanismo uniforme para el acceso a


datos.

La tecnología JDBC consiste en la utilización de un conjunto de clases (API JDBC) que


disponen de una serie de métodos para operar con la base de datos. Utilizando estos
métodos, la aplicación dirige todas las peticiones hacia un software intermediario,
conocido como Driver JDBC, cuya misión es traducir las llamadas a los métodos a
órdenes nativas del gestor de BD (figura 7.1).

Figura 7.1 Acceso a datos mediante JDBC.

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.

7.2 EL DRIVER JDBC


Como se desprende de lo que hemos comentado, el driver JDBC juega un papel
fundamental en las aplicaciones Java de acceso a datos. Por ello, vamos a comentar
algunos aspectos relevantes sobre éste antes de abordar los aspectos propios de la
programación.

Pág. 68
Programación en Java

7.2.1 ESTRUCTURA Y FUNCIONAMIENTO


Básicamente, un driver JDBC es una clase Java que implementa toda la funcionalidad
del API JDBC, proporcionando la comunicación entre la aplicación y la base de datos.
Normalmente, son los fabricantes de bases de datos los que distribuyen los driver
JDBC aunque también se puede encontrar en productos de terceros, como entornos de
desarrollo o servidores de aplicaciones.

Figura 7.2 Capas de un driver.

En un driver JDBC se distinguen dos capas o interfaces:

 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.

7.2.2 TIPOS DE DRIVER JDBC


Independientemente del tipo de base de datos para el que se haya diseñado, un driver
JDBC puede pertenecer a una de las siguientes categorías:
 Driver puente JDBC – ODBC

 Driver nativo
 Driver intermedio

 Driver puro – Java

Driver puente JDBC – ODBC

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.

El driver puente JDBC – ODBC se incluye en el conjunto de clases J2SE, concretamente


se trata de la clase sun.jdbc.odbc.JdbcOdbcDriver.

Driver nativo
El driver nativo convierte las llamadas JDBC en llamadas al API nativo del gestor de
base de datos (figura 7.4).

Figura 7.4 Driver nativo.

Su principal inconveniente es la necesidad de instalar el API nativo de la base de datos


en el equipo donde reside la aplicación.

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

Figura 7.5 Driver que utiliza un servidor intermedio.

La principal característica de este driver es que no requiere ningún tipo de instalación


en el equipo donde se ejecuta la aplicación.

Driver puro – Java

Convierte las llamadas JDBC en el protocolo de red utilizado directamente por el


servidor de base de datos (figura 7.6), permitiendo llamadas directas desde la
máquina cliente (aplicación) al servidor de base de datos.

Figura 7.6 Driver puro - Java con acceso directo a la BD.

Este driver no requiere ningún tipo de configuración especial en la máquina cliente.

7.3 EL LENGUAJE SQL

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.

Este lenguaje es soportado por la mayoría de gestores de bases de datos relacionales


existentes en el mercado.

Sus instrucciones, de estructura muy simple, permiten operar sobre un conjunto de


datos en vez de tener que hacerlo individualmente.

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:

 Una acción o verbo, que determina la operación a realizar.


 Un objeto, combinación de campos de las tablas de la base de datos.
 Una cláusula que determina sobre qué objetos actúa el verbo.

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.

7.3.2 TIPOS DE SENTENCIAS SQL

El juego de sentencias SQL se divide en tres grupos:

 Sentencias DDL. En este grupo se incluyen aquellas sentencias que se encargan


de la creación, definición y destrucción de objetos. Entre ellas destacan CREATE
y DROP.
 Sentencias DCL. Permiten controlar aspectos varios como la confidencialidad de
los datos. Entre estas destacan GRANT y REVOKE.
 Sentencias DML. Están incluidas en este grupo las sentencias utilizadas para
manipulación de datos, como son extracción (SELECT), modificación (UPDATE) ,
inserción (INSERT) y borrado (DELETE).

7.3.3 SENTENCIAS PARA MANIPULACIÓN DE DATOS (DML)

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

La sentencia SELECT es la más importante y la más compleja de todas las sentencias


que forman SQL. Se utiliza para extraer datos de una o varias tablas de la base de
datos, su forma general operando sobre una única tabla es:

SELECT campo1, campo2, …


FROM tabla
[WHERE criterios]
[ORDER BY campo]

Esta instrucción, devuelve el conjunto de registros de la tabla indicada en FROM, que


cumplen las condiciones establecidas en WHERE y que están formados por los campos
indicados en SELECT. Para tomar todos los campos de la tabla se escribirá *, en lugar
de los nombres de los campos. Los registros se devolverán ordenados según el campo
o campos indicados en ORDER BY.

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.

La siguiente instrucción de ejemplo devolverá el campo “nombre” y “ciudad” de


aquellos registros de la tabla “usuarios”, cuya ciudad sea “Madrid”. Además, las filas se
devolverán ordenadas por el “nombre” del usuario.

SELECT nombre, ciudad


FROM usuarios
WHERE ciudad = „Madrid‟
ORDER BY nombre

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

Las condiciones de selección de registros, llamadas también predicados, se establecen


en la cláusula WHERE mediante un criterio, que es una expresión cuyo resultado se
evalúa como verdadero o falso. La forma general es:

Campo operador valor

A la hora de escribir la condición hay que tener en cuenta que:

 Si valor no es numérico, deberá entre comillas simples, aunque algunos


gestores de BD obligan a que los valores tipo fecha se escriban delimitados por
#.
 Valor puede ser el resultado de otra instrucción SELECT.
 La cláusula WHERE puede incluir varias expresiones de este tipo vinculadas con
los operadores AND y OR.

Ordenación de registros

La cláusula ORDER BY determina cómo se van a ordenar los registros según la forma:

Campo1 ASC/DESC, campo2 ASC/DESC, …

Donde hay que tener en cuenta que:

 Si dos o más registros poseen el mismo valor de campo1, se ordenaran según


campo2.
 El modo de ordenación puede ser ascendente (ASC) o descendente (DESC).
 El modo de ordenación predeterminado es ascendente.

Pág. 73
Programación en Java

Consultas SELECT sobre varias tablas

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:

WHERE tabla1.campo = tabla2.campo

La siguiente instrucción obtiene el nombre de todos los alumnos apuntados al curso


“SQL Server”, estando el campo “nombre”, cuyo valor se quiere recuperar, y el campo
“curso”, que contiene la condición, en tablas diferentes. Ambas tablas se encuentran
relacionadas por un campo común “id”:

SELECT nombre FROM alumnos, cursos


WHERE alumnos.id = cursos.id AND curso = “SQL Server”

Instrucciones SELECT subordinadas

Dentro de la cláusula WHERE de una sentencia SELECT se pueden especificar otras


instrucciones SELECT, a las que se considera subordinadas de la primera. La
instrucción anterior también se puede escribir utilizando un SELECT subordinado de la
siguiente manera:

SELECT nombre FROM alumnos


WHERE id = (SELECT id FROM alumnos WHERE curso = “SQL Server”)

Operadores

Además de los operadores simples (<, >, =, …) una cláusula WHERE puede incluir
otros tipos de operadores:

 LIKE. Se utiliza para buscar campos que contengan combinaciones de


caracteres que cumplan ciertas condiciones:

Campo LIKE constante_alfanumérica

La constante alfanumérica puede contener caracteres cualquiera, puede incluir


comodines con significado especial:

% cadena de longitud aleatoria

Pág. 74
Programación en Java

_ carácter no nulo

[x-y] carácter dentro del rango x – y

 BETWEEN. Comprueba si un valor está comprendido entre dos dados.

Exp1 [NOT] BETWEEN exp2 AND exp3

El resultado es verdadero si exp1 está comprendido entre exp2 y exp3.

 IN. Comprueba si un valor está incluido en una lista de valores.

Exp IN (cte1, cte2, cte3, …)

En lugar de una lista de constantes podría especificarse una sentencia SELECT


subordinada, que no puede incluir la cláusula ORDER BY y debe dar lugar a una
tabla con una sola columna.

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.

En el caso de añadir filas individuales se utiliza el formato:

INSERT INTO tabla (campo1, campo2, …)


VALUES (valor1, valor2, …)

Esta instrucción inserta en “tabla” un registro cuyos valores se especifican en VALUES,


cada valor será asignado a un campo según el orden especificado en la lista de
nombres de campos.

Si no se especifica la lista de campos, se asume que se suministrarán valores en la


lista de valores para todos los campos de la tabla.

El formato para insertar varias filas en una tabla es:

INSERT INTO tabla subselect

La siguiente instrucción añade a la tabla “alumnos” todos los registros de la tabla


“alumnos2”

INSERT INTO alumnos


SELECT * FROM alumnos2

Sentencia DELETE

Pág. 75
Programación en Java

Permite borrar una o varias filas de una tabla. Su formato es:

DELETE FROM tabla


WHERE condición

La siguiente instrucción elimina de la tabla “alumnos” a aquellos que hayan cursado


Access:

DELETE FROM alumnos


WHERE id_curso = (SELECT id_curso FROM cursos WHERE nomcurso =
“Access”)

Sentencia UPDATE

Su función es modificar los valores de ciertos campos en aquellos registros que


cumplan una determinada condición. El formato de esta instrucción es el siguiente:

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.

7.4 EL API JDBC

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.

Tabla 7.1 El API JDBC.

Clase / Interfaz Función


Establece conexiones con la base de datos a través del
DriverManager
Driver.
Connection Representa una conexión con la base de datos.
Statement Ejecución de consultas SQL.
Ejecución de consultas preparadas y procedimientos
PreparedStatement
almacenados.
ResultSet Manipulación de registros en consultas de tipo SELECT.
ResultSetMetadata Proporciona información sobre la estructura de los datos.

En los próximos apartados estudiaremos con detenimiento este API.

7.5 UTILIZACIÓN DE JDBC PARA ACCEDER A DATOS

En toda aplicación que utilice JDBC para acceder a una base de datos, se distinguen
cuatro fases o pasos a realizar:

1. Conexión con la base de datos

Pág. 76
Programación en Java

2. Ejecución de consultas
3. Manipulación de registros
4. Cierre de la conexión

A continuación, describiremos cada uno de ellos, analizando las clases e interfaces


implicadas en cada paso.

7.5.1 CONEXIÓN CON LA BASE DE DATOS

Para realizar cualquier operación con la base de datos es necesario, primeramente,


establecer una conexión con la misma.

Esta acción requiere la realización de dos operaciones:

 Cargar el driver
 Creación de la conexión

Carga del Driver

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(String clase_driver)

Este método localiza, lee y enlaza dinámicamente el driver, devolviendo un objeto


Class asociado a la clase indicada. Se debe tener en cuenta que la llamada a
forName() puede provocar una excepción de tipo ClassNotFoundException, por lo que
deberá ser capturada por la aplicación.

La siguiente instrucción realizaría la carga del driver puente JDBC – ODBC


proporcionada por Sun:

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:

Connection getConnection(String url)

La cadena url representa la dirección de la base de datos y su formato es:

jdbc:subprotocolo:base_datos

Pág. 77
Programación en Java

donde, subprotocolo depende del tipo de driver utilizado y base_datos es el nombre de


la base de datos.

Por otro lado, el método getConnection() devuelve un objeto que implementa la


interfaz Connection, la cual proporciona varios métodos para manejar la conexión.

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”);

El método getConnection() está sobrecargado, existiendo una versión que además de


la url permite suministrar el usuario y contraseña para acceder a la base de datos. El
siguiente ejemplo permitiría establecer una conexión con una base de datos de Oracle
llamada “info”, utilizando un driver nativo Oracle:

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.

7.5.2 EJECUCIÓN DE CONSULTAS

Una vez establecida la conexión con la base de datos, se empleará esta para enviar
consultas SQL a la base de datos.

Creación del objeto Statement

Las consultas SQL se manejan a través de un objeto que implementa la interfaz


Statement, cuya creación se realiza mediante el método createStatement() de la
interfaz Connection:

Statement st = cn.createStatement();

Esta operación, también puede provocar una SQLException.

Ejecución de la consulta SQL

La interfaz Statement proporciona diversos métodos para enviar una consulta SQL a
través de la conexión. Los más importantes son:

 boolean execute(String sql). Envía a la base de datos la consulta SQL


proporcionada como parámetro. Si se trata de una consulta de acción (INSERT,
UPDATE o DELETE), el método devolverá false indicando que no se generan

Pág. 78
Programación en Java

resultados. Cuando es una consulta de selección (SELECT), el método devolverá


true.
 int executeUpdate(String sql). Envía una consulta de acción a la base de datos,
devolviendo el número de registros afectados por la acción.
 ResultSet executeQuery(String sql). Envía una consulta de selección de
registros a la base de datos, devolviendo un objeto ResultSet para su
manipulación.

En caso de producirse algún tipo de error en la ejecución de la consulta, los tres


métodos generan una excepción SQLException.

Para enviar una consulta de actualización a la tabla “empleados” de la base de datos


“empresa”, utilizaríamos:

st.execute(“Update empleados set salario=salario*1.1”);

7.5.3 CIERRE DE LA CONEXIÓN

Las consultas de acción no devuelven datos a la aplicación para su tratamiento, por lo


que una vez ejecutada la consulta o consultas, se debe proceder al cierre de la
conexión. Esto permite liberar recursos de memoria y CPU en la máquina.

El cierre de una conexión se realiza mediante el método close() de la interfaz


Connection.

cn.close();

La llamada a este método también puede provocar una SQLException.

La interfaz Statement también dispone de un método close() para liberar el objeto


Statement, esto debe realizarse antes de cerrar la conexión. Cuando se cierra una
conexión, todos los objetos Statement que quedan abiertos serán cerrados
automáticamente.

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();
}
}
}

7.5.4 MANIPULACIÓN DE REGISTROS

El envío de una consulta de selección de registros a la base de datos devuelve como


resultado un objeto que, además de disponer de un cursor para desplazarse por el
conjunto de registros seleccionados, permite acceder a los valores de los campos. Este
objeto implementa la interfaz ResultSet.

La interfaz ResultSet del API SQL, proporciona métodos para desplazarse por el
conjunto de registros afectados por la consulta y manipular sus contenidos.

Obtener un objeto ResultSet

Un objeto ResultSet se crea al invocar al método executeQuery() del objeto


Statement:

ResultSet rs = st.executeQuery(“select * from empleados”);

De forma predeterminada, este objeto posee la característica de ser de sólo avance y


sólo lectura. Esto implica que el recorrido se deberá hacer siempre desde el primer
registro hacia adelante y los contenidos de los campos no podrán ser modificados.

Desde el punto de vista de la velocidad y el consumo de recursos, ésta es la forma


más óptima en la que se puede presentar un ResultSet, resultando además esta
funcionalidad suficiente en la mayoría de los casos. No obstante, en apartados
posteriores veremos cómo crear ResultSets desplazables de lectura/escritura.

Desplazamiento por el conjunto de registros

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

Para realizar el desplazamiento por los registros, la interfaz ResultSet proporciona el


método next(). La llamada a este método desplaza el cursor al siguiente registro del
conjunto, devolviendo como resultado un boolean que indica si la nueva posición
apuntada se corresponde con un registro (true), o si el cursor se ha salido del conjunto
(false).

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
}

Acceso a los campos

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:

 xxx getXxx(int posición)


 xxx getXxx(String nombre_campo)

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.

El primer grupo de métodos permite obtener el valor de un campo a partir de su


posición dentro del registro, siendo 1 la posición del primer campo. Por su parte, el
segundo grupo de métodos obtiene el valor del campo a partir del nombre del mismo.

El siguiente trozo de código, muestra en pantalla la edad de todos los empleados:

ResultSet rs = st.executeQuery(“select * from empleados”);


while(rs.next()) {
System.out.println(rs.getInt(“edad”));
}

El siguiente programa representa otro ejemplo de utilización de JDBC para extraer


información de una base de datos. En este caso, se solicita a un usuario que introduzca
su usuario y contraseña por teclado, utilizando esta información para comprobar si el
usuario está o no registrado en la tabla “clientes” de una base de datos:

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

String user = bf.readLine();


System.out.println("Introduzca la contraseña: ");
String pwd = bf.readLine();
try {
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection cn =
DriverManager.getConnection("jdbc:odbc:tienda");
Statement st = cn.createStatement();
String tsql = "select usuario, contrasena from clientes where
usuario='";
tsql += user + "' and contrasena='" + pwd + "'";
ResultSet rs = st.executeQuery(tsql);
if (rs.next()) {
System.out.println("Usuario válido");
} else {
System.out.println("Usuario no válido");
}
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();
}
}
}

Otros métodos de la interfaz ResultSet

Además de los anteriores, la interfaz ResultSet proporciona los siguientes métodos:

 boolean isFirst(). Devuelve true si el cursor apunta al primer registro.


 boolean isBeforeFirst(). Devuelve true si el cursor está situado antes del primer
registro.
 boolean isLast(). Devuelve true si el cursor está apuntando al último registro.
 boolean isAfterLast(). Devuelve true si el cursor se encuentra después del
último registro.
 int getRow(). Devuelve la posición del registro actual, siendo 1 la posición del
primer registro.

Todos los métodos de la interfaz ResultSet pueden lanzar una SQLException.

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.

Si un mismo objeto Statement se utiliza para crear un segundo ResultSet, el primer


ResultSet será cerrado automáticamente de forma implícita. Esto significa que si se

Pág. 82
Programación en Java

desea tener dos ResultSet abiertos al mismo tiempo, habrá que crearlos con dos
Statements diferentes.

De hecho, cualquier operación que se realice con el objeto Statement después de


haber creado un ResultSet, provocara implícitamente el cierre inmediato de éste.

7.5.5 INFORMACIÓN SOBRE LOS DATOS

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.

Obtener un objeto ResultSetMetaData

Primeramente, necesitamos obtener un objeto ResultSetMetaData, para lo cual


utilizaremos el método getMetaData() de la interfaz ResultSet:

ResultSetMetaData rm= rs.getMetaData();

Acceso a la información

La interfaz ResultSetMetaData proporciona los siguientes métodos para obtener


información sobre los datos:

 int getColumnCount(). Devuelve el número de campos o columnas del conjunto


de registros referidos por el ResultSet.
 String getColumnName(int posición). Permite obtener el nombre de un campo a
partir de su posición.
 int getColumnType(int posición). Devuelve una constante entera que representa
el tipo de dato SQL soportado por el campo. Las constantes de los tipos SQL se
encuentran definidas en la clase java.sql.Types (Tabla 7.2).
 String getColumnTypeName(int posición). Devuelve el nombre del tipo de dato
soportado por el campo, según está definido en el gestor de base de datos.

Tabla 7.2 Equivalencia entre tipos SQL y tipos Java.

Constantes Tipo equivalente


java.sql.Types Java
SMALLINT short
INTEGER int
BIGINT long
FLOAT float
DOUBLE double
BOOLEAN boolean
VARCHAR String

Pág. 83
Programación en Java

7.5.6 CONSULTAS PREPARADAS

Las consultas preparadas se basan en la utilización de consultas SQL precompiladas.

La idea es precompilar una instrucción SQL, utilizando parámetros en vez de valores


fijos, y sustituir éstos por valores concretos en el momento en que se desee ejecutar la
instrucción. Esto aumenta la eficiencia de la aplicación en aquellos casos en que vaya a
utilizarse repetidas veces una determinada instrucción.

Las consultas preparadas se gestionan mediante la interfaz PreparedStatement.

Creación de un objeto PreparedStatement

Para la creación de una consulta preparada utilizamos el método prepareStatement()


de la interfaz Connection. Este método recibe como parámetro la consulta SQL para su
precompilación:

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.

Esta operación se realiza con el siguiente grupo de métodos existentes en la interfaz


PreparedStatement:

void setXxx(int índice_parametro, xxx valor);

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.

Para el ejemplo de la consulta anterior, podríamos hacer la siguiente asignación de


parámetros:

ps.setInt(1,4000);
ps.setString(2, “50069368R”);

Ejecución de la consulta

Pág. 84
Programación en Java

Para poder acceder a la ejecución de la consulta utilizaremos los métodos execute() o


executeQuery(), dependiendo de si es una consulta de acción o de selección de
registros.

En el ejemplo que estamos analizando:

ps.execute();

7.5.7 RESULTSET DESPLAZABLES

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.

Para disponer de un ResultSet con mayores prestaciones, tendríamos que modificar la


forma en que creamos los objetos Statement o PreparedStatement, utilizando las
siguientes versiones de métodos de creación de consultas de la interfaz Connection:

 Statement createStatement(int resultSetType, int resultSetConcurency)


 PreparedStatement prepareStatement(String sql, int resultSetType, int
resultSetConcurency)

El parámetro entero resultSetType representa el tipo de ResultSet que pueden ser


creados con el objeto. Los posibles valores que puede tomar este parámetro están
recogidos en las siguientes constantes definidas en la interfaz ResultSet:

 ResultSet.TYPE_FORWARD_ONLY. Los ResultSet creados son del tipo “sólo


avance”, es el valor predeterminado.
 ResultSet.TYPE_SCROLL_INSENSITIVE. Permite crear ResultSet que se
desplacen en ambas direcciones, aunque no muestra los cambios que puedan
realizar otros usuarios en la base de datos mientras el ResultSet esté abierto.
 ResultSet.TYPE_SCROLL_SENSITIVE. Permite crear ResultSet desplazables en
ambas direcciones y, además, sensibles a los cambios que otros usuarios
realicen sobre la base de datos.

En cuanto a resultSetConcurrency, indica si los datos son de sólo lectura o de


lectura/escritura. Los posibles valores que puede tomar son:

 ResultSet.CONCUR_READ_ONLY. Los campos son de sólo lectura, es el valor


predeterminado.
 ResultSet.CONCUR_UPDATABLE. Los campos son de lectura/escritura.

Los ResultSet desplazables pueden utilizar, además de los estudiados, los siguientes
métodos de la interfaz ResultSet:

 void first(). Desplaza el cursos hasta el primer registro.


 void beforeFirst(). Desplaza el cursor a la posición situada antes del primer
registro.

Pág. 85
Programación en Java

 void last(). Desplaza el cursos hasta el último registro.


 void afterLast(). Desplaza el cursor hasta la posición que esta después del
último registro.
 void absolute(int pos). Desplaza el cursor hasta la posición indicada en el
parámetro pos.

Pág. 86
Programación en Java

8 APLICACIONES BASADAS EN ENTORNO GRÁFICO

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.

En el caso de las aplicaciones Web, estos entornos o interfaces gráficas se


implementan a través de HTML/JavaScript. Para las aplicaciones de escritorio, Java
incluye como parte de J2SE dos conjuntos de librerías que proporcionan una amplia
variedad de componentes gráficos, así como todo el soporte necesario para gestionar
la interacción con el usuario. Estos conjuntos de librerías son:

 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.

En este capítulo abordaremos los fundamentos de la creación de aplicaciones gráficas,


tanto en AWT como en Swing. Nuestro análisis se va a centrar en la mecánica a seguir
para el diseño de una interfaz gráfica y en la comprensión del modelo de eventos de
Java para la gestión de la interacción con el usuario, no siendo el objetivo de este libro
la realización de un estudio exhaustivo de todos los componentes gráficos incluidos en
las librerías.

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.

8.1.1 PRINCIPALES CLASES DE AWT

Pág. 87
Programación en Java

Las clases para la creación de aplicaciones gráficas con AWT se encuentran en el


paquete java.awt. La figura 8.1 muestra algunas de las clases que conforman este
paquete.

Figura 8.1 Principales clases de AWT.

Por un lado tenemos un grupo de clases organizadas jerárquicamente, cuya superclase


es Component, que representa el conjunto de componentes gráficos para el desarrollo
de una interfaz. Por otro lado existe una serie de clases de apoyo que, aunque forman
parte del paquete java.awt, no pertenecen a la jerarquía de subclases de Component
pero sirven de soporte a éstas.

8.1.2 CONTENEDORES

En toda aplicación basada en entorno gráfico, el conjunto de objetos visuales


(controles) con los que interactúa el usuario se encuentra incluidos dentro de un objeto
superior, conocido como contenedor, cuya principal función es la de organizar la vista
de la aplicación.

AWT proporciona dos tipos principales de contenedores:

 Ventanas. Su aspecto es el de la clásica ventana Windows con barra de título y


cuadro de control para maximizar, minimizar y cerrar. Normalmente, el usuario
puede modificar manualmente el tamaño y posición de las ventanas.
 Paneles. Los paneles son contenedores que no disponen de barra de título ni
cuadro de control, además, su tamaño y posición son fijos. Por lo general, un
panel está a su vez contenido en otro contenedor, que puede ser una ventana o
una página Web.

8.1.3 CREACIÓN DE UNA VENTANA

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:

1. Creación del objeto. Lo primero será crear el objeto de la clase de ventana


elegida (Frame o Dialog). En el caso de Frame, utilizaremos alguno de los
siguientes constructores:
 Frame(). Crea una ventana sin título.
 Frame(String s). Crea una ventana con el título indicado en el
argumento.
2. Definir tamaño y posición de la ventana. Al crear una ventana, ésta adquiere un
tamaño mínimo y una posición predeterminada. Para personalizar estos
parámetros, podemos utilizar los siguientes métodos heredados de la clase
Component:
 void setSize(int ancho, int alto). Establece el tamaño del componente, el
cual quedará definido por el ancho y el alto indicado en los argumentos.
Los valores se miden en pixeles.
 void setLocation(int x, int y). Posiciona el componente en las
coordenadas indicadas, los valores se toman respecto a las esquina
superior izquierda de la pantalla. Los valores se miden en pixeles.
 void setBounds(int x, int y, int ancho, int alto). Define,
simultáneamente, la posición y tamaño del componente.
3. Visualizar la ventana. De forma predeterminada, una ventana es invisible. Para
que esta se visualice se deberá utilizar el método setVisible() heredado de
Component:
 void setVisible(boolean estado). Permite visualizar (true) u ocultar
(false) un componente.

El siguiente programa de ejemplo ilustra la creación de una ventana siguiendo los


pasos anteriores:

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);
}
}

La figura 8.2 muestra la ventana generada por el programa anterior.

Pág. 89
Programación en Java

Figura 8.2 Aspecto de una ventana AWT.

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.

8.1.4 PERSONALIZACIÓN DE VENTANAS

Cuando se trabaja con ventanas en una aplicación, lo normal es no crear derectamente


objetos de la clase Frame o Dialog. Resulta más interesante definir subclases de éstas
que encapsulen en su interior todo el proceso de construcción de la ventana e inclusión
de controles, “liberando” de esta tarea al método main().

El siguiente programa para la creación de una ventana ilustra esta nueva filosofía:

public class CreaVentana {


public static void main(String[] args) {
Ventana v = new Ventana("Nueva ventana", 100, 100, 400, 250);
}
}
import java.awt.Frame;
class Ventana extends Frame {
public Ventana(String titulo, int x, int y, int ancho, int alto) {
super(titulo);
this.setBounds(x, y, ancho, alto);
this.setVisible(true);
}
}

Como se puede apreciar en este caso, el método main() solamente incluye la


instrucción para la construcción del objeto Ventana.

8.1.5 AGREGAR CONTROLES A UN CONTENEDOR

Pág. 90
Programación en Java

El paquete java.awt incluye un variado número de clases para la creación de los


elementos gráficos más comunes que puedan aparecer en una aplicación. Todas estas
clases son subclases de Component. Independientemente de su tipo, para incorporar
un control AWT a un contenedor hay que seguir los siguientes pasos:

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.

Normalmente, las instrucciones para creación de controles dentro de un contenedor


son incluidas en el constructor del propio contenedor. El siguiente ejemplo muestra
cómo crear un botón dentro de una ventana:

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);
}
}

8.2 EL MODELO DE GESTIÓN DE EVENTOS EN JAVA

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.

Normalmente, se espera que cuando se produzca uno de esos sucesos en la aplicación


el programa reaccione de alguna manera. Para ello, el programador debe escribir algún
tipo de rutina vinculada al evento, de modo que ésta se ejecute cada vez que el evento
tiene lugar.

A esta forma de programar se le conoce como programación basada en eventos y es la


metodología utilizada en el desarrollo de las aplicaciones que utilicen un entorno
gráfico para interaccionar con el usuario.

8.2.1 INTERFACES DE ESCUCHA Y ESCUCHADORES

La idea base de la gestión de eventos consiste pues en implementar una serie de


métodos que se ejecuten de manera automática en el momento en que estos eventos
se producen dentro de la aplicación, métodos que tendrán que ajustarse a un
determinado formato preestablecido.

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.

Las interfaces de escucha para la gestión de eventos en aplicaciones AWT se


encuentran en el paquete java.awt.event. La figura 8.3 muestra algunas de las
principales interfaces de escucha AWT y su organización jerárquica.

Figura 8.3 Interfaces de escucha de java.awt.event.

Pág. 92
Programación en Java

La figura 8.4 muestra el formato de los diferentes métodos contenidos en la interfaz


WindowListener, indicando en que momento serán invocados.

Figura 8.4 Métodos de la interfaz WindowListener.

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.).

Para responder a un evento dentro de una aplicación, el programador deberá definir


una clase que implemente la interfaz de escucha correspondiente al tipo de evento que
quiere gestionar. A los objetos de esta clase se les conoce como escuchadores.

8.2.2 EL PROCESO DE GESTIÓN DE EVENTOS

Origen y destino del evento

De lo comentado hasta el momento, se deduce que el proceso de captura y gestión de


un evento en Java implica a dos actores u objetos principales de la aplicación:

 Origen del evento. Representa el objeto gráfico en el que se produce el evento


que se quiere capturar (botón, ventana, etc.).
 Escuchador. Es el objeto de la clase que implementa la interfaz de escucha y
que contiene el método de respuesta al evento.

Asociación objeto origen-escuchador

En una aplicación gráfica típica, lo normal es que se quieren capturar más de un


evento. Habitualmente, esto implica la creación de varias clases de escucha y objetos
escuchadores para los diferentes eventos a controlar, por lo que debe existir alguna
forma de asociar a cada objeto origen del evento el objeto escuchador que gestiona el
evento.

Pág. 93
Programación en Java

Este vínculo se establece gracias a los métodos addXxxListener() proporcionados por


las clases de componentes gráficos del AWT, cuyo formato es:

void addXxxListener(XxxListener l)

donde XxxListener representa el nombre de la interfaz de escucha que contiene el


método para la gestión del evento. este método se invocaría sobre el objeto origen,
siendo el argumento del método el objeto escuchador.

Por ejemplo, si queremos capturar el evento “cierre de la ventana”, dado que el


método para responder a este evento se encuentra en la interfaz WindowListener, el
método del objeto ventana (heredado de Window) que nos permite asociar este con su
escuchador es:

void addWindowListener(WindowListener l);

Resumen de pasos a seguir

Con el fin de definir claramente el proceso de gestión de eventos en Java, a


continuación se resumen los pasos que debería seguir un programador para desarrollar
una aplicación gráfica basada en eventos:

 Implementar clases de escucha. Una vez identificados los eventos que se


quieren controlar, se definen las clases que implementarán las interfaces de
escucha correspondientes. Aunque no hay una norma al respecto, por regla
general cada objeto origen del evento tendrá su propia clase para la gestión de
sus eventos, así, en caso de que haya que controlar el mismo evento en dos
objetos diferentes, por ejemplo el “clic” en dos botones, se definirán dos clases
de escucha diferentes, cada una tendrá su propia implementación del método
actionPerformed(). Si el código a ejecutar en ambos sucesos es el mismo,
entonces puede optarse por definir una única clase.
 Crear los objetos de escucha. Para cada objeto origen, se creará un objeto de la
clase de escucha. Esta operación, se realiza normalmente en el constructor de
la clase contenedor del control.
 Asociar el objeto origen del evento con su escuchador. Como hemos comentado
anteriormente, esto se realiza invocando al método addXxxListener() del objeto
origen, operación que es llevada a cabo habitualmente en el constructor del
contenedor.

Ejemplo de gestión de eventos

Para ilustrar el proceso, vamos a desarrollar un ejemplo que consistirá simplemente en


responder a la pulsación del botón de cierre de una ventana. Al producirse este suceso,
el programa responderá finalizando la aplicación.

Lo primero, será codificar la clase de escucha, ésta deberá implementar la interfaz


WindowListener. De los siete métodos que tiene esta interfaz, WindowClosing() es el

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) { }
}

Como se puede apreciar, aunque sólo estamos interesados en un método, por


obligación debemos implementar todos los que tenga la interfaz.

Seguidamente, hemos de crear un objeto de esta clase de escucha y vincularlo con el


objeto de origen. Esta operación la realizaremos en el constructor de la clase Ventana:

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);
}
}

8.2.3 CLASES DE EVENTO

Todos los métodos de respuesta a eventos existentes en las diferentes interfaces de


escucha del J2SE, reciben como parámetro un objeto que nos permite acceder en
tiempo de ejecución a información relacionada con el evento producido. Estos objetos
son creados y enviados como parámetros a los métodos escuchadores en el momento
en que se produce el evento dentro de la aplicación.

Estos objetos de evento pertenecen a algunas de las llamadas clases de evento,


incluidas en los paquetes java.awt.event y javax.swing.event.

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

MouseEvent, cuyos métodos permiten a los escuchadores que implementan


MouseListener conocer la posición del ratón en el momento en que se produjo el
evento.

Las clases de evento se encuentran organizadas jerárquicamente, siendo la clase


java.util.EventObject la superclase de todas ellas. En la figura 8.5 se muestra algunas
de las clases más significativas de la jerarquía de clases de evento del AWT.

Figura 8.5 Clases de evento de AWT.

Seguidamente, vamos a presentar una nueva versión de la clase GestionVentana. En


este caso, el método windowClosing() lleva a cabo el cierre de la ventana, para lo cual
invoca al método dispose() del objeto ventana, heredado de la clase Window.

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

La clase de escucha para la gestión del evento “cerrando ventana” desarrollado en el


ejemplo anterior, presentaba el inconveniente de tener que implementar los siete
métodos de la interfaz cuando tan sólo estábamos interesados en uno de ellos. En este
caso puede resultar más cómodo heredar el adaptador correspondiente a la interfaz,
en vez de implementar está directamente.

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.

Heredando el adaptador en vez de implementar la interfaz, la clase de escucha no está


obligada a implementar todos los métodos de la interfaz (ya lo hace el adaptador),
sobrescribiendo únicamente aquellos métodos del adaptador para los que se quiera
proporcionar una respuesta.

Utilizando adaptadores, la clase GestionVentana anterior quedaría ahora:

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();
}
}

8.2.5 REFERENCIA A LOS OBJETOS DE LA INTERFAZ DESDE LA CLASE


DE ESCUCHA

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

Existen diferentes formas para resolver este problema, en el ejemplo anterior,


utilizamos el método getSource() de ActionEvent para acceder al objeto origen del
evento, si bien ésta es una solución parcial pues sólo nos resuelve el problema si el
objeto al que necesitamos acceder es aquel en el que se produjo el evento. Otras
soluciones pueden ser crear variables públicas estáticas para almacenar los controles,
o utilizar clases internas para implementar las interfaces de escucha.

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

Para aclarar esta técnica vamos a presentar un ejemplo a continuación, consistente en


una aplicación que tome un dato introducido en una caja de texto (objeto TextField), y
lo muestre en un control Label al efectuar la pulsación de un botón. La figura 8.6
muestra el aspecto de la interfaz gráfica de esta aplicación.

Figura 8.6 Aspecto de la interfaz gráfica del ejemplo.

El código de la clase Ventana es el siguiente:

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

tf.setBounds(150, 70, 100, 30);


lb = new Label(". . . . . . . . . . .");
lb.setBounds(150, 130, 100, 30);
//Se pasa al objeto ventana el constructor de la
//clase que gestionará el evento ActionEvent
GestionBoton gb = new GestionBoton(this);
bt.addActionListener(gb);
this.add(bt);
this.add(lb);
this.add(tf);
this.setVisible(true);
}
}

La clase GestionBoton será la encargada de gestionar la pulsación del botón, de modo


que para que desde ella se pueda acceder a los controles de la interfaz gráfica,
definidos con ámbito por defecto en la clase Ventana, se debe pasar el propio objeto
Ventana como parámetro del constructor de la clase.

El código de la clase GestionBoton quedará de la siguiente forma:

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);
}
}

La clase que contiene el método main() para crear la ventana no es diferente a la de


los ejemplos anteriores:

public class CreaVentana {


public static void main(String[] args) {
Ventana v = new Ventana("Ventana con botón", 100, 100, 400,
250);
}
}

Cuando se pulse el botón “Mostrar texto” la frase introducida en la caja de texto se


mostrará en la etiqueta, tal y como se refleja en la figura 8.7.

Pág. 99
Programación en Java

Figura 8.7 Resultado de la pulsación del botón.

8.2.6 GESTORES DE ORGANIZACIÓN AWT

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.

Figura 8.8 Mantenimiento de la disposición de controles al cambiar el tamaño del contenedor.

Pág. 100
Programación en Java

Los gestores de organización de AWT pertenecen a alguna de las clases que


implementan la interfaz LayoutManager. La figura 8.9 muestra algunas de las más
significativas, todas ellas se incluyen en el paquete java.awt.

Figura 8.9 Gestores de organización.

Establecimiento de un gestor de organización

Todos los contenedores disponen de un gestor de organización predeterminado. En


caso de querer establecer otro diferente, la operación habrá que realizarla antes de
añadir controles al mismo, para ello, se hará uso del método setLayout() de la clase
Container siguiendo la expresión:

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:

add(Component comp, Object constraints)

Donde el argumento constraints representa algún tipo de información adicional


requerida por el gestor de organización para proceder a la colocación del control.

Principales gestores de organización AWT

En la figura 16 se presentaron las clases de los gestores de organización más utilizados


en AWT, a continuación vamos a analizar sus características.

FlowLayout

El gestor de organización FlowLayout coloca los controles siguiendo un orden de


izquierda a derecha y de arriba hacia abajo. Por defecto, cada línea de controles se
encuentra centrada en el contenedor (figura 8.10), pudiendo definir una alineación
diferente en la creación del gestor.

Pág. 101
Programación en Java

Figura 8.10 Gestor de organización FlowLayout.

La clase FlowLayout proporciona los siguientes constructores para la creación de este


gestor:

 FlowLayout(). Crea un FlowLayout con alineación centrada.


 FlowLayout(int align). Crea un FlowLayout con la alineación especificada en el
argumento. Los posibles valores que puede tomar este parámetro están
definidos en las siguientes constantes públicas de la clase FlowLayout:
o CENTER. Alineación centrada
o LEFT. Alineación izquierda
o RIGHT. Alineación derecha
 FlowLayout(int align, int hgap, int vgap). Al igual que el anterior, permite
especificar el tipo de alineación, añadiendo además la posibilidad de definir una
separación vertical y horizontal entre los componentes.

Para añadir controles a un contenedor que disponga de este gestor de organización se


utiliza la versión estándar del método add() de la clase Container.

Como ejemplo, a continuación se muestra el código de la clase correspondiente a la


ventana presentada en la figura 17:

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

Button b2 = new Button("Botón muy grande que no cabe en la


línea anterior");
//añade los controles al contenedor
this.add(b1);
this.add(t1);
this.add(c1);
this.add(b2);
this.setVisible(true);
}
}

BorderLayout

El gestor de organización BorderLayout divide el contenedor en cinco regiones: norte,


sur, este, oeste y centro (figura 8.11). Cada región será ocupada por un control.

Figura 8.11 Organización del contenedor con el gestor BorderLayout.

Los constructores proporcionados por la clase BorderLayout son:

 BorderLayout(). Crea un BorderLayout sin separación entre los componentes.


 BorderLayout(int hgap, int vgap). Crea un BorderLayout con la separación
horizontal y vertical entre componentes especificada en los argumentos.

A la hora de añadir los controles a un contenedor de este tipo, es necesario indicar en


que región se desea situar el componente. Para ello, es necesario utilizar la versión
sobrecargada de add():

add(Component cmp, Object constraints)

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:

 NORTH. Colocación del control en la zona norte.


 SOUTH. Colocación del control en la zona sur.

Pág. 103
Programación en Java

 EAST. Colocación del control en la zona este.


 WEST. Colocación del control en la zona oeste.
 CENTER. Colocación del control en la zona centro.

El siguiente código corresponde a la clase Ventana de la figura 18:

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

Este gestor de organización divide el contenedor en filas y columnas, formando una


especie de rejilla de celdas rectangulares con idéntico tamaño. Cada rejilla es ocupada
por un componente (figura 8.12).

Pág. 104
Programación en Java

Figura 8.12 Organización con GridLayout.

Los constructores utilizados para la creación de este tipo de gestor son:

 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.

Para añadir los componentes a un contenedor de estas características se utiliza la


versión estándar de add().

El siguiente listado corresponde a la clase Ventana de la figura 19:

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).

Figura 8.13 Colocación de controles con CardLayout.

La mayoría de las aplicaciones gráficas que se diseñan no se ajustan exactamente a las


características definidas por un único gestor de organización, esto nos lleva muchas
veces a tener que combinar diferentes gestores de organización mediante la utilización
de paneles.

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:

 Apariencia independiente de la plataforma. Uno de los principales problemas de


AWT es que los componentes gráficos son generados por el sistema operativo,
lo que hace que la apariencia de las aplicaciones sea bastante dependiente de
éste. Los componentes swing son en este sentido “autónomos”, adaptándose
dinámicamente al sistema operativo y plataforma en que esté corriendo.

Pág. 106
Programación en Java

 Mayor número de componentes gráficos. El juego de controles gráficos


proporcionados por swing es mucho mayor que el de AWT, lo que permite crear
aplicaciones mucho más potentes desde el punto de vista de la presentación.
 Mejora de los componentes clásicos. Además de proporcionar un mayor número
de controles, swing también introduce sustanciales mejoras en los controles
típicos (botones, cajas de texto, listas, etc.), dotándolos de un mayor número
de métodos y posibilidades de presentación.

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.

8.3.1 PRINCIPALES CLASES DE SWING

Las clases swing se encuentran en el paquete javax.swing. El cuadro de la figura 8.14


muestra algunas de las clases más destacadas de este paquete.

Figura 8.14 El paquete javax.swing.

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.

8.3.2 CREACIÓN DE UNA INTERFAZ GRÁFICA SWING

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:

 Los objetos JFrame tienen habilitado el botón de cierre de la ventana. Esto


significa que no es necesario programar el suceso “cerrando la ventana” en las

Pág. 107
Programación en Java

ventanas swing. El comportamiento por defecto de este botón es provocar el


cierre de la ventana.
 Los controles swing no se añaden directamente al objeto JFrame. Un objeto
JFrame incluye un contenedor interno, conocido como “panel de contenido”,
donde se deben situar los componentes visuales de la interfaz. Es en este
objeto donde habrá que agregar los controles y sobre el que habrá que definir
el gestor de organización a utilizar. Para obtener el panel de contenido de una
ventana swing, utilizaremos el método getContentPane() de JFrame:

Container getContentPane()

Como ejemplo ilustrativo de la utilización de swing, vamos a desarrollar la versión


swing del último ejercicio creado con AWT y cuya interfaz gráfica se presentaba en la
figura 8.6.

La figura 8.15 muestra el aspecto de la interfaz swing equivalente.

Figura 8.15 Interfaz Swing.

El código de la clase contenedor, que para seguir la nomenclatura swing se ha llamado


JVentana, es el siguiente:

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

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);
//los controles se agregan al panel de contenido
this.getContentPane().add(jbt);
this.getContentPane().add(jtf);
this.getContentPane().add(jlb);
this.setVisible(true);
}
}

El código de la clase GestionBoton para el tratamiento del evento ActionListener en el


botón swing es prácticamente idéntica al de la versión AWT pues, al ser el mismo
evento el que se quiere controlar, la interfaz de escucha utilizada es la misma:

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);
}
}

La clase para la creación del objeto JVentana será:

public class Principal {


public static void main(String[] args) {
JVentana v = new JVentana("Ventana Swing", 30, 80, 400, 300);
}
}

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).

Figura 8.16 Acceso a un applet desde un navegador.

Es importante destacar que, a diferencia de otros procedimientos para añadir


programas a una página Web, como por ejemplo los scripts, el código del applet no
aparece en el archivo HTML. Lo que éste incluye es una referencia al archivo Java del
applet compilado (.class).

Dada la potencia y amplia variedad de componentes gráficos disponibles para la


creación de applets, con su inclusión en páginas Web se consigue potenciar las
posibilidades de presentación de estas, aumentando además su capacidad para
interaccionar con el usuario.

8.4.1 LA CLASE APPLET

La base para la creación de applets la proporcionan las clases java.applet.Applet, para


la creación de applets basadas en AWT, y su subclase java.swing.JApplet para swing.
Estas clases, que son a su vez subclases de Panel, representan el contenedor sobre el
que se agregarán los diferentes componentes gráficos que conforman la aplicación.

Así pues, la creación de un applet no difiere demasiado de la creación de una


aplicación basada en ventanas: en una subclase de Applet o JApplet se crearán los
componentes gráficos de la aplicación y se asociarán a los objetos de escucha
encargados de la gestión de eventos, cuyas clases deberán implementar las interfaces
de escucha correspondientes.

8.4.2 MÉTODOS DEL CICLO DE VIDA DE UN APPLET

Debido a que un applet es cargado y ejecutado por un navegador, su ciclo de vida es


algo más complejo que el de una ventana estándar. Durante la vida de un applet, se
producen una serie de sucesos que le son notificados por el navegador en forma de
llamadas a métodos expuestos por el applet, estos métodos son conocidos como
métodos del ciclo de vida y se encuentran en la clase Applet:

Pág. 110
Programación en Java

 void init(). Método de inicialización. Es invocado cuando se produce la carga del


applet en el navegador, inmediatamente después de crear el objeto. Dado que
este método es invocado una sola vez durante la vida del applet, suele ser
utilizado para añadir en él las instrucciones de creación y configuración de la
interfaz gráfica. En este punto, es necesario destacar el hecho de que algunos
navegadores invocan este método más de una vez después de haber cargado el
applet.
 void start(). Es invocado cada vez que la página que contiene el applet es
visualizada en el navegador, lo cual sucede después de cargar el applet y
cuando el navegador retorna a la página después de haberla abandonado. Es el
lugar indicado para iniciar sucesos en segundo plano encargados de realizar
algún tipo de animación en el applet.
 void stop(). Este método es invocado por el navegador cuando va a abandonar
la página que contiene el applet. Puede utilizarse para detener animaciones u
otros procesos que se ejecuten en segundo plano.
 void destroy(). Se produce una llamada a este método justo antes de que el
applet vaya a ser destruido.

8.4.3 CREACIÓN DE UN APPLET

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);
}
}

Del código anterior se desprende la existencia de dos diferencias destacables entre la


creación de un applet y una ventana:

Pág. 111
Programación en Java

 La creación de la interfaz gráfica del applet se realiza en el método inti(), no en


el constructor. Al ser un objeto gestionado por un contenedor externo (el
navegador), y no desde el código de la aplicación, las instrucciones que definen
el comportamiento del applet se incluyen en los métodos del ciclo de vida del
mismo.
 El tamaño y posición del applet se definen fuera del applet. Un applet esté
incluido dentro de una página Web, por lo que es en ésta donde se establecerán
estos parámetros. Por otro lado, un applet siempre es visible dentro de la
página Web, por lo que no es necesario invocar explícitamente al método
setVisible() de la misma.

La gestión de los eventos en el applet se lleva a cabo exactamente de la misma


manera que en las aplicaciones basadas en ventanas, esto significa que el código para
gestionar el evento ActionEvent en el botón del applet del ejemplo será el mismo que
el del ejercicio anterior:

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);
}
}

8.4.4 INCLUSIÓN DE UN APPLET EN UN DOCUMENTO HTML

Para que un navegador pueda descargar y ejecutar un applet, es necesario que la


página donde éste se va a visualizar incluya una referencia al archivo .class que
posibilite su localización.

El lenguaje HTML dispone de la etiqueta <APPLET> para incluir referencias a applets


en páginas Web, el formato de esta etiqueta es:

<APPLET
code = “archivo_class”
codebase = “dirección_base”
width = “ancho”
height = “alto”
name = “nombre”>
</APPLET>

El significado de los atributos de <APPLET> es el siguiente:

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>

En la figura 8.17 se muestra el aspecto final de la página al cargarse en el navegador.

Figura 8.17 Aspecto de la página con el applet al cargarse en el navegador.

Pág. 113
Programación en Java

8.4.5 PASO DE PARÁMETROS A UN APPLET

Con el fin de poder personalizar determinados datos manejados por un applet es


posible suministrarle información en tiempo de ejecución, lo cual se lleva a cabo
mediante el paso de parámetros al applet desde la página HTML que la contiene.

Para pasar parámetros a un applet se utiliza la etiqueta <PARAM> en el interior de


<APPLET>, por cada parámetro que se quiera pasar se utilizará una etiqueta
<PARAM> de la siguiente forma:

<APPLET …>
<PARAM name=”nombre_parametro”
value=”valor_parametro”>
<PARAM name=”nombre_parametro”
value=”valor_parametro”>
</APPLET>

Donde los atributos de <PARAM> son:

name. Nombre del parámetro.

value. Valor del parámetro.

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:

public String getParameter(String name)

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

La página HTML donde se incluye la referencia al applet tendrá el siguiente aspecto:

<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.

9.1 APLICACIONES MULTITAREA EN JAVA

Pág. 116
Programación en Java

El paquete java.lang incluye dos elementos para la creación de aplicaciones multitarea


en Java: la clase Thread y la interfaz Runnable. Mediante estos, disponemos de dos
caminos alternativos a la hora de implementar una aplicación multitarea:

 Heredar la clase Thread


 Implementar la interfaz Runnable

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.

A pesar de esto, vamos a comenzar estudiando la creación de aplicaciones multitarea


mediante la extensión de la clase Thread, dado que el entendimiento de esta clase
resulta fundamental para la correcta utilización de la interfaz Runnable.

9.2 EXTENSIÓN DE LA CLASE THREAD

La creación y ejecución de tareas en modo multitarea utilizando este sistema requiere


la realización de los siguientes pasos:

 Sobrescritura del método run() de la clase Thread.


 Creación y ejecución de las tareas.

9.2.1 SOBRESCRITURA DEL MÉTODO RUN()

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:

public class Tarea1 extends Thread {


public void run() {
for (int i = 1; i < 101; i++) {
System.out.println(i);

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);
}
}
}

9.2.2 CREACIÓN Y EJECUCIÓN DE LAS TAREAS

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.

El método start() se encuentra definido en la clase Thread, la llamada a este método


provoca que el thread pase al estado “preparado”. En esta situación, el gestor de
tareas de la JVM (thread scheduler) podrá, en cualquier momento, elegir la tarea para
su ejecución invocando al método run() del objeto.

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.

El siguiente código, incluido en el método main(), se encarga de crear los objetos


Thread del ejemplo anterior y de ponerlos en ejecución:

public class Principal {


public static void main(String[] args) {
Tarea1 t1 = new Tarea1();
Tarea2 t2 = new Tarea2();
t1.start();
t2.start();
}
}

Aunque la clase Principal no hereda Thread, el código contenido en el método main()


también es considerado como una tarea más por la JVM. Esto significa que si hubiera
otras instrucciones después de las llamadas a start() de los threads, éstas serían
ejecutadas concurrentemente con el resto de las tareas.

9.2.3 MÉTODOS PARA EL CONTROL DE THREADS

Pág. 118
Programación en Java

Antes de estudiar cómo crear aplicaciones multitareas mediante la interfaz Runnable,


vamos a analizar algunos métodos interesantes proporcionados por la clase Thread
para el control de tareas.

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:

public static void sleep(long tiempo)

Se trata de un método estático que, al ser invocado, pone a “dormir” el thread en


ejecución durante los milisegundos indicados en el argumento. Mientras el thread está
dormido no podrá ser puesto en ejecución por el gestor de tareas, hasta que, una vez
transcurrido el periodo de descanso, vuelva al estado “preparado”.

Poner a “dormir” un thread es un mecanismo de ralentización de procesos que se


utiliza, por ejemplo, cuando las operaciones realizadas por el thread implican el acceso
a dispositivos lentos, de modo que para ajustar la velocidad de procesamiento de la
tarea a la del dispositivo, se mande el thread a dormir durante un tiempo.

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:

public class Tarea1 extends Thread {


public void run() {
for (int i = 1; i < 101; i++) {
System.out.println("El número es: "+i);
try {
Thread.sleep(100);
} catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
public class Tarea2 extends Thread {
public void run() {
for (int i = -1; i > -101; i--) {
System.out.println("El número es: "+i);
try {
Thread.sleep(100);
} catch(InterruptedException e){
e.printStackTrace();
}

Pág. 119
Programación en Java

}
}
}

La creación y ejecución de las tareas sería como en el caso anterior:

public class Principal {


public static void main(String[] args) {
Tarea1 t1 = new Tarea1();
Tarea2 t2 = new Tarea2();
t1.start();
t2.start();
}
}

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 de métodos para establecer y obtener el nombre de un


thread:

 public void setName(String nombre)


 public String getName()

También se puede establecer el nombre de un thread cuando se crea utilizando el


siguiente constructor:

 public Thread(String nombre)

Obtener thread en ejecución

La clase Thread dispone del método estático currentThread() que permite obtener una
referencia al thread actual en ejecución, su formato es:

 public static Thread currentThread()

El programa que se presenta a continuación consiste en la creación de tres tareas


encargadas de mostrar su nombre en pantalla, 100 veces cada una, ejecutándose en
modo multitarea. En este caso, dado que las tres tareas realizan la misma operación,
tan sólo será necesario tener una única implementación del método run():

public class Tarea1 extends Thread {


public Tarea1(String n) {
// Utiliza el constructor de Thread para establecer
// el nombre de la tarea
super(n);
}
public void run() {

Pág. 120
Programación en Java

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


System.out.println("nombre: " +
Thread.currentThread().getName());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Principal {
public static void main(String[] args) {
Tarea1 t1 = new Tarea1("pepe");
Tarea1 t2 = new Tarea1("ana");
Tarea1 t3 = new Tarea1("juan");
t1.start();
t2.start();
t3.start();
}
}

La ejecución de este programa produciría una salida similar a ésta:

nombre: juan
nombre: pepe
nombre: ana
nombre: juan
nombre: pepe
nombre: ana
nombre: juan
nombre: pepe
nombre: ana
:

Prioridad de un Thread

La prioridad de un thread es un factor importante a tener en cuenta por el gestor de


tareas a la hora de repartir la CPU entre las mismas. De forma predeterminada, todo
thread tiene asignada una prioridad en el momento de su creación, la cual es la misma
que la del thread que lo crea. En el ejemplo anterior los tres thread tendrán la misma
prioridad y ésta coincidirá con la del método main().

Es posible establecer explícitamente una prioridad a una tarea mediante el método


setPriority() de la clase Thread, su formato es el siguiente:

public void setPriority(int p)

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

A modo de recomendación, es necesario destacar el hecho de que no es conveniente


desarrollar una aplicación multitarea haciendo depender su funcionamiento de las
prioridades de los thread, puesto que el comportamiento del gestor de tareas con las
prioridades es impredecible, siendo totalmente dependiente de la implementación de la
JVM. Las prioridades tan sólo deben ser utilizadas como una forma de mejorar la
eficiencia de los programas.

El método yield()

Lo que hace este método es devolver el thread en ejecución al estado de “preparado”,


poniendo en ejecución otro thread de su misma prioridad.

Su formato es:

public static void yield()

La intención de este método es promover la rotación de la CPU entre threads de igual


prioridad, sin embargo, no hay ninguna garantía de que el nuevo thread elegido para
la ejecución no sea el mismo que se acaba de sacar de ejecución.

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.

El formato de join() es:

public void join()

Si “A” es un objeto thread, el thread que ejecute la instrucción:

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.

9.2.4 ESTADOS DE UN THREAD

Un thread puede encontrarse en uno de los siguientes estados:

 Nuevo. El thread entra en este estado al crear la instancia Thread. Permanece


en este estado hasta que se produzca la llamada a start(). Un thread en este
estado aún no se considera vivo.
 Preparado. Un thread entra en este estado al invocar al método start(), aunque
también puede regresar a este estado después de haber estado en ejecución,
dormido o bloqueado. En este estado el thread se considera vivo. Hay que tener

Pág. 122
Programación en Java

en cuenta que si se realiza una segunda llamada a start() se producirá una


excepción.
 Ejecución. El thread entra en este estado cuando el scheduler lo selecciona para
su ejecución.
 Esperando/bloqueado/dormido. Son tres estados combinados en uno y se
caracterizan porque el thread aún está vivo pero no preparado, pudiendo estar
dormido, bloqueado a la espera de algún recurso o en espera.
 Finalizado. Un thread se considera muerto cuando finaliza la ejecución de su
método run(). Cuando el thread finaliza no puede volver de nuevo al estado de
preparado, por ello, si se invoca al método start() sobre un thread finalizado se
produce una excepción.

9.3 IMPLEMENTACIÓN DE LA INTERFAZ RUNNABLE

La técnica más adecuada para la creación de aplicaciones multitarea en Java se basa


en la implementación de la interfaz Runnable, en vez de la extensión de la clase
Thread.

Si optamos por esta vía, las acciones que tenemos que llevar a cabo serán las
siguientes:

 Implementar método run() de la interfaz Runnable


 Creación y ejecución de tareas

9.3.1 IMPLEMENTACIÓN DEL MÉTODO RUN()

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.

Como ya se comentó al principio, codificar este método mediante la implementación de


la interfaz Runnable permite a la clase heredar al mismo tiempo la funcionalidad de
alguna otra clase existente.

A continuación, se presenta el código de la clase para la impresión de los nombres de


los thread implementando la interfaz Runnable:

public class TareaRb implements Runnable {


public void run() {
for (int i = 1; i < 101; i++) {
System.out.println("nombre: " +
Thread.currentThread().getName());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

Pág. 123
Programación en Java

Como podemos observar, el método run() no difiere en absoluto del de la clase


anterior. Sin embargo, hay que destacar una diferencia entre estas, y es que esta
última, al no heredar Thread, no dispone de un constructor al que pasarle el nombre
de la tarea, así que la pregunta es: ¿Cómo puede la llamada al método getName()
devolver el nombre de la tarea en ejecución? La respuesta a esta pregunta es que,
como veremos en el siguiente apartado, la llamada a currentThread() no devuelve un
objeto TareaRb, sino un objeto de la clase Thread que representa la tarea en
ejecución.

9.3.2 CREACIÓN Y EJECUCIÓN DE TAREAS

Como ya se ha mencionado en varias ocasiones, toda tarea en ejecución multitarea es


un thread. Esto significa, que habrá que crear tantos objetos de la clase Thread como
tareas queramos poner en ejecución, el tema es que el código asociado a esas tareas
debe ser el que está definido en el método run() de la clase que implementa Runnable.

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.

Así pues, la creación y ejecución de las tareas en el ejemplo de la impresión de


nombres quedaría como sigue:

public class Principal {


public static void main(String[] args) {
//Se crea un único objeto TareaRb, el cual
//es compartido por todos los thread
TareaRb t = new TareaRb();
//Las tres tareas son instancias de la clase Thread
Thread t1 = new Thread(t, "pepe");
Thread t2 = new Thread(t, "ana");
Thread t3 = new Thread(t, "juan");
//Los thread se ponen en ejecucion
t1.start();
t2.start();
t3.start();
}
}

9.4 SINCRONIZACIÓN DE THREADS

Pág. 124
Programación en Java

9.4.1 ACCESO CONCURRENTE A OBJETOS

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;
}
}

Partiendo de la existencia de la clase anterior, prestemos atención al código de la


siguiente clase:

class Tarea implements Runnable {


Accesos ac;
public Tarea() {
ac = new Accesos();
}
public void run() {
int actual = ac.getValor();
actual++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
ac.setValor(actual);
System.out.println("Total accesos: " + ac.getValor());
}
}

Utilizando un objeto de la clase Accesos, el método run() del programa anterior


pretende llevar la cuenta del número de veces que las tareas han ejecutado ese
código.

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

public static void main(String[] args) {


Tarea tr = new Tarea();
Thread t1 = new Thread(tr);
Thread t2 = new Thread(tr);
Thread t3 = new Thread(tr);
t1.start();
t2.start();
t3.start();
}
}

A partir de este momento, los threads comienzan su ejecución concurrente. Como ya


sabemos, no es posible saber con detalle qué es lo que va a pasar a partir del
momento en que los threads se ponen en ejecución, pero podría darse la siguiente
situación:

El thread t1 comienza su ejecución y llega a completarla, en ese momento, el total de


accesos es 1. Después, el thread 2 comienza su ejecución y justo antes de hacer la
llamada al método setValor(), el gestor de tareas lo saca de ejecución para poner en
funcionamiento a t3. Y es aquí donde viene el problema; como t2 no llegó a actualizar
el valor del contador, cuando t3 invoque a getValor() obtendrá el valor 2, en vez de 3.
Esta situación puede repetirse varias veces, por lo que nos encontramos con que el
objeto accesos no refleja realmente el número de accesos que se han realizado al
bloque de código.

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.

9.4.2 SINCRONIZACIÓN Y MONITORES

La sincronización de un bloque de código consiste en impedir la entrada de un thread


al bloque hasta que el último thread en entrar en dicho bloque finalice la ejecución del
mismo, de esta forma se garantiza que todas las instrucciones del bloque son
ejecutadas completamente por un thread antes de que otro entre en el mismo.

La sincronización de bloques de código se basa en el uso de monitores o “cerrojos”.


Todo objeto lleva asociado un monitor, el cual puede ser adquirido por un único
thread. Cuando se sincroniza un bloque de código, se necesario indicar el objeto cuyo
monitor será utilizado para controlar el proceso de sincronización, siguiendo la sintaxis
indicada a continuación:

:
synchronized(ref objeto) {
//bloque de instrucciones a sincronizar
}
:

Pág. 126
Programación en Java

En el momento en que un thread entra en el bloque sincronizado, adquiere el monitor


del objeto especificado, bloqueando la entrada de otros thread al bloque hasta que el
monitor del objeto sea liberado, hecho que sucede automáticamente cuando el thread
finaliza la ejecución del bloque sincronizado. A partir de ese instante, el monitor del
objeto queda libre y cualquier thread que esté a la espera de entrar en el bloque
sincronizado puede adquirirlo.

Utilizando la sincronización de código, podemos solucionar el problema planteado en el


ejemplo anterior, implementando la clase Tarea de la siguiente forma:

class Tarea implements Runnable {


Accesos ac;
public Tarea() {
ac = new Accesos();
}
public void run() {
synchronized (ac) {
int actual = ac.getValor();
actual++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
ac.setValor(actual);
System.out.println("Total accesos: " + ac.getValor());
}
}
}

Hay que tener en cuenta que cuando un thread se va a dormir, se lleva el monitor del
objeto consigo.

9.5 COMUNICACIÓN ENTRE THREADS

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.

Estos métodos son invocados en el interior de un bloque synchronized sobre el objeto


del que se adquiere el monitor, siendo el significado de cada uno de ellos el siguiente:

 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:

wait (int milisegundos)

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().

El siguiente código de ejemplo pretende aclarar el funcionamiento de estos métodos.


En él se presenta una clase Runnable, llamada Proceso, con un método run()
encargado de calcular la suma de los 100 primeros números naturales, por otro lado,
tenemos una clase con su método main(), cuya misión es instanciar un thread a partir
de la clase anterior y mostrar el resultado de la suma calculada por éste. Para que el
resultado mostrado por el método main() sea correcto, necesita esperar a que el
thread de Proceso le notifique que ha terminado de realizar la operación:

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

//monitor del objeto a fin de que pueda continuar la ejecución


this.notify();
}
}
}

Pág. 129

También podría gustarte