Programación usando Java

Transcripción

Programación usando Java
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
¡¡¡ Para que aprendas... !!!
Programación usando Java
Apuntes para Aprender
Programación en Lenguajes Estructurados
Reservados todos los derechos, en cualquier idioma.
El contenido de esta obra está protegido por la Ley.
No está permitida su reproducción o transmisión, total o parcial,
cualquiera que sea el procedimiento utilizado sin la autorización previa y
por escrito del autor.
Bajo ningún concepto está permitida su manipulación, o modificación.
Cada comprador estará autorizado a almacenar e imprimir un ejemplar
siempre que sea para su uso exclusivo. En caso de que el propietario desee
hacer accesible su copia publicamente (bibliotecas, centro públicos, etc..)
deberá antes pedir permiso al autor.
En cualquier caso dicho ejemplar no podrá ser de ningún modo reproducido
ni copiado.
Autor: Fernando Toboso Lara
Autoedición: Autor
e-mail: [email protected]
[email protected]
Registrado en el Registro de la Propiedad Intelectual.
Autor : FernandoToboso Lara
e-mail: [email protected]
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
dedico este libro a Carolina (la mujer de mi vida)
y a mis padres Matilde y Fernando
con todo mi cariño...
Autor : FernandoToboso Lara
e-mail: [email protected]
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
CAPITULO 1 - Generalidades.......................................................................................1
Introducción .................................................................................................................... 2
Evolución de los Ordenadores .................................................................................. 2
Ciclo de vida de una aplicación ................................................................................ 4
Documentación........................................................................................................... 5
Algoritmo.................................................................................................................... 6
Paradigmas de la programación............................................................................... 8
CAPITULO 2 - Conceptos..............................................................................................9
Evolución de los lenguajes ........................................................................................... 10
Programación Estructurada ................................................................................... 11
Modularidad............................................................................................................. 12
Programación Orientada a Objetos ....................................................................... 13
Traducción del código ............................................................................................. 14
Compilación e Interpretación versus Combinación de ambos........................ 14
Tipos de Errores....................................................................................................... 16
CAPITULO 3 - Toma de Contacto ..............................................................................17
Utilidades de Desarrollo............................................................................................... 18
Mi primer programa .................................................................................................... 19
Consideraciones a tener en cuenta: .............................................................. 20
CAPITULO 4 - Tipos de Datos ....................................................................................22
Qué son los tipos de datos ............................................................................................ 23
Datos predefinidos en Java .......................................................................................... 24
Generalizando... los datos en Java.......................................................................... 26
Declaración de identificadores .................................................................................... 27
Nomenclatura de la Programación JAVA ........................................................ 28
Palabras reservadas ............................................................................................ 28
Tipos Primitivos............................................................................................................ 29
Operadores ............................................................................................................... 31
Operadores Aritméticos ..................................................................................... 31
Operadores de Asignación.................................................................................. 32
Operadores incrementales.................................................................................. 33
Operadores relacionales ..................................................................................... 34
Operadores lógicos.............................................................................................. 34
Operador ternario condicional .... ? .... : ........................................................ 35
Operadores que actúan a nivel de bits .............................................................. 36
Ejemplo práctico............................................................................................. 36
Precedencia de operadores...................................................................................... 37
Conversión de Tipos ................................................................................................ 37
Tipos Referenciados ..................................................................................................... 39
Cadenas de Caracteres ............................................................................................ 42
Uso directo de Tipos Referenciados ....................................................................... 45
CAPITULO 5 - Sintaxis ................................................................................................47
Utilización de Comentarios.......................................................................................... 48
Sentencias ...................................................................................................................... 49
Sentencias declarativas............................................................................................ 49
final....................................................................................................................... 50
Acciones .................................................................................................................... 51
Otro posible criterio de catalogación: asignación, entrada, salida.................. 53
Bloque de Instrucciones........................................................................................... 54
Autor : FernandoToboso Lara
e-mail: [email protected]
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
CAPITULO 6 - Flujos de Ejecución de un Programa ...............................................55
Sentencias de control de flujo. ................................................................................ 56
Alternativas.......................................................................................................... 56
Diagramas de Nassi-Scheiderman (o de Chapin) ........................................ 61
Ejemplo de análisis para la resolución de una ecuación de 2º grado......... 62
Bucles.................................................................................................................... 65
Diagramas de Nassi-Scheiderman (o de Chapin) ........................................ 69
Sentencias de Ruptura ........................................................................................ 71
break................................................................................................................. 71
continue............................................................................................................ 73
Etiquetas.......................................................................................................... 73
CAPITULO 7 - Metodología Básica ............................................................................74
Contador ................................................................................................................... 75
Acumulador.............................................................................................................. 76
Cálculo de la media de una serie de números enteros................................. 77
Cálculo del factorial de un entero positivo................................................... 78
Lectura Anticipada .................................................................................................. 78
Interruptor ............................................................................................................... 80
Intercambio .............................................................................................................. 81
Búsqueda del máximo y del mínimo ...................................................................... 82
CAPITULO 8 - Tablas de Datos ..................................................................................84
Arrays de datos ............................................................................................................. 85
Recorridos de una tabla .......................................................................................... 89
Arrays Bidimensionales........................................................................................... 91
Arrays Multidimensionales..................................................................................... 93
Recorridos de una Tabla bidimensional ........................................................... 93
Recorridos de una Tabla multidimensional (con N índices) ........................... 95
Algoritmos de búsqueda.......................................................................................... 98
Tablas no ordenadas ........................................................................................... 98
Tablas de más de una dimensión................................................................... 99
Tablas ordenadas .............................................................................................. 100
búsqueda dicotómica (o binaria)................................................................. 100
Algoritmos de ordenación ..................................................................................... 101
Selección directa................................................................................................ 102
Inserción directa................................................................................................ 105
Intercambio directo (de la burbuja)................................................................ 108
Algoritmo de la sacudida (variante de la burbuja)........................................ 110
CAPITULO 9 - Modularidad.....................................................................................113
Subprogramas (Funciones)........................................................................................ 114
Sintaxis .......................................................................................................... 116
Sentencia return ............................................................................................ 117
Identificadores y datos locales..................................................................... 119
Parámetros .................................................................................................... 119
Parámetros de la función principal
... main ( String [ ] ... ).......................... 124
Recursividad........................................................................................................... 125
Errores de ejecución .............................................................................................. 127
Manejo de Errores ............................................................................................ 129
Excepciones (marcadas y sin marcar) ............................................................. 133
Autor : FernandoToboso Lara
e-mail: [email protected]
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
CAPITULO 10 - Clases y Objetos .............................................................................137
Introducción ................................................................................................................ 138
Atributos (datos agrupados) ................................................................................ 140
preparadosListos(...)..................................................................................... 144
avance(...)....................................................................................................... 145
Métodos (o funciones miembro) de una clase de objetos ................................... 146
Identificadores estáticos ........................................................................................ 150
Declaración y ámbito de identificadores.............................................................. 152
Inicialización........................................................................................................... 157
Inicializador estático (de clase) ........................................................................ 159
Código nativo ................................................................................................ 160
Inicializador no estático (de objeto) ................................................................ 161
Constructores (de objeto) ................................................................................. 161
Todo programa Java se compone de clases.............................................................. 163
Inicio de la ejecución de un programa ................................................................. 165
CAPITULO 11 - Paquetes y Permisos de Acceso.....................................................166
Utilización.................................................................................................................... 167
Creación....................................................................................................................... 171
Permisos de acceso...................................................................................................... 175
Necesidad de controlar niveles de accesibilidad.................................................. 175
Acceso a los paquetes............................................................................................. 176
Acceso a las clases (no anidadas) de un paquete ................................................. 176
Acceso a los elementos de una clase (a la cual se tenga acceso) ......................... 178
CAPITULO 12 - Reutilización del código.................................................................181
Herencia....................................................................................................................... 182
Inicializadores y Constructores de clases hija (derivadas por herencia).......... 185
Palabra reservada: super.................................................................................. 189
Palabra reservada: this ..................................................................................... 190
Todas las clases derivan de la clase Object .......................................................... 194
Destructores de clases............................................................................................ 195
Modificadores......................................................................................................... 197
final ................................................................................................................ 197
abstract .......................................................................................................... 198
Lanzamiento de Excepciones ................................................................................ 200
Interfaces ..................................................................................................................... 203
ejemplo: interface Comparable................................................................... 204
CAPITULO 13 - Varios ..............................................................................................207
Análisis de un objeto desconocido ........................................................................ 208
Operador instanceof.......................................................................................... 209
Clase Racional mejorada ............................................................................. 210
Clonación ................................................................................................................ 211
Herencia de interfaces ........................................................................................... 215
ejemplo: herencia múltiple de interfaces.................................................... 217
CAPITULO 14 - Polimorfismo ..................................................................................218
Enlace Tardío.............................................................................................................. 221
1.- Las existentes en todos los objetos de clases derivadas de una misma clase............ 223
2.- Las de todos los objetos de clases que implementan una misma interface... ........... 224
3.- Las que todos los objetos heredan de la clase Object... .............................................. 225
Autor : FernandoToboso Lara
e-mail: [email protected]
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
ejemplo: La Pizarra................................................................................................ 228
(en el paquete por defecto... directorio base) .................................... 231
(en el paquete figuras... subdirectorio figuras) ................................. 232
Código Completo de la Aplicación PIZARRA ............................................. 235
Clases alojada y herencia (uso polimórfico)............................................................. 240
ejemplo: Enumeracion de líneas...................................................................... 241
Clases anónimas.......................................................................................................... 242
CAPITULO 15 - Estructuras de Datos......................................................................245
Colecciones de datos ................................................................................................... 246
Iteradores................................................................................................................ 249
Ejemplo: Lista Enlazada ................................................................................... 251
class Collections ..................................................................................................... 253
Pila de datos y Cola de datos................................................................................. 254
CAPITULO 16 - Entrada/Salida de Datos................................................................256
Dispositivos de almacenamiento masivo................................................................... 257
Operando con ficheros........................................................................................... 259
Acceso Secuencial .............................................................................................. 260
Escribiendo en fichero.................................................................................. 261
Leyendo de fichero ...................................................................................... 262
Acceso Directo ................................................................................................... 263
Acceso Indexado................................................................................................ 267
Unicode ................................................................................................................... 268
Formato de Transformación Unicode (UTF) ................................................. 269
Flujos de Entrada/Salida....................................................................................... 270
La clase Entrada......................................................................................... 273
InputStreamReader y OutputStreamWriter.................................................. 276
BufferedReader y BufferedWriter
BufferedInputStream y BufferedOutputStream............................................ 277
FileReader y FileWriter FileInputStream y FileOutputStream................... 277
StringReader y StringWriter ........................................................................... 278
CharArrayReader, CharArrayWriter ByteArrayInputStream,
ByteArrayOutputStream.................................................................................. 279
PipedReader y PipedWriter PipedInputStream y PipedOutputStream ..... 280
SequenceInputStream....................................................................................... 281
FilterReader y FilterWriter FilterInputStream y FilterOutputStream ...... 282
PrintWriter y PrintStream............................................................................... 283
LineNumberReader .......................................................................................... 284
PushbackReader y PushbackInputStream..................................................... 284
DataInputStream y DataOutputStream ......................................................... 285
ObjectInputStream y ObjectOutputStream................................................... 286
Gestión del entidades fichero ................................................................................ 287
clase File ........................................................................................................ 287
interfaces FilenameFilter y FileFilter ....................................................... 290
Serialización de Objetos............................................................................................. 292
interface Externalizable ......................................................................................... 295
Entidades de almacenamiento de información específicas ..................................... 296
Acceso a Bases de Datos. ....................................................................................... 296
Autor : FernandoToboso Lara
e-mail: [email protected]
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Capitulo 1
Generalidades
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 1
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Introducción
La programación es, dicho a grosso modo, el área que se va a ocupar de cómo podemos
decirle a una máquina las acciones que queremos que realice.
Para un ordenador no existe la libertad, es un esclavo de las ordenes con que el
programador predetermina su comportamiento. Podríamos describir la programación
como el proceso de planificar secuencias de instrucciones que una computadora deberá
seguir.
Dicha planificación debe ser exhaustiva y abarcar absolutamente todos los casos ante
los que un ordenador deba responder. La ejecución de un programa ante una situación
no prevista tendrá un comportamiento igualmente imprevisible y puede suponer desde
la inmediata finalización de su ejecución, hasta la pérdida de datos que estuviesen
siendo procesados, efectos en la ejecución de otras aplicaciones, etc...
Evolución de los Ordenadores
La evolución de los ordenadores, acompañada de la evolución de los sistemas
operativos, hace que actualmente nos encontremos con máquinas de enormes
capacidades funcionales. Hoy por hoy a ningún usuario de ordenador le sorprende que
en su ordenador se ejecuten simultáneamente varias aplicaciones (procesador de textos,
su programa de dibujo favorito y otros).
Un ordenador es una máquina con dos características fundamentales:
· Actúa sobre entidades abstractas que constituyen la información y no sobre
los entes físicos que estos puedan representar.
· Es una máquina de propósito general, con un campo de aplicación
tremendamente amplio.
Hoy en día los ordenadores forman parte de multitud de aparatos electrodomésticos,
dispositivos de control en vehículos, aparatos multimedia, ascensores, cajeros
automáticos y mil más. Pero en sus comienzos, allá por los años 70, no pasaban de ser
súper calculadoras capaces de realizar sus operaciones con una tremenda precisión y
rapidez. Preparar los cálculos que deseaban que realizase era una tarea cada vez más
compleja y costosa. Un ordenador, con una potencia de cálculo muy inferior a cualquier
ordenador personal de los de ahora, bien podía ocupar el espacio de varias habitaciones.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 2
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Básicamente la arquitectura de cualquier ordenador se ajusta a un esquema, donde...
- La CPU se encarga de procesar
instrucciones muy elementales.
Unidad
- En la memoria central se
Memoria Central
Central
almacenan los datos e instrucciones
(ram)
de
que la CPU está a punto de
Proceso
procesar. Contiene la información
de los programas que se están
(C.P.U.)
Dispositivos Periféricos
ejecutando.
de Entrada/Salida y de
- Los dispositivos periféricos
almacenamiento masivo
incluyen dispositivos como:
teclados, modem, discos, cdrom,
redes, monitor, impresora, etc... con la finalidad de recibir, mostrar o almacenar
permanentemente la información que los programas gestionan.
El procesador (CPU) de un ordenador sólo entiende signos binarios que interpreta en
forma de señales de corriente alta (uno) o baja (cero). Además, cada modelo funciona de
modo diferente, por lo que su manejo llegó a ser tan complicado que fue necesario el
uso de una estrategia que los hiciera más accesibles, más manejables, al tiempo que
asegurase un funcionamiento lo más eficaz posible de un aparato que tenía coste muy
elevado.
Así aparece lo que llamamos Sistema Operativo. Un grupo de programas que se
ejecutan sobre una máquina en cuando ésta se enciende, de modo que...
… Hace de intermediario.
- Cualquier programa que después se ejecute en la máquina deberá dirigir sus
instrucciones al sistema operativo de modo que será éste quien determine si la
máquina debe realizar o no la tarea que se le solicita. Una petición de una tarea
puede ser denegada según la política de permisos de acceso y uso que se
establezcan.
… Organiza el trabajo.
- El sistema operativo organiza el trabajo de la máquina para optimizar al
máximo su rendimiento.
… Hace similares máquinas diferentes.
- Los programas dan órdenes al sistema operativo, de modo que un mismo
programa funcione en cualquier ordenador que disponga del sistema para el cual
dicho programa se realizó.
Tanto es así que a menudo se presta
más atención al sistema operativo que
tiene instalado una máquina que a la
propia máquina real, llamando
Máquina Virtual al conjunto que
forman la Máquina Real y el Sistema
Operativo.
Autor : FernandoToboso Lara
Aplicación
Sistema Operativo
Harware, circuitos,
máquina cableada.
e-mail: [email protected]
Máquina
Real
o máquina
cero
Pag. 3
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
En los ordenadores domésticos algunas de las máquinas reales más habituales son de
fabricación clónica (sin marca de fabricante) con CPU Intel o AMD, o las máquinas
Macintosh, todo ello con unas determinadas características de velocidad de proceso,
tipo de memorias y otras características físicas.
Los Sistemas Operativos son capaces de que una aplicación vea similares a dos
máquinas reales diferentes.
Algunos sistemas operativos se crean para una determinada máquina (como el caso de
Macintosh) mientras que otros distribuyen separadamente de ésta (como los
Windows’9x y NT, LINUX, UNIX, VMS, PING, etc...).
Con los adelantos que existen hoy en día en integración de circuitos electrónicos se
fabrican ordenadores cada vez más pequeños, más rápidos y más económicos. Esto ha
permitido que se hayan implantado en la práctica totalidad de los ámbitos de nuestro
entorno.
Con la creciente importancia de las redes informáticas, los sistemas operativos usan
cada vez más el enfoque que no sólo tienen la tarea de gestionar el buen funcionamiento
de una máquina, sino más bien la gestión de máquinas que forman un entramado cada
vez más interrelacionado.
Ciclo de vida de una aplicación
El proceso de desarrollo de una aplicación se divide en varias fases:
-
Analizar el problema que se intenta resolver (o informatizar) hasta llegar a
describir claramente la tarea que el sistema informático realizará, definiendo las
tareas a realizar, en qué modo se deben realizar, quién las va a programar, sobre
qué máquinas se va a ejecutar, quién la manejará, etc...
-
Implementación de las secuencias de pasos que cada una de las tareas
realizará, usando para ello lenguajes de programación que el ordenador sea
capaz de interpretar.
-
Probar la corrección del código resultante y que el ordenador realice lo que
debe tal y como se especificó en la fase de análisis.
Una vez realizados estos pasos tendremos un programa, un guión que la máquina
seguirá escrupulosamente. Un último paso será proceder a implantar la aplicación en el
contexto para el que se ha preparado:
-
Explotación y Mantenimiento de la aplicación poniéndola en uso para sacarle
partido realizando sus tareas, a la vez que puede ser necesario dar retoques para
que siga actualizada a los cambios que se puedan producir (cambios en las
máquinas, personal que las usa, legislación, cambios en las tareas a realizar,
etc..)
Cada una de estas fases deben realizarse con sumo cuidado, puesto que el material y
documentos que se obtiene de cada una pasa a la siguiente y un fallo detectado tarde
puede forzar a elaborar todo desde que se cometió el error, con la consiguiente pérdida
de tiempo y dinero que esto supondrá.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 4
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Con esto podemos definir como Ciclo de Vida de un Aplicación Informática al
periodo que va desde el momento en el que surge la necesidad de la aplicación y se
comienza el análisis de la misma, hasta que tras las fases vistas, la aplicación deja de
utilizarse por no ser ya necesaria (o por haber sido substituida por otra).
Documentación
Cuando un profesional escribe un programa no debe pensar sólo en la máquina que lo
ejecutará, sino también en otros colegas a los que debe hacer grata la tarea de
comprender el código del programa.
Programar no es cosa de una sola persona, es un trabajo de equipo. Incluso en el caso de
un programa que realice una única persona, una buena documentación será necesaria,
por ejemplo, cuando más tarde se necesite hacer alguna modificación (es increible lo
rápido que se olvida cómo funciona lo que uno mismo hizo).
Por esto es importante que los programas se acompañen de una buena documentación
principalmente enfocada a dos tipos de destinatarios: otros desarrolladores que
colaboren en el proyecto y los usuarios finales.
- Para los usuarios finales hay que preparar (por ejemplo) un manual de usuario donde
sólo interesa cómo deben manejar el producto final para sacarle el máximo partido con
la mayor sencillez posible, un manual de instalación con explicaciones sobre cómo
instalar (o implantar) la aplicación en un sistema, ...
- Para otros desarrolladores distinguiremos dos tipos de documentación:
o La documentación externa recoge el historial de desarrollo en las distintas fases
del proyecto: análisis, especificación, algoritmos aplicados, diagramas de las
distintas partes, etc...
o La documentación interna será la que vaya contenida en el código del programa y
nos interesa especialmente por que es responsabilidad directa del programador.
Son principalmente comentarios anexos que expliquen usando un lenguaje claro y
directo, qué hace (no cómo lo hace) el código al que acompañan.
- Al principio del código fuente una líneas de comentario deberían especificar:
el nombre del fichero que lo contiene, su fecha de creación y una breve
descripción de lo que hace el programa.
- A continuación una información más técnica de qué datos se introducen al
programa y cómo se introducen:
Qué datos generará o qué acciones realizará.
Premisas que se necesiten considerar para que el funcionamiento sea correcto.
Organización interna de los datos.
Organización por niveles del código.
etc...
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 5
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
- Además de esto, también se considera documentación interna lo que se suele
llamar código autodocumentado y que se refiere a todas aquellas consideraciones
a tener en cuenta para hacer más legible y comprensible el código que escribimos.
A lo largo del curso esto se planteará en diversas consideraciones que si bien no
serán precisas para que el programa funcione sí lo serán para no terminar
escribiendo auténticos galimatías.
Algoritmo
A la secuencia de pasos que describen lo que se debe hacer para resolver (en un tiempo
finito) un problema planteado se le llama algoritmo. Podría decirse que el propio
programa es el algoritmo completo de cómo una máquina actúa al ejecutar una
aplicación. Pero más bien se entiende que un algoritmo es algo enfocado a que lo
entienda una persona, no una máquina.
Quede claro que a menudo el algoritmo que resuelve un problema no es la única
solución posible. Casi siempre existe más de una forma de hacer las cosas pero
generalmente una de ellas resulta más apropiada que las demás.
Para resolver los problemas que irán produciendo los resultados deseado en los
programas, se debe comenzar por escribir los algoritmos que se van a aplicar para tener
una mejor visión de la manera en que se llega a la solución y poder aplicar un método lo
más eficaz posible.
Existen múltiples algoritmos que resuelven problemas típicos y habituales en
programación de manera que a veces simplemente se trata de elegir cuál de ellos se
utiliza.
El siguiente algoritmo resuelve metódicamente el problema conocido como “el cubo
mágico”.
Dicho problema propone que coloquemos los números de 1 al 9 sobre un tablero de 3x3
casillas, de modo que los números de cualquier fila, columna, o diagonal sumen sea 15.
Para conseguirlo basta con recorrer todas las casillas en el orden que describe este
algoritmo e ir colocando los números.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 6
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Se entiende por:
- Siguiente casilla:
o la casilla de la siguiente fila y siguiente
columna, si está vacía.
o si no, será la casilla de la columna anterior,
en su misma fila.
Donde además...
- ...la fila siguiente a la última será la primera fila y la
anterior a la primera la última.
- ...la columna siguiente a la última columna será la
primera columna y la anterior a la primera la última.
1. El recorrido se iniciará en la casilla situada más a
la derecha de la fila central. Allí se escribe el
número uno.
2. Se avanzar a la siguiente casilla y se escribe el
siguiente número.
3. Repetir el paso 2 hasta colocar todos los números.
Este algoritmo es válido para resolver
cualquier cubo mágico de tamaño NxN
(donde N siempre es impar) como el cubo de
5x5 de la figura de al lado en el que todas
las filas, columnas y diagonales suman 65.
Por desgracia, o quizás por suerte, las máquinas no comprenden los algoritmos escritos
en lenguaje natural (lenguaje cotidiano de las personas). El lenguaje que usamos para
comunicarnos es muy ambiguo y esto las máquinas “no lo pueden aceptar”.
nota.- En el lenguaje humano, la expresión “matar el tiempo” es aceptable (en según que contexto) pero
para una máquina significa algo demasiado abstracto y demasiado confuso.
Una buena idea será partir de la base de que el ordenador es una máquina totalmente
estúpida (esto queda demostrado desde el momento en que por darle una orden
mínimamente incorrecta es capaz de hacer lo contrario de lo que queremos que haga.)
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 7
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Paradigmas de la programación
Existen diferentes enfoques con los que podemos expresar al ordenador lo que
pretendemos de él.
La programación usada ya desde los primeros computadores usa un estilo Imperativo,
una secuencia de instrucciones que interpretadas por el ordenador consiguen que éste
realice la tarea que se le ordena.
Esta secuencia no es lineal sino más bien un circuito de instrucciones con un comienzo
y un final. En cada ejecución se ejecutan partes que otras veces quizás no se ejecutarán
mientras que otras se ejecutan repetidas veces, todo ello dependiendo de los datos que
en cada momento maneje el programa.
También surgen otras propuestas más adecuadas para según qué ámbitos. Por ejemplo,
ligado al desarrollo de la inteligencia artificial aparecen lenguajes que intentan facilitar
la especificación de cómo una máquina debe retroalimentarse de sus propios datos, para
aprender. A ello contribuyen lenguajes de Programación Lógica (como Prolog) y de
Programación Funcional (como Lisp).
A menudo la máquina cuyo funcionamiento se quiere programar dispone de diversos
puntos por los que recibe información de modo que dependiendo no sólo de los datos
que recibe sino también de la vía por la que le llegan, debe reaccionar dando una
respuesta adecuada. Para estos casos se usa un tipo de programación llamada Orientada
a Eventos, en la cual, el programa dispone de fragmentos de código que indican a la
máquina lo que debe hacer ante cada posible evento (llegada de alguna información, o
señal). Esto es apropiado por ejemplo para la programación en entornos gráficos, donde
a menudo la tarea del ordenador se centra en responder a cliks de ratón, marcado de
casillas, pulsación de botón, seleción de menús, ...
ejem.- Si se pulsa sobre el boton_A hacer esto, si se pulsa sobre el boton_B hacer lo
otro, si la hora es 15:30 hacer lo de mas allá y así para cada posible evento.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 8
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Capitulo 2
Conceptos
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 9
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Evolución de los lenguajes
A las computadoras hay que darles todo muy claro. Por eso es necesario traducir
(codificar) los algoritmos a un lenguaje de programación, un lenguaje con un conjunto
de reglas (sintaxis), símbolos (léxico) y significados (semántica) que describa sin
ambigüedad al ordenador los pasos que debe dar para aplicar el algoritmo.
El enfoque que se la ha dado a la programación desde la aparición de los primeros
ordenadores ha evolucionado mucho.
Inicialmente lo más importante era la máquina y todo se hacía en función de que el
programa resultante fuera lo más óptimo posible para ésta. Programas muy eficaces y de
poco tamaño para unas máquinas muy limitadas en velocidad de cálculo y con poca
capacidad de almacenamiento de información.
Pronto las máquinas mejoran y los programas son cada vez más grandes y complejos, de
manera que hacen complicada la labor de los programadores, que al tener que detallar
excesivamente sus instrucciones se ven abrumados no sólo por la dificultad creciente de
escribir el código sino también porque después se ven incapaces de mantenerlos (hacer
correcciones, modificaciones, mejoras, ...) .
Los lenguajes van buscando estrategias que faciliten la descripción de los problemas
que los informáticos deben codificar. Estrategias con las que crear un código más fácil
de entender (para las personas), que el desarrollo sea fácilmente compartible por las
personas de un equipo, que los trabajos de un proyecto puedan ser aprovechados en
otros trabajos posteriores, etc...
Cuanto más fácil resulta el código para las personas (aun a costa de perder eficacia en la
máquina) más alto nivel se dice que tiene un lenguaje de programación. Obviamente la
pérdida de eficacia se ve enormemente compensada por las mejoras técnicas de las
máquinas cada vez más potentes que ejecutan los programas. Consecuentemente las
simplificación de los lenguajes genera programas que pueden crecer en complejidad sin
que ésta llegue a superar la capacidad de sus creadores.
Un ordenador sólo comprende instrucciones codificadas en binario (ceros y unos), de
hecho los primeros programas eran directamente escritos como una ristra de datos
binarios. Es de suponer lo engorroso de introducir esto en la computadora y lo fácil que
sería equivocarse:
Código Máquina
(directamente interpretado por el microprocesador)
Instrucción
.......Datos...............
10101010 11001100 01110100
01010111 01010101
10001110 10001011 10001100
.......y sigue...
El primer paso para ‘humanizar’ el trabajo del programador se dio con los lenguajes
Mnemotécnicos y Ensambladores que asocian al código de cada instrucción un nombre
con la finalidad de que se recuerde más fácilmente.
Código Ensamblador
(casi directamente interpretado por el
microprocesador)
Autor : FernandoToboso Lara
Instrucción
.......Datos...............
MOV
11001100 01110100
JUMP
01010101
INC
10001011 10001100
.......y sigue...
e-mail: [email protected]
Pag. 10
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Después van surgiendo lenguajes con instrucciones cada vez más distantes de las que el
microprocesador realmente ejecuta y más cerca de lo que el programador necesita
expresar en sus programas.
Lenguaje Pascal
(cada instrucción es compleja y al traducirse da lugar
a múltiples instrucciones para el microprocesador)
..... ..... ....
VAR
INTENTAR: INTEGER;
...... ...
BEGIN
IF(INTENTAR) THEN
WRITELN(‘Es posible’);
.. ... ......
......y sigue...
Programación Estructurada
En los primeros lenguajes de programación hubo una sentencia que desempeñaba un
papel fundamental y que estaba directamente inspirada en una instrucción habitual de
los microprocesadores... la sentencia de salto. La norma respecto al orden en que por lo
general se ejecutan las sentencias de un programa consiste en que lo hagan
secuencialmente en el orden en que aparecen escritas.
La instrucción de salto (más conocida como ‘goto’) suele actuar dependiendo de alguna
condición de manera que si es cierta, la ejecución no continúa por la siguiente
instrucción sino que pasa a otra distinta cuya posición debe indicar la instrucción de
salto.
- Si la condición no se cumple, la ejecución continuará por la siguiente pero si se
cumple, pasa al sitio indicado por la sentencia de salto.
Las instrucciones de salto pueden ubicarse en cualquier sitio al igual que las sentencias
a las que se salta.
La instrucción ofrece muchísimas posibilidades, quizá demasiadas, porque a menudo su
utilización generaba programas imposibles de entender por la cantidad y complejidad de
los saltos. Las posible líneas de ejecución de los programas eran tan liosas y
complicadas de seguir que más tarde a este estilo se le llamó metafóricamente
programación espagueti.
linea1
linea2
linea3
linea4
linea5
linea6
linea7
linea8
linea9
linea10
linea11
… ....
.. .....
· ··· ·· ·
··· · ·· ·······
linea_N
Autor : FernandoToboso Lara
sentencia
si se cumpe una condicion salta a la linea11
sentencia
sentencia
si se cumpe una condicion salta a la linea7
sentencia
si se cumpe una condicion salta a la linea29
si se cumpe una condicion salta a la linea1
si se cumpe una condicion salta a la linea4
salta a la linea3
sentencia
sentencia
e-mail: [email protected]
Pag. 11
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Estos programas, siempre que no fuesen demasiado grandes, resultaban muy eficaces
inicialmente pero después resultaba muy costoso realizar sobre ellos modificaciones o
ampliaciones. Sólo lograba comprenderlos quien los había realizado y no con poca
dificultad.
Por esto acabó extendiéndose el uso de una estrategia que impone una estructura a la
programación, haciendo de este modo los programas más legibles y fáciles de entender.
Nace así la idea de la programación estructurada que se caracteriza principalmente por
exigir el cumplimiento de las siguientes normas:
-
Un programa debe tener un único punto de comienzo y un único punto de
finalización.
El orden de ejecución de las sentencias se deberá ajustarse exclusivamente a
uno de estos:
o Secuencia: orden implícito y por defecto de cualquier sentencia.
o Alternativa: en función de una condición se presentan generalmente dos
opciones de ejecución escritas en las líneas de código que van
inmediatamente a continuación de la condición (sin saltos).
o Repetición: en función de una condición, un bloque compuesto por una o
más sentencias (escritas junto a la condición) se ejecutan repetidas veces en
tanto que la condición se siga cumpliendo.
nota.- Para aquellos que tenga curiosidad sobre este tema existe un teorema que demuestra de manera
analítica que cualquier programa (programable con sentencias de salto) puede de igual modo
programarse de manera estructurada (sin usar saltos).
Durante el desarrollo del libro iremos viendo cómo java respeta esta metodología y de
qué manera implementa las tres variantes de control para el orden de ejecución de sus
sentencias.
Modularidad
Otro gran avance de la programación se produce con la introducción de estrategias de
modularidad que permiten repartir fácilmente el código de un programa en varios
bloques, o módulos.
La programación no modular que se venía utilizando hasta ese momento a menudo
recibe el apodo de programación monolítica, puesto que toda la aplicación se hacía en
un sólo bloque, en un único fichero de código.
Los módulos son fragmentos de código que funcionan del modo más independiente
posible, por lo que suelen escribirse en ficheros separados. Cada cual se ocupa de una
parte de la aplicación y de este modo se consigue...
… que cada módulo al ocuparse solamente de una parte del programa sea más
sencillo de codificar, documentar, probar y mantener.
… y que además el trabajo puede repartirse entre varios equipos de modo que al
trabajar simultáneamente consiguen terminar en mucho menos tiempo.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 12
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Cuando la codificación de los
módulos se concluye son agrupados
para formar la aplicación completa.
De entre ellos uno suele ser el módulo
principal a partir del cual se organiza
la ejecución de los demás.
Cada trozo de programa
(o subprograma) puede recibir una
serie de datos para procesar y a partir
de los cuales realizar la tarea que se le
haya encomendado.
MODULO ...
encargado de leer una
serie de nombre y edades
correspondientes a un
grupo de personas
MODULO ...
encargado de mostrar con un
formato de presentación
específico una serie de datos
MODULO ...
encargado de ordenar
una serie de datos
MODULO ...
encargado del cálculo de la
media de una serie de
datos numéricos
En el contexto de la programación clásica estructurada a cada uno de estos fragmentos
de programa se le han dado varios nombres como: función, subrutina, procedimiento,
subprograma, módulo, ...
Programación Orientada a Objetos
Este nuevo enfoque de la tarea de programar supone un cambio de conceptos tremendo
para los programadores acostumbrados a anteriores estilos de programación sin
embargo, una vez entendida la idea resulta muy intuitiva y las ventajas muy numerosas.
La programación orientada a objetos consigue que los lenguajes de programación
resulten más parecidos a los lenguajes naturales que usamos las personas.
Por ejemplo, permite que las acciones solicitadas tengan un significado diferente
dependiendo del contexto en que se utilizan (no significa lo mismo que el precio
aumente, o que un sonido aumente, ni que la altura de algo aumente). Pero es sólo un
ejemplo y ya se verá cómo lo aplicamos a la práctica.
Posiblemente lo más llamativo de esta metodología consiste en que unifica en una sola
entidad la información (o datos) y la instrucciones que se encargan de modificarla.
La programación tradicional siempre ha establecido una clara diferenciación entre las
instrucciones y los datos. La visión clásica de un programa suele ser la de una especie
de caja negra que procesa datos.
Datos
de
Entrada
Instrucciones
del PROGRAMA
Datos
PROCESADOS
Datos
Propios
La programación orientada a objetos atribuye a cada tipo de dato, además de un
conjunto de valores o estados posibles, un conjunto de instrucciones asociadas a él
íntimamente. Según este nuevo modelo, los datos de un determinados tipo actúan como
contenedores de información a la cual podemos acceder exclusivamente a través de las
instrucciones (u operaciones) que son propias del tipo de dato en cuestión.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 13
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Las aplicaciones podrían entenderse como conjuntos de datos que interactúan entre sí
para obtener un resultado o generar una acción.
Java desarrolla de manera muy potente esta metodología de programación por lo que
tendremos oportunidad de comprender en profundidad lo que en este apartado sólo
podemos entrever y otras características en las que por ahora no merece la pena
ahondar.
Traducción del código
Existen muchos lenguajes de programación así como también diferentes maneras de
clasificarlos según sea el criterio que nos interese. Según la metodología de
programación que se quiera utilizar, según el ámbito en el que funcionará el programa,
según el grado de seguridad que deba ofrecer, según la tarea que van a desempeñar los
programas que con ellos codificamos (contabilidad, cálculos científicos, juegos,
sistemas de comunicación, etc...) pero este momento nos interesa destacar la
catalogación...
... según el alto o bajo nivel del lenguaje. Más alto nivel cuanto más se asemeja al
lenguaje natural o humano; más bajo cuanto más se parezca al código que utiliza la
máquina.
... y según el momento en el que el código fuente es traducido al código de la máquina,
se tratará de un lenguaje compilado o interpretado.
nota.- Este último criterio nos permitirá explicar uno de los motivos del éxito de Java.
Compilación e Interpretación versus Combinación de ambos
Cuando un algoritmo se codifica en un determinado lenguaje se escribe en un fichero de
texto (usando un editor de texto cualquiera) al que denominaremos fichero de código
fuente. Ese texto es muy complicado para el ordenador por lo que un programa se
encargará de traducirlo a código máquina, un código muy elemental compuesto por los
instrucciones que la máquina es capaz de entender rápidamente.
Esta traducción se lleva a cabo tradicionalmente en dos momentos distintos:
·
Código Interpretado: cada uno de los pasos se va traduciendo en el momento en
que el ordenador va a realizar la tarea descrita.
VENTAJA:
(inmediatez) el funcionamiento no requiere la traducción completa sino
sólo la que corresponde a la parte ejecutada y cualquier modificación se
tiene en cuenta inmediatamente.
INCONVENIENTE:
(lentitud) las parte ejecutadas más de una vez se traducen
nuevamente cada vez que se vuelven a ejecutar.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 14
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
·
Código Compilado: primero se traduce todo el código generando un fichero que
contiene todo el código en forma incomprensible para un humano, pero comprensible
para el ordenador. Después el ordenador ejecuta las instrucciones que lee
directamente de este fichero.
VENTAJA: (eficacia) la ejecución es mucho más rápida.
INCONVENIENTE: (muy específico) cada tipo de ordenador requiere su propia versión
del fichero compilado y cualquier modificación requiere que se repita
todo el proceso de traducción.
Ambos modelos se han usado respectivamente en ámbitos para los que era propicio,
pero en el desarrollo de aplicaciones se han impuesto (por eficacia) los programas
compilados.
Java nace con la finalidad de crear programas que sean aceptables para una gran gama
de aparatos programables (videos, programadores domésticos, decodificadores de TV,
etc...) para lo cual utiliza un modelo que combina lo anteriormente expuesto.
El código fuente (java) se compila para que resulte cercano a lo que una máquina
cualquiera entiende, pero no del todo para evitar predeterminar la máquina en la que se
va a ejecutar. El código resultante queda a mitad de traducir, de modo que la última
parte se deja pendiente para que sea la maquina que finalmente lo leerá, la que complete
la traducción interpretando cada línea del código semi-traducido.
Por ejemplo, la orden de visualizar una información se traduce hasta un punto genérico
y común para todos los aparatos. Después, cada aparato interpreta el modo más
apropiado de mostrarla en sus propios dispositivos de visualización.
Con la aparición de internet se crea un ámbito común en el que son evidentes las
diferencias entre los muchos ordenadores de diferentes tipos y donde el modelo de
tradución compilada no resulta adecuado.
El interprete de java instalado en cada máquina hace que todas entiendan un lenguaje
común que, por otra parte, no les es difícil de interpretar puesto que el compilador java
ya habrá simplificado tremendamente el esfuerzo, permitiendo una ejecución más eficaz
que si el código fuese totalmente interpretado.
El interprete de java es un programa totalmente compilado que debe ejecutarse en cada
máquina que ejecuta programas hechos en java.
Además existen otras ventajas que el lenguaje a aportado a la programación y que
iremos descubriendo poco a poco.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 15
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Tipos de Errores
Los inevitables errores que se pueden cometer a lo largo de las fases de desarrollo de
una aplicación también se catalogan en función de las fases en las que se produce y los
defectos que puede producir.
Cada tipo de error supone en mayor o menor medida el encarecimiento del producto que
se esté desarrollando. Cuanto más tarde se detecta un error más costoso resulta
localizarlo y corregirlo.
En general los errores se agrupan en:
- Errores de diseño. Son los que se cometen en la fase de análisis y están
relacionados con la forma en que se organizan los módulos de la aplicación, los
algoritmos que se aplican, la adaptabilidad de la aplicación ante posibles
cambios, errores en la especificación de lo que cada módulo debe hacer, etc...
Suelen ser los más problemáticos puesto que cuando se detectan en fases
posteriores suelen forzar a revisar nuevamente el proyecto desde su primera fase.
- Errores de compilación. Son los que se presentan al escribir en un determinado
lenguaje por fallos en la sintaxis (reglas de composición), en el léxico (palabras
válidas), o en la semántica (significado) del código fuente.
No son demasiado preocupantes puesto que el compilador revisa el código y nos
notifica a veces incluso el número de línea del fichero que contiene el error.
- Errores de ejecución. No impiden que el fichero fuente pueda ser compilado,
incluso es posible que alguna veces se ejecute correctamente. Sin embargo, ante
determinadas situaciones (o quizás siempre), produce un mal funcionamiento
haciendo imposible que el programa se siga ejecutando. Se dice entonces que el
programa aborta su ejecución (termina bruscamente). Se producen estos errores,
por ejemplo, cuando se intenta realizar una división por cero ( 34/0 ), cuando el
programa intenta leer o escribir en algún fichero para el que no posee permiso de
acceso, etc...
- Errores de lógica. Son dificultosos de localizar puesto que, a pesar de ellos, el
programa se ejecutará correctamente en apariencia pero los resultados que ofrece
no son los correctos. A menudo se trata de expresiones incorrectamente escritas.
Un ejemplo... cuando ante el cálculo de los intereses de un préstamo se utiliza una
fórmula errónea de manera que la aplicación funciona pero realiza mal sus
cálculos y facilita datos equívocos o realiza acciones inadecuadas.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 16
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Capitulo 3
Toma de
Contacto
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 17
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Utilidades de Desarrollo
La compañía Sun, creadora de Java, distribuye gratuitamente el Java(tm) Development
Kit (JDK). Se trata de un conjunto de programas que permiten compilar y ejecutar
programas en Java. También distribuyen gratuitamente una versión reducida
denominada JRE (Java Runtime Enviroment) destinada únicamente a ejecutar código
Java. No obstante, tanto para compilar como para interpretar, existen diversos
programas entre los que podemos elegir.
Para empezar a trabajar sólo se necesita instalar en el ordenador el kit de desarrollo
mencionado, conjunto de ficheros y ejecutables que se puede encontrar, por ejemplo, en
la dirección de internet www.sun.com , o en diversos CD’s en los que se distribuye
gratuitamente junto con diferentes utilidades relacionadas con java.
nota.- Si eres usuario de Mac, las utilidades correspondientes tanto para compilar como para ejecutar tus
programas java (como es habitual en el entorno Mac) serán utilidades gráficas, pero funcionarán de
manera similar al resto de sistemas.
En la dirección de Sun se podrá también encontrar en formato HTML (fichero
consultable desde cualquier navegador de internet) toda la documentación con las
especificaciones técnicas sobre el funcionamiento de los diferentes “elementos” que se
utilizan en java, así como un estupendo tutorial (en inglés) para aprender java que te
permitirá consultar o ampliar fácilmente cualquier información que pudiese no quedar
suficientemente clara en este curso.
nota.- Al final del libro aparece una relación más extensa de referencias interesantes.
Procura instalar la última versión disponible del JDK.
Básicamente, sólo será necesario colocar este conjunto de ficheros (que suelen
distribuirse comprimidos) dentro de un directorio y disponer de un editor de textos
cualquiera.
nota.- No es lo mismo editor de textos (que se usará para escribir el código de los programas), que
procesador de textos. Este último no nos interesa, pues no sólo escribe caracteres (letras y símbolos)
sino también códigos de tipos de letra, tamaño de letra, color y demás propiedades típicas de los
textos con formato de presentación.
Existen editores que aportan utilidades que facilitan la escritura del código, como la de destacar con
colores las distintas categorías de palabras de un determinado lenguaje (variables, operaciones, etc).
Será opcional (pero muy recomendable):
- Añadir la ruta del directorio que contenga los ejecutables (compilador, interprete y
otros) a la variable de entorno que haga posible su ejecución desde cualquier otro
directorio. (PATH en el caso de Ms-dos/Windows)
- Crear la variable de entorno CLASSPATH con las rutas de los directorios que
contengan librería de programación java que se vayan a usar.
- Preparar un directorio dentro del cual se puedan ir colocando todos los ficheros con
los programas que vayamos realizando.
nota.- Para cualquier duda consulta a tu administrador de sistema... y si eres tú mismo, consulta el manual
del sistema operativo y la documentación de cómo instalar el JDK que tengas.
Existen muchos programas comerciales que pueden simplificar y hacer más cómodo el
proceso de desarrollar código java.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 18
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Los IDEs (Integrated Development Enviroment) o Entornos de Desarrollo Integrados,
son programas que, tal y como su nombre indica, integran todas las funciones necesarias
para desarrollar un programa: un editor de texto, un compilador, un intérprete y otras
utilidades prácticas de las que aún no se ha tratado, enfocadas a facilitar nuestro trabajo.
Por mencionar algunos:
- JBuilder
- Forte
- JCreator
- Code Warrior
- Visual Café
- WebObject
- VisualAge for java
Para obtenerlos puedes acceder vía internet a la página web del fabricante y descargarlo
directamente a tu ordenador o solicitar que te lo envíen en formato CD, previo pago de
su precio. No todos se venden, algunos se regalan y otros –shareware– tienen un precio
muy económico con una calidad nada despreciable. Es posible que estos IDE necesiten
alguna configuración inicial. Sigue sus instrucciones.
A menudo estos entornos se limitan a la parte visual construyendo un espacio de trabajo
sencillo pero práctico y a la hora de compilar e interpretar la ejecución , utilizan el JDK
(que presuponen instalado) o bien otro que ello mismos incluyan en su instalación.
Mi primer programa
Supongamos que ya tienes el “kit” de desarrollo java en tu ordenador, correctamente
instalado y configurado. Vamos a escribir nuestro primer programa java...
nota.- El código imprescindible es el que va en negrita.
/**
Fichero: Saluda.java
Este deberá ser forzosamente el nombre del fichero (respetando incluso la
letra mayúscula del comienzo).
Este es un programa que simplemente muestra por la salida estándar
del sistema el mensaje --- Hola Persona --**/
public class Saluda {
public static void main(String[] argumentos) {
// Muestra Hola Persona
System.out.println("Hola Persona");
}
}
Una vez que tenemos el código fuente escrito, el primer paso que daremos será
compilarlo. El programa que compila se llama javac y lo ejecutaremos en la línea de
comandos de Ms-dos (o en el shell correspondiente del sistema en el que se trabaje)
escribiendo detrás el nombre del fichero fuente que se quiere compilar.
C:\> javac Saluda.java
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 19
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
El compilador generará como resultado un fichero con el nombre, no del fichero fuente
sino, del programa implementado (class Saluda) y con extensión “.class”
nota.- Por clase (o class) se entenderá, por ahora, algo así como programa y el nombre que le sigue será el
nombre del programa.
C:\> dir Saluda.*
.
<DIR>
..
<DIR>
SALUDA~1 CLA
SALUDA~1 JAV
......
431
293
29/01/01
29/01/01
09/02/01
09/02/01
22:16
22:16
12:26
11:42
.
..
Saluda.class
Saluda.java
Este fichero contiene un código que llamaremos neutro, porque es general y vale para
cualquier ordenador que disponga de interprete java.
En nuestro caso para ejecutarlo llamaremos al interprete de Sun java escribiendo detrás
el nombre de la clase.
C:\> java Saluda
Hola Persona
El interprete del ordenador se ejecutará e irá traduciendo el código neutro del programa
al código que cada máquina realmente comprende ejecutandolo al tiempo que se va
realizando la traducción.
Consideraciones a tener en cuenta:
·
·
·
·
Java considera distintas la letras mayúsculas y minúsculas, por lo que no es lo
mismo class que Class, que CLASS, que clAsS, ...
Tanto el programa compilador javac como el interprete java deben estar
accesibles para ejecutarse desde el directorio en que estamos trabajando, de no ser
así será necesario ejecutarlos escribiendo el camino completo de directorios en el
que estén instalados.
El compilador necesita encontrar, además del fichero *.java con el código fuente
del programa que va a compilar, ciertos ficheros llamados librerías. Si no los
encuentra el compilador generará un error y no podrá compilar el programa.
El interprete de igual manera necesita encontrar, además del fichero *.class
resultante de la compilación, estos mismo ficheros e igualmente si no los
encuentra genera un error y no podrá ejecutar el programa.
Si al ejecutarse (el compilador o el interprete) se incluye en la línea de ejecución,
la opción -classpath, todos estos ficheros se buscarán solamente en los directorios
que esta opción indique.
Si no, en caso de existir la variable de entorno CLASSPATH, se buscarán estos
fichero en los directorio que la variable indique.
Si tampoco esta variable existe, por defecto la búsqueda se hará tanto en el
directorio en donde ellos mismos (compilador e interprete) estén instalados como
en el directorio actual desde el que se hayan ejecutado.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 20
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Si haciendo uso de un IDE o directamente con los comandos de línea del JDK de Sun,
has conseguido compilar y ejecutar tu primer programa java estás en el buen camino.
Vamos a comentar algunos aspectos interesantes del código de nuestro primer ejemplo.
public class Saluda {
.. . ...
}
class es una palabra reservada del lenguaje (es decir, que tiene un uso específico y no se
puede usar con otro fin). Indica que a continuación (entre las llaves { ... } ) vamos a
describir cómo es y qué hace algo. En este caso y en los que por ahora se verán, ese
algo será nuestro programa.
Para identificar esta descripción, se le asocia un nombre, Saluda. A continuación se
abren llaves y todo lo que escribiremos hasta cerrar con la llave correspondiente,
describirá nuestro programa.
....
public static void main(String[] argumentos) {
. .. .....
}
....
Este es el encabezamiento del módulo principal (main en inglés) y del mismo modo que
lo anterior va seguido de una pareja de llaves dentro de las cuales se describen las
acciones que realizará el programa.
Los detalles de esta línea los dejamos pendientes para un mejor momento, por ahora
puede limitarse a transcribirlo a sus programas tal como está.
.... ...
System.out.println("Hola Persona");
.. ......
Esta instrucción le dice al ordenador que imprima en pantalla (o en cualquiera que sea la
salida estándar del ordenador) el texto que aparece entrecomillado entre los paréntesis.
Nótese que la instrucción finaliza con un punto y coma. Aunque habitualmente se
escriba una instrucción por línea para hacer más legible el código, el compilador no
tendrá ningún problema si encuentra todo escrito en una sola línea. Para él, lo
importante será encontrar el signo “;” marcándole el final de la instrucción.
Existen diferentes categorías en las que se suelen clasificar las instrucciones básicas
usadas en la mayoría de los lenguajes, pero antes trataremos de otro asunto fundamental
para escribir nuestros programas, los datos.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 21
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Capitulo 4
Tipos de Datos
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 22
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Qué son los tipos de datos
Se entiende por dato (u objeto), entidad:
... que describe los posibles estados (o valores) posibles de algo. Quizás no todos
sus posibles estados pero sí aquellos que interesan a la aplicación que los
maneja.
... y que es capaz de cambiar y actuar de modo similar a como lo hace aquello
a lo que representa.
Lo que representa puede ser algo tan abstracto como: un número, o tan real como: un
avión.
- Si el dato representa un número entero, como es de suponer, no podrá representar el rango
infinito de números pero sí los suficientes como para que nuestro programa pueda realizar
cálculos con la suficiente precisión.
Por otra parte, este dato cambiará como los números que representa, mediante operaciones
propias de números enteros.
- Si el dato representa un avión, no representará todos los detalles del avión sino sólo aquellos
que resulten significativos para la aplicación que lo use. Para una aplicación de control de
tráfico aéreo, el dato avión estará probablemente representando su plan de vuelo, posición
actual, quién lo pilota, si está en el aire o no lo está, peso del equipaje, etc... en fin, todos
aquellos datos que definan lo que para la aplicación de control es un avión. Por otra parte el
dato avión modificará su estado, es decir los valores de sus diferentes características, de modo
similar a como cambian en el objeto real que representa (por ejemplo el peso del equipaje no
podrá exceder de un máximo, su plan de vuelo contendrá la ruta que seguirá el avión
representado, ...) y actúa igualmente como él (por ejemplo despega... y su estado varía
indicando que está en el aire)
Rara vez un dato tiene un comportamiento único, lo normal es que sea similar a otros,
por eso resulta deseable conocer cómo son este conjunto de datos similares en lugar de
tener que describir cada dato cada vez que se usa.
Cada dato se ajusta a un tipo de dato que describe de qué estados dispone y cómo se
comporta. Esto se concreta en dos elementos:
- qué valores (o conjunto de valores) describen el estado de cada dato de este tipo.
- qué acciones (u operaciones) se le pueden solicitar.
Cuando se escribe un programa, se dispone de una serie de tipos de datos predefinidos
que el compilador conoce, de modo que se pueden utilizar tantos datos de dichos tipos
como sean necesarios.
Ejemplos básicos de estos tipos predefinidos son los número enteros y reales, datos
lógicos (verdadero/falso) y caracteres que están disponibles en casi todos los lenguajes
de programación modernos.
A menudo los lenguajes disponen de más tipos de datos con los que se facilita la tarea
de escribir un programa.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 23
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Datos predefinidos en Java
En java existen una serie de datos predefinidos (además de tipos de datos predefinidos
que se estudiarán) que pueden ser utilizados directamente.
Por ejemplo, el dato System que representa a la máquina en la que se ejecuta el
programa, sus estados representan el estado actual del ordenador y a veces pueden ser
consultados o modificados accediendo a los datos más elementales de los que se
compone, o bien, solicitándole que realice alguna acción que se precise.
- Uno de los estados más obvios que podemos consultar es la hora actual del sistema,
escribiendo:
System.currentTimeMillis();
// calcula la hora del sistema.
... obtenemos el tiempo transcurrido en milisegundos desde el 1 de Enero de 1970
hasta el instante actual, de donde resultaría elemental obtener la hora del sistema.
- Un dato ya usado en el ejemplo anterior y que forma parte de System, es el dato out,
que representa al canal de salida para los mensajes del programa (habitualmente la
pantalla) y que ofrece entre otras acciones la anteriormente utilizada println(...) que
muestra el mensaje que se le coloca entre los paréntesis.
System.out.println(“-Mensaje-”);
// visualiza
-Mensaje-
en la pantalla
En el siguiente ejemplo se ve cómo se utilizan algunos datos; uno de tipo número entero
(int), otro de tipo frase (String) y una línea de la que baste decir por ahora que nos sirve
para leer un número entero de la entrada estándar (el teclado).
/**
Fichero: Edad.java
Primero pide que se le introduzca la edad de una persona,
para luego mostrar un mensaje por la salida estándar.
**/
import java.io.*;
public class Edad {
public static void main(String[] argumentos) {
int edad;
String nombre="Ana Rosa";
// Pide una edad
System.out.println("Que edad tiene "+nombre);
edad=Entrada.readInt();
System.out.println(nombre+" tiene "+edad+’.’);
}
}
Obsérvese cómo primeramente se especifican los datos que se van a utilizar, eligiendo
para cada uno de ellos un identificador (nombre y edad) declarando de qué tipo son
(String e int respectivamente). En el caso del identificador nombre se le asigna un dato
concreto, la cadena “Ana Rosa”.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 24
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
El tipo String (frase o cadena de caracteres) tiene la capacidad de concatenarse cuando
se suma con otro dato, por lo que el dato “Que edad tiene “ se une al dato String
almacenado en nombre para mostrarse en pantalla como una única cadena de caracteres.
… … … … …
System.out.println("Que edad tiene " + nombre);
… … … … … … …
Si en lugar de manejar un dato de tipo edad se necesitasen dos, simplemente habría sido
necesario declarar un dato más, asociándole otro nombre:
… … … … …
int edad, otra_edad;
… … … … … … …
Mediante lo que se ha llamado Entrada leemos los datos que se introducirán por la
entrada estándar (generalmente el teclado). Como se espera que el futuro usuario del
programa introduzca un número entero, lo recogemos usando Entrada.readInt() y lo
guardamos en el dato variable de tipo entero edad.
… … … … …
edad = Entrada.readInt();
… … … … … … …
nota.- Entrada esta estrechamente ligado al fichero Entrada.class que se facilita con el curso y que debe
ubicarse en un directorio referenciado por classpath. Su código fuente también se adjunta y se
analizará al final, en el capítulo dedicado a la entrada/salida de datos.
Finalmente el programa genera un mensaje con la cadena resultante de la concatenación
de...
el dato nombre (de tipo String) ...
... el dato “ tiene ” de tipo String ...
... el dato edad de tipo int (entero) ...
... y el dato ‘.’ de tipo char (carácter)
… … … … …
System.out.println( nombre + " tiene " + edad + ’.’ );
… … … … … … …
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 25
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Generalizando... los datos en Java
No son muchos los datos predefinidos y salvo casos excepcionales (como System, o
Entrada) , en un programa para poder manipular un dato será necesario asociarle un
nombre (identificador) declarándolo según la siguiente estructura sintáctica:
<tipo> <nombre_de_objeto> { ,<nombres_de_otros_objetos>} ;
Primero el nombre del tipo de dato, después el identificador con el que se conocerá al
dato y para terminar el símbolo (;) punto y coma.
nota.- Si hay varios datos del mismo tipo, se pueden declarar en la misma línea separados por comas.
Cuando se utiliza un dato concreto puede hacerse referencia a él de tres formas:
1.- Como variable (siempre con un identificador): declarando tal como se ha visto un
identificador para referirse al dato. A estos identificadores se les suele llamar
variables porque su información puede cambiar durante la ejecución del programa.
nota.- Por ejemplo, las anteriores variables edad o nombre.
2.- Como constante implícita (sin identificador): escribiendo literalmente el valor
constante que se desea siguiendo las especificaciones propias del tipo de dato de
que se trate.
nota.- Por ejemplo, el dato int (entero) 5
o el dato double (real) 3.1415 .
o el dato String (cadena)
“HOLA”
3.- Como constante con nombre: declarando un identificador asociado al dato y
precediendo la declaración de la palabra final. En ese caso el dato asociado será
definitivamente el mismo durante toda la ejecución del programa, sólo se le podrá
asignar dato una vez (aunque no tendrá porqué ser siempre en el mismo instante de
la declaración).
nota.- Por ejemplo..
Autor : FernandoToboso Lara
final double PI = 3.141592653589793; // cte. matemática π .
// después, usar PI o usar 3.1415···, es indistinto.
e-mail: [email protected]
Pag. 26
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Declaración de identificadores
Una primera cuestión a determinar es dónde declarar los identificadores de nuestro
programa y la respuesta (por ahora) es, en cualquier parte situada entre la pareja de
llaves de la función main(...), como instrucciones del programa (obviamente antes de
que sean usados desde otras instruciones).
Sin embargo es muy recomendable realizar estas declaraciones al principio del
programa de modo que no se mezclen con el resto de instrucciones, Así resultará más
fácil hacerse una idea de los identificadores que se están utilizando.
Cuando se declara un identificador, se especifica para él un tipo de dato, de modo que
sólo podrá contener datos de dicho tipo.
Existe una diferencia fundamental a tener en cuenta cuando se declara un
identificador, que tiene que ver con el tipo de dato que se le especifica.
Los tipos de datos en java se clasifican en dos categorías, datos de tipo primitivo y de
tipo referenciado.
- Los primitivos los componen una serie
de datos elementales e indivisibles
íntimamente ligados a un único
identificador al que se les asocia y se
tratarán a continuación.
<Identificador>
<Identificador>
<Identificador>
- Los referenciados son datos más
<Identificador>
complejos (suelen estar compuestos de
datos más elementales) y tienen una
DATO
DATO
existencia más independiente, de modo
que el identificador con que se les
Tipos de Datos
Tipos de Datos
nombra, puede no ser el único por el
Primitivos
Referenciados
que se les conozca.
Se les denomina referenciados, porque el identificador no representa el dato sino
solamente la ubicación del mismo.
Un dato de tipo primitivo, existe mientras exista el identificador asociado. La existencia
de un dato referenciado finaliza después de que ningún identificador lo esté
referenciando.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 27
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Nomenclatura de la Programación JAVA
A la hora de elegir los identificadores para los datos (y para otras entidades que estudiaremos) debemos
tener en cuenta una serie de normas:
• Los identificadores estarán compuestos por una serie de caracteres (dígitos, letras y carácter guión
bajo ‘_’) del estándar Unicode, debiendo empezar por letra y de longitud ilimitada.
• Recuerda que Java distingue entre letras mayúsculas y minúsculas, por lo que nombre, Nombre, o
NOMBRE, o NoMbRe, son identificadores distintos.
Además es conveniente respetar una serie de recomendaciones con el fin de obtener un código más fácil
de leer, consiguiendo de esta forma un código auto-documentado.
• Los nombres elegidos para cada dato debe ser descriptivo de lo que el dato representa.
• Los tipos de datos referenciados se escriben con la primera letra mayúscula y las demás minúsculas.
• Para los identificadores constantes se utilizan solamente letras mayúsculas. (ejem.- PI)
Mientras que para identificadores de variables es preferible usar minúsculas. (ejem.- edades)
• En todo caso se les pone una letra mayúscula en medio del identificador cuando se trata de una
palabra compuesta, para así destacar el comienzo de cada palabra. (ejem.- primerDatoRecibido)
Palabras reservadas
Esta es una lista de palabras que por tener un significado especial para el lenguaje, no
pueden ser utilizadas como identificadores.
abstract
char
else
if
long
protected
switch
try
boolean
class
extends
implements
native
public
synchronized
void
break
continue
final
import
new
return
this
volatile
byte
default
finally
instanceof
null
short
throw
while
case
do
float
int
package
static
throws
const
catch
double
for
interface
private
super
transient
goto
nota.- Las dos últimas palabras de la tabla, no se utilizan en lenguaje java, pero se consideran reservadas
por si en posteriores versiones se les diese alguna utilidad especial.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 28
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Tipos Primitivos
Java dispone de los siguientes tipos primitivos:
- Uno para almacenar valores lógicos: cierto (true) y falso (false) (boolean).
- Uno para almacenar caracteres (char).
- Seis para manejar valores numéricos:
- Cuatro tipos para enteros (byte, short, int y long).
- Y dos para valores reales (float y double).
La variedad de tipo numéricos tiene como fin poder optar por aquel que más se ajuste al
rango de valores deseado (o con la precisión decimal más adecuada en el caso de datos
reales) ya que evidentemente no es posible que se representen todos los infinitos
números posibles.
1. El tipo char contiene caracteres en código Unicode y ocupan 16 bits por carácter.
Comprende los caracteres de prácticamente todos los idiomas, incluidos signos de
ortografía algunos otros, como los signos de la escritura musical.
2. Los tipos byte, short, int y long son números enteros que pueden representar valores
positivos o negativos, con distintos rangos.
3. Los tipos float y double son valores de punto flotante (números reales) con
aproximadamente 6 y 15 cifras decimales respectivamente.
Un dato de un tipo concreto, ocupa la misma cantidad de memoria en todas y cada uno
de los posibles sistemas. Por ejemplo, un int ocupa siempre la misma memoria y tiene el
mismo rango de valores, en cualquier tipo de ordenador.
nota.- Un byte (u octeto) es una agrupación de ocho bits. Cada bit es una mínima unidad de información
binaria representada por cero (0) o uno (1).
Tipo
Tamaño de
Memoria
void
Descripción
Uso especial que especifica la ausencia de tipo.
boolean
1 byte.
Valores true y false
char
2 bytes.
Unicode. (Incluye en él al código ASCII)
byte
1 byte.
Valor entero entre -128 y 127
short
2 bytes.
Valor entero entre -32768 y 32767
int
4 bytes.
Valor entero entre -2.147.483.648 y 2.147.483.647
long
8 bytes.
Valor entre
-9.223.372.036.854.775.808 y 9.223.372.036.854.775.807
float
4 bytes
(entre 6 y 7 cifras decimales equivalentes).
De -3.402823E38 a -1.401298E-45 y
de 1.401298E-45 a 3.402823E38
double
8 bytes
(unas 15 cifras decimales equivalentes).
De -1.79769313486232E308 a -4.94065645841247E-324 y
de 4.94065645841247E-324 a 1.79769313486232E308
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 29
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
En el mismo momento de declarar un identificador se puede ya especificar el valor que
se desea que tenga inicialmente, escribiendo después del nombre un signo = y el dato en
cuestión.
… … … … …
int x=12;
… … … … … … …
Esto nos lleva a la cuestión de cómo se especifican los valores de cada uno de los tipos
primitivos anteriormente expuestos, lo que llamamos constantes implícitas o literales,
es decir valores sin identificador asociado, que el compilador java interpreta
adecuadamente.
ejemplos
Tipo carácter (char):
- El carácter va acotado entre comillas simples.
‘a’
- En caso de que se trate de un carácter especial (no disponible en
nuestro teclado), éste se podrá representar también entre comillas pero
dentro escribiríamos el código numérico hexadecimal con 4 dígitos del
carácter (según el estándar Unicode), precedido por la barra invertida y
la letra u.
‘\u004a’
‘\u5c10’
- Hay caracteres con significados especiales (retorno de carro,
tabulador, ...) que tienen un símbolo que los representa si va precedido
de barra invertida..
‘\n’,‘\t’
- Existen algunos caracteres que, teniendo un símbolo que los
representa, podrían ser mal interpretados por tener un significado
especial para java. La manera de especificar el carácter sin significados
adicionales es precederlo igualmente del carácter barra inversa.
‘\’’, ‘\”’,
‘\\’
Tipo lógico (boolean):
- Los únicos posibles valores son cierto y falso.
true, false
Tipos numéricos:
Tipo entero (int):
- Valores dentro del rango válido, en caso de valores negativos se
preceden del signo guión ( - ).
-345
56
Tipo entero largo (long):
- Igual que el anterior pero acabando con la letra L (o l).
9000000L,
En ambos casos es posible escribir el número usando una base
numérica distinta de la decimal.
- Base hexadecimal: precediendo el número de los símbolos
cero (0) y equis (x).
- Base octal: precediéndolo del digito cero.
Autor : FernandoToboso Lara
e-mail: [email protected]
0x3F4A
023
Pag. 30
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Tipo real (float):
234.203F
- Parte entera y parte decimal separadas por un punto y acabando con la
letra F ( o f ).
- Se reservan ciertas combinaciones de bits para representar los valores
especiales infinito positivo, infinito negativo y otro para valores como la
indeterminación matemática 00 , conocido como N.A.N. (Not A
Number).
Tipo real de alta precisión (double):
- Igual que el anterior pero sin letra, o cambiándola por la letra D (o d).
- Infinito positivo y negativo y NAN, como en el tipo float.
9.000000001
12.545545D
nota.- No es lo mismo el carácter ‘2’, que el número int 2.
No es lo mismo el carácter ‘x’, que el identificador x.
No es lo mismo el carácter ‘x’ que la frase de tipo String (con una sola letra) “x”.
(este último tipo de dato aún no se ha analizado porque no es de tipo primitivo)
Operadores
Una vez que conocemos como declarar variables y constantes de tipos primitivos y
como expresar los valores de cada tipo, nos falta algo fundamental para cualquier tipo
de dato: saber de qué operaciones dispone.
A menudo se llama operadores binarios (nada que ver con código binario) a los que
implican a dos operandos (ejemplo 23+4) y operadores unarios si van con uno solo (12). Existe en java además, un caso de operando ternario, con tres operandos, un tanto
peculiar como se verá.
Para que servirían los números si no existieran las operaciones de suma, resta,
multiplicación, etc... pues esto es lo que trataremos a continuación, qué operaciones
existen para cada tipo de datos.
Operadores Aritméticos
Símbolo
Nombre
*
/
Multiplicación
División
%
+
-
Modulo
Suma
Resta
Autor : FernandoToboso Lara
Observaciones
Si los operados son enteros, el resultado será la división entera, sin decimales.
Si son reales obtendremos el cociente con decimales.
Resto de la división entera. Sólo para números enteros.
Con un sólo operando, da el negativo correspondiente (- expresión_numérica)
e-mail: [email protected]
Pag. 31
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Operadores de Asignación
Los operadores de asignación permiten asignar un valor a un objeto variable. La forma
general de las sentencias de asignación con este operador es:
<identificador_variable> = <expresión> ;
Entendiendo por expresión, una combinación de operandos y operadores que dan como
resultado un dato del mismo tipo que la variable que recibirá el resultado.
Este operador, además de dar a la variable el valor resultante de evaluar la expresión, da
como resultado el propio valor que asigna, de manera que éste puede ser nuevamente
asignado u operado.
..........
int x,y,z;
x=y=z=1+3;
.... ...
-
// se asigna cuatro (1+3)
// a las tres variables.
-
primero se evalúa la expresión que se va a
asignar. ( 1+3 )
la variable z recibe el valor resultante de la
expresión... ( z=4 ) dando como resultado lo
asignado (4), valor con el que se sigue
evaluando de derecha a izquierda.
el valor resultante (4) se asigna a la siguiente variable hacia la izquierda, (y=(z=4)).... (y=4) lo que
nuevamente da como resultado lo asignado (4), para continuar evaluando.
el valor resultante (4) se asigna nuevamente a la siguiente variable de la izquierda (x=(y=(z=4)))....
(x=4), que aunque también da resultado cuatro, es despreciado (alguna vez había que terminar ¿no?).
Java dispone de otros operadores de asignación, que son versiones abreviadas de
diversas combinaciones entre asignación y operación, que realizan operaciones
acumulativas sobre una variable.
La siguiente tabla muestra estos operadores y su equivalencia con el uso de la
asignación.
Símbolo Utilización Equivalencia
*=
op1 *= op2 op1 = op1 * op2
/=
op1 /= op2 op1 = op1 / op2
%=
op1 %= op2 op1 = op1 % op2
+=
op1 += op2 op1 = op1 + op2
-=
op1 -= op2 op1 = op1 - op2
En general:
Operador=
op1 Operador= op2
op1 = op1 Operador op2
...donde op1 debe ser un identificador variable y op2 puede ser cualquier expresión que
una vez evaluada dé el resultado con el que se va a operar op1.
A efectos de encadenar varias de estas operaciones (aunque no es muy común) la norma
es similar a la aplicada en la asignación simple... la operación da como resultado op2.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 32
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Operadores incrementales
Disponemos de un operador incremento (++) y otro de decremento (--). El primero (++)
incrementa en una unidad la variable a la que se aplica, mientras que el otro (--) la
disminuye en una unidad.
Cuando el operador se aplica de manera aislada a una variable, no tiene mayor
dificultad, pero si se utiliza sobre una variable que forma parte de una expresión mayor
su funcionamiento tiene matices que merece la pena comentar.
Estos operadores se pueden utilizar de dos formas:
1.
Precediendo a la variable (por ejemplo: ++i). En este caso, primero se
incrementa el dato que la variable contiene y luego se utiliza (ya incrementado)
en la expresión en la que aparezca.
2.
Siguiendo a la variable (por ejemplo: i++). En este caso también se incrementa
la variable, pero el valor que se utiliza en la expresión es el correspondiente al
dato antes de ser incrementado.
Supongamos el siguiente ejemplo:
… … … … …
int a=5,b=5, x;
x=(++a) + (b++) + 3;
… … … … … … …
Tanto a como b incrementan su valor en una unidad, pero en el cálculo de la expresión:
++a se sustituiría por 6 (el valor ya incrementado) mientras que b++ se sustituye por
5 (el valor de antes de incrementarse) resultando finalmente... (6) + (5) + 3
No es conveniente elaborar expresiones enredosas y complicadas, por lo que en muchas
ocasiones estos operadores se usan formando solos una instrucción independiente.
… … … … …
++a; // o bien... a++;
… … … … … … …
En este caso ambos usos del operador son equivalente.
nota.- No confundir la exprexión ++a (o a++) con la exprexión a+1. Esta última, no modifica el
contenido de la variable a.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 33
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Operadores relacionales
Los operadores relacionales sirven para realizar comparaciones de igualdad,
desigualdad y relación de orden. El resultado de estos operadores es siempre
de tipo boolean, o bien true o bien false según se cumpla o no la relación considerada.
Estos operadores se utilizan con mucha frecuencia pera expresar las condiciones que
determinan lo que un programa decide hacer en cada instante de su ejecución.
Operador Utilización El resultado es true...
>
op1 > op2 si op1 es mayor que op2
>=
op1 >= op2 si op1 es mayor o igual que op2
<
op1 < op2 si op1 es menor que op2
<=
op1 <= op2 si op1 es menor o igual que op2
==
op1 == op2 si op1 y op2 son iguales
!=
op1 != op2 si op1 y op2 son diferentes
nota.- No confundir la comparación de igualdad (==) con la operación de asignación (=).
Operadores lógicos
Los operadores lógicos se utilizan para componer expresiones lógicas, a partir de datos
lógicos (boolean) o con otras expresiones lógicas.
op1
op2
op1 Y op2
op1 O op2
op1 XOR op2
NO op1
true
true
false
false
true
false
true
false
true
false
false
false
true
true
true
false
false
true
true
false
false
“
true
“
Estos nos permitirán expresar condiciones complejas con la combinación de
condiciones más elementales.
… … … … …
.....(x<y && x==23)
… … … … … … …
Los signos de estos operadores en java son los de esta tabla:
Operador Nombre
!
&&
&
||
|
^
NO
Y
Y
O
O
XOR
Autor : FernandoToboso Lara
Utilización
! op
op1 && op2
op1 & op2
op1 || op2
op1 | op2
op1 ^ op2
El resultado es true
si op es false
si op1 Y op2 son true. Si op1 es false ya no se evalúa op2
si op1 Y op2 son true. Siempre se evalúa op2
si op1 O op2 son true. Si op1 es true ya no se evalúa op2
si op1 O op2 son true. Siempre se evalúa op2
cierto si sólo uno de los operandos es cierto
e-mail: [email protected]
Pag. 34
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
A los operadores && y || se les denomina de corte porque con ellos el segundo
operando no se evalúa cuando no es necesario.
Si ambos tienen que ser true y el primero es false, ya se sabe que la condición de que
ambos sean true no se va a cumplir y por tanto no se continúa evaluando el resto de la
expresión.
Esto podría traer resultados no deseados... (aunque no ocurre con frecuencia)
… … … … …
....(x<y || (++x)==23)
… … …
… … … … … … …
// como la 1ª cond. es suficiente para determinar
// que la condición es cierta, la 2ª no se evalúa y
// por lo tanto x no se incrementa.
Para casos como éste, disponemos también de los operadores & y | que garantizan
que los dos operandos se evalúan siempre (aunque ello no varíe el resultado lógico de la
expresión).
… … … … …
....(x<y | (++x)==23)
… … … … … … …
// ahora si que se tiene certeza de que
// se ejecutará el incremento ++x
Operador ternario condicional .... ? .... : ....
Su forma general es:
expression_lógica ? resutado1 : resultado2
Tras evaluarse expression_lógica, el resultado será:
- resultado1 si la expression_lógica es true,
-
resultado2 si la expression_lógica es false.
Es el único operador ternario (tres argumentos) de java. Como todo operador que
devuelve un valor puede ser utilizado dentro de una expresión. Por ejemplo:
… …
int
z =
… …
… … …
x=5,y=8,z;
(x<y)?x+2:y-8;
… … … … …
Autor : FernandoToboso Lara
Se asigna a z el resultado de x+2, ya que como puede
comprobarse el dato de x es menor que el de y. Pero si la
condición x<y hubiera sido falsa se le hubiera asignado y-8.
e-mail: [email protected]
Pag. 35
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Operadores que actúan a nivel de bits
Java dispone también de un conjunto de operadores que actúan a nivel de bits.
Son operaciones que se realizan directamente sobre los bits que componen los datos, tal
como se almacenan en la memoria del ordenador, en binario.
Operador
>>
<<
>>>
&
|
^
Utilización
op1 >> op2
op1 << op2
op1 >>> op2
op1 & op2
op1 | op2
op1 ^ op2
~
~op2
Resultado
Desplaza los bits de op1, op2 posiciones a la derecha
Desplaza los bits de op1, op2 posiciones a la izquierda
Desplaza los bits de op1 (sin signo) a la derecha una distancia op2
Operador Y a nivel de bits
Operador O a nivel de bits
Operador O Exclusiva a nivel de bits
(cierto si sólo uno de los operandos es cierto)
Operador complemento (invierte el valor de cada bit)
nota.- Cuando los operadores >> y << desplazan los bits de un dato respetan el bit más a alto (que
representa el signo positivo o negativo). El operador >>> al contrario, desplaza todos los bits que
forman parte del dato en la memoria.
La tabla de verdad de los operandos es similar a la de los operadores booleanos, si
consideramos que cero (0) representa falso y uno (1) representa cierto.
bit1
bit2
bit 1 Y bit 2
bit 1 O bit 2
bit 1 XOR bit 2
~ bit1
0
0
1
1
0
1
0
1
0
0
0
1
0
1
1
1
0
1
1
0
1
“
0
“
Se utilizan entre datos de alguno de los tipos numéricos enteros (sin decimales), lo que
permite distinguirlos de los operadores lógicos & , | y ^ que operan entre datos
boolean.
Ejemplo práctico
Las operaciones de bits son, entre otras cosas, útiles para manejar bits que dentro de
datos byte, short, int o long tienen algún significado particular.
Supongamos que de alguna manera se ha asignado a la variable estadoDispositivo de
tipo byte un dato cuyos bits informan del estado de algún dispositivo según el siguiente
esquema:
peso de cada bit : 128
estadoDispositivo
0
64
32
16
8
4
2
1
1
1
1
0
1
0
0 .....
116
( 1 si el dispositivo está activo y 0 si no lo está )
El número es el 116 (o en hexadecimal 0x74), pero la información que nos interesa se
encuentra en un sólo bit. Pero cómo hacer para saber (independientemente del resto de
bits) si el dispositivo está activo, o no lo está.
La respuesta se obtiene usando con habilidad los operadores de bits.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 36
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
... ... ... ...
byte señal, estadoDispositivo;
// Suponemos que se asigna a la variable estadoDispositivo,
// el estado actual del supuesto dispositivo.
... . .. ..
señal = estadoDispositivo & 64;
// Ahora la variable señal vale:
//
0 ...si el bit indica inactivo (0),
//
(distinto de cero) 64 ...si el bit indicaba activo (1).
........ ...
Como el operador & opera sobre cada bit,
cualquier bit ( Xi ) operado con un bit cero
(0) dará como resultado el bit cero (0), pero
al operar con el bit uno (1) da como resultado
el bit que él mismo sea ( Xi ) .
X7 X6 X5 X4 X3 X2 X1 X0 estado
&
0
1
0
0
0
0
0
0
(64)
0
X6
0
0
0
0
0
0
señal
Precedencia de operadores
nota.- Además de los operadores estudiados, existen algunos más en los que por
ahora no merece la pena detenerse; Ya se verán más adelante. No obstante,
todos han sido incluidos en la tabla que a continuación se expone.
El orden en que se realizan las operaciones es fundamental para determinar el resultado
de una expresión. Por ejemplo, el resultado de x/y*z dependerá de cuál sea la operación
(división o producto) que se realice primero.
Operadores
ordenados de
mayor a
menor
precedencia
Esta lista
muestra el
orden en que
se ejecutan los
distintos
operadores
dentro de la
expresión en
la que
aparecen.
operadores postfijos
operadores unarios
creación y casting
multiplicativos
aditivos
desplazamiento
relacionales
igualdad
y (de bits)
o exclusivo (de bits)
o (de bits)
y (lógico)
o exclusivo (lógico)
o (lógico)
condicional
[]
.
(params)
expr++
expr-++expr --expr +expr -expr
~
!
new
(type)expr
*
/
%
+
<<
>>
>>>
< > <=
>=
instanceof
==
!=
&
^
|
&&
&
^
||
|
?:
asignaciónes = += -= *= /= %= &= ^= |= <<= >>= >>>=
En java, como en el resto de lenguajes, los operadores que forman las expresiones, se
evalúan de izquierda a derecha, a excepción de los operadores de asignación que se
evalúan de derecha a izquierda.
Conversión de Tipos
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 37
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
En ocasiones debemos plantearnos que relación existe entre datos de diferentes tipos.
Si a un identificador variable de tipo float se le asigna un dato de tipo int, se produce
una transformación automática obvia:
… … … … …
float x;
x = 3;
// a x se le asigna 3.0
… … … … … … …
Pero otras veces, no resulta tan evidente:
… … … … …
int x;
x = 3 * 1.6;
//
//
//
//
//
ERROR
...de 3 * 1.6 resulta el número 4.8
pero al asignarse a la variable entera, como
no puede asegurar que no se pierda precisión
en los datos, el compilador señala un error.
… … … … … … …
En esto casos es necesario que sea explicita la intención de que el dato cambie de tipo.
A esta operación se le llama casting y se realiza anteponiendo a la parte que se desea
transformar, el tipo deseado puesto entre paréntesis.
… … … … …
int x;
x = (int)(3 * 1.6);
//
//
//
//
CORRECTO
de 3 * 1.6 resulta el número 4.8
convertido a tipo (int), resulta 4
que es lo que se asigna a la variable x
… … … … … … …
Hay que advertir que no todos los tipos de datos son convertibles entre sí. Algunos
casos como los datos char se transforman al tipo int correctamente (aunque pueda
parecer inadecuado) transformándose en el código numérico que le corresponde en el
estándar Unicode con el que se representan.
Se pueden encontrar algunos lenguajes (como por ejemplo C) muy permisivos a la hora
de mezclar datos de diferentes tipos.
Esto tiene ventajas e inconvenientes. Por una parte si el programador sabe exactamente
lo que hace, tiene vía libre para combinar cualquier dato. Pero, ese exceso de
flexibilidad exige del programador un gran pericia para no equivocarse y además
propicia que se escriban programas complicados de entender.
Los lenguajes, como java, fuertemente tipados imponen unas limitaciones muy estrictas
que a menudo simplifican la detección de errores, evitando que se cometan por abusar
de esa flexibilidad.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 38
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Tipos Referenciados
Ya en ejemplos anteriores hemos trabajado con un dato de tipo referenciado, la variable
nombre de tipo String.
Por ahora no será necesario conocerlos muy a fondo, pero sí es conveniente que se tenga
una noción de cómo se usan y en qué se diferencian básicamente de los datos de tipos
primitivos.
Veamos el siguiente ejemplo contenido en un fichero de texto llamado Objeto.java ...
// Fichero: Objeto.java
public class Objeto {
public static void main(String[] args) {
CromoMovil cromo;
cromo = new CromoMovil();
System.out.println();
System.out.println();
System.out.println(" ** Con esta ventana activa (no la ventana movil) ... **");
System.out.println(" ** ... introduce dos números enteros positivos *******");
System.out.println(" ** para dar una nueva coordenada al cromo movil *******");
System.out.println();
System.out.print("coordenada X: ");
cromo.x=Entrada.readInt();
System.out.print("coordenada Y: ");
cromo.y=Entrada.readInt();
cromo.ActualizaPosicion();
cromo.CargaCromo("imagenfinal.gif");
}
}
En él se utiliza un tipo de dato llamado CromoMovil de tipo referenciado.
Este es el aspecto que toma inicialmente el dato
CromoMovil, desplazándose por la pantalla:
Este dato tiene la capacidad de mostrar en el
entorno gráfico del sistema una imagen que se
desplaza hacia donde se le indica pulsando las
teclas de movimiento r o p q hasta que detecta
la pulsación de la tecla de escape }.
nota.- Por ahora, no vamos a tratar sobre cómo funciona internamente este dato, que nos sirve para mostar
cómo se usa un dato referenciado. Si se desea ejecutar el ejemplo, será necesario disponer de un
fichero llamado CromoMovil.class en el cual se encuentra el código compilado de este tipo de dato.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 39
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
En lo relativo a los datos referenciados hay que tener en cuenta lo siguiente:
Cuando se declara un identificador de tipo referenciado sólo creamos la
referencia al dato, pero no el dato propiamente dicho. Por eso después de la
declaración, asignamos al identificador la referencia del dato que creamos con la
instrucción new seguido del nombre del tipo con una pareja de paréntesis.
cromo
… … … … …
CromoMovil cromo;
cromo = new CromoMovil();
… … … … … … …
Dato Referenciado
de tipo CromoMovil
Como puede verse, al margen de que la declaración del identificador, el nuevo
dato se debe crear usado la estructura genérica ...
new <tipo_de_dato> (<posibles_datos_de_inicialización>)
... cuya referencia suele ser asignada a algún identificador de dicho tipo.
Cuando se asigna algo a un identificador de una variable de tipo referenciado,
éste pasa a referenciar al dato que se le asigna dejando de referenciar al que
apuntara anteriormente. Un dato puede ser referenciado desde varios
identificadores simultáneamente (compartiéndolo).
var1
Dato Referenciado
… … … … …
var1 = new Dato();
var2 = var1;
… … … … … … …
var2
Un dato, que ya no es referenciado desde ningún identificador, no puede ser
utilizado, pero no deja de existir inmediatamente. Existe una tarea que se ejecuta
cada cierto tiempo de manera asíncrona (a su ritmo) cuya labor consiste en
localizar datos que no estén referenciados desde ningún identificador y
eliminarlos. A esta tarea se le suele llamar “recolector de basura”.
El dato especial null, representa la referencia nula. Si deseamos que un
identificador no referencie a ningún dato, podemos asignarle null.
nota.- Si el dato al que referenciaba, ya no es referenciado desde ningún otro identificador,
será eliminado.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 40
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Las operaciones de comparación de tipo primitivos no suelen ser útiles aquí.
La expresión var1 == var2 (con identificadores de tipo referenciado) sólo será
true si ambos identificadores referencian al mismo dato (o si ambas valen null) y
será false si referencian datos distintos, incluso aunque dichos datos sean
idénticos.
nota.- Más adelante veremos cómo compararlos correctamente.
Los datos referenciados están compuestos por:
-
Las instrucciones que le son propias y que permiten manipularlos, a los
que se llama métodos.
… … … … …
cromo.CargaCromo("imagenfinal.gif");
… … … … … … …
-
Y por otros datos más elementales (primitivos, o a su vez también
referenciados) que definen el estado (o valor) actual del dato, por lo que
se les denomina propiedades).
… … … … …
cromo.x=Entrada.readInt();
… … … … … … …
nota.- En el ejemplo, x (que forma parte del dato cromo)
es un dato del tipo primitivo int, que se ocupa de
almacenar la posición horizontal de la imagen.
Dichos métodos y propiedades tienen su propio identificador, por el que se les
nombra cuando es preciso, pero como forman parte de un dato con su propio
identificador se accede a ellos usando la notación...
<nombre de dato>.<nombre de método o propiedad>
... quedando de este modo claro cuál es la parte (método o atributo) que se
pretende utilizar y de qué dato referenciado.
Es fácil distinguir cuándo se nombra a un método, porque detrás siempre se
colocan un par de paréntesis, que a su vez pueden encerrar datos necesarios para
el método solicitado.
Simplemente nombrando un método, se solicita que se realice una vez la
operación que ésta realiza, como si se tratase de una instrucción más.
.........
cromo.ActualizaPosicion();
....... ... .
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 41
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Cadenas de Caracteres
Estudiemos con un poco más detalle el tipo String ya que además de ser un buen
ejemplo de dato referenciado, resulta básico en cualquier programa por ser el
responsable del manejar las cadenas de caracteres (textos).
Representan una secuencia de caracteres de contenido invariable, por los que los datos
de este tipo disponen de métodos para analizar, comparar, etc... los texto que la
aplicación precise.
Estos datos son utilizados con mucha frecuencia, por lo que existe un modo
simplificado de crearlos (recuerda que los datos referenciados se deben crear
explícitamente).
En lugar de usar la instrucción new, podemos crearlos directamente escribiendo su
secuencia de caracteres acotada por comillas dobles: “ abcde... ”
“cadena de caracteres”
············
new String(“cadena de caracteres”)
... son equivalentes ...
“”
············
new String()
De este modo la referencia del nuevo dato puede ser directamente asignado a un
identificador variable de tipo String, o utilizada allí donde sea necesario.
.. .. . ..... ...
String nombre = "Hola Juanola";
System.out.println(“esta frase se va a mostrar directamente”);
.... . ... .. ...
Conviene recordar que si a continuación se ejecuta...
.. .. . ... .. ...
String otroNombre = nombre;
.... . ... .. ...
// dos identificadores para un sólo dato
... NO se dispone de un nuevo dato String,
ya que tratándose de un tipo referenciado
el identificador otro_nombre, recibe la
referencia al dato ya existente
nombre
“Hola Juanola”
otroNombre
“Hola Juanola”
Si se necesita un nuevo dato String
idéntico al dato referenciado por nombre,
pero independiente de éste, escribiríamos...
.... .. .. .. .
String otroNombre = new String(nombre);
... ... .... ...
nombre
“Hola Juanola”
otroNombre
“Hola Juanola”
nota.- Como se puede observar, cuando se crea un nuevo dato String, se puede colocar entre los paréntesis
un String ya existente, para que el nuevo dato sea igual que el facilitado.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 42
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Como cualquier otro dato de tipo referenciado, éste pone a disposición del programador
una serie de métodos con los que acceder a su contenido.
Aunque ninguno de ellos permite modificar el dato, algunos nos pueden permitir crear
nuevos datos String por extracción de partes del String original, o como resultado
adaptado del mismo, o por combinación de varios datos String.
“Ana Rosa”.substring(0,3)
... retorna un nuevo dato String “Ana”
String frase=“Cupo, Luko”;
frase.Substring(4,10)
... retorna un nuevo dato String “, Luko”
“Hola ”.concat(“Manola”)
... retorna un nuevo dato String “Hola Manola”
“Hola”.toUpperCase()
... retorna un nuevo dato String “HOLA”
Otros permiten chequear el contenido, o realizar comparaciones con otros datos.
int pos=0;
frase.charAt(pos)
... retorna el caracter ‘C’ (en la primera posición)
(comenzando desde la posición cero)
frase.charAt(2)
... retorna el caracter ‘p’ (en la posición tercera)
“10 más menos”.indexOf(‘s’)
... retorna 5, la posición donde aparece el caracter ‘s’
por primera vez.
frase.length()
... retorna 10, el número de caracteres de frase.
(incluidos espacios, signos, letras, etc..)
frase.equalsIgnoreCase(“Otro texto”) ... comprueba si el String facilitado es igual
al que el identificador frase referencia
(sin distinguir entre mayúsculas y minúsculas)
Observe que ninguno de los métodos permite la modificación del dato, a no ser
mediante la creación de otro nuevo. Pero sí que podemos hacer que el nuevo pase a ser
referenciado por el identificador que referenciaba el original...
.... ... . .... ... ....
String frase=“Cupo, Luko”;
frase = frase.concat(” y YO”);
.... ... . ..
nota.- Recuérdese que el String original, desaparece automáticamente
cuando ya no es referenciado por ningún identificador.
Sin embargo, esto hace que el programa no use de manera muy eficaz estos dato
pensados para ser fijos. Si pretendemos modificar con frecuencia el contenido de un
texto, existe un tipo de dato más adecuado llamado StringBuffer, que es similar al tipo
String, pero modificable.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 43
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Para crear un nuevo dato de este tipo se usa como siempre new seguido del nombre del
tipo. Entre los paréntesis se puede (opcionalmente) colocar un dato String para que sea
el contenido inicial del dato.
nota.- Si no se pone ningún String como contenido inicial, se supone inicialmente vacío, sin caracteres.
new StringBuffer(<cadena_de_caracteres_inicial>)
Este tipo dispone de métodos que facilitan la modificación del propio dato, sin
necesidad de crear otro nuevo.
StringBuffer texto= new StringBuffer(frase);
texto.append(“ y YO”)
... modifica el dato referenciado por texto
añadiendole al final los caracteres del
String “ y YO”.
y retorna una referencia al propio dato
StringBuffer modificado ya existente.
texto.toString();
... retorna un nuevo dato String con los caracteres
del StringBuffer texto, “Cupo, Luko y YO”.
texto.insert(10, ‘-’);
... modifica el dato referenciado por texto
insertando un caracter guión ‘-’ en la
posición 10. (comenzando desde la posición cero)
El dato texto queda como “Cupo, Luko- y YO”.
texto.setCharAt(10, ‘$’)
... modifica el dato referenciado por texto
sustituyendo el caracter ‘-’ de la posición 10,
y poniendo en su lugar un caracter coma ‘$’.
El dato texto queda como “Cupo, Luko$ y YO”.
Ya hemos podido ver en ejemplos anteriores como el dato out (que forma parte del dato
System) dispone del método println() entre cuyos paréntesis colocamos el String que
deseamos mostrar por la salida estándar (la pantalla normalmente).
.. .... .. ..... .
System.out.println("Que edad tiene " + nombre);
.... .. ......
Pues bien, cuando el operador suma (+) se evalúa con al menos un operando String, da
como resultado un nuevo dato String, resultado de la concatenación los datos operados.
... . .. ... ..
“Tengo “ + 2
... obtenemos un nuevo dato de tipo String y es equivalente a escribir...
... new StringBuffer().append(“Tengo “).append(2).toString()
.. . ..... .. ... .
. ..... .. ... .
1 + “. Respuesta: “ + true
... obtenemos un nuevo dato de tipo String y es equivalente a escribir...
...new StringBuffer().append(1).append(“. Respuesta: “).append(true).toString()
... .. ... .. ... .
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 44
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
¡¡¡ Atención con lo siguiente... !!!
.. ... .. ... .
“euros “+1+2
.. . .
... se obtiene “euros 12” ...
new StringBuffer().append(“euros “).append(1).append(2).toString()
.... .. ... .
.. . ... .. .....
1+2+” euros.”
... se obtiene “3 euros.” ...
new StringBuffer().append(1+2).append(“ euros.”).toString()
o sea...
new StringBuffer().append(3).append(“ euros.”).toString()
.. . ........ ...
nota.- La expresión se evalúa de izquierda a derecha, por lo que en el último caso se opera primero
1+2 (suma de datos int) obteniéndose 3 y finalmente la concatenación 3+” euros.”
Uso directo de Tipos Referenciados
Como se habrá venido observado, en muchas ocasiones un tipo de dato referenciado
permite el acceso a métodos y atributos propios, haciendo uso del nombre del propio
tipo, el lugar hacer a través de un dato de dicho tipo.
- Por ejemplo el atributo out (de un tipo aún no estudiado) se utiliza directamente mediante el
tipo System y representa la salida estándar.
....... ... ..
System.out.println();
... ...... ..
- O por ejemplo el método gc() que igualmente se utiliza mediante el tipo System y que obliga
a que se ejecute el recolector de basura, lo antes posible.
nota.- Recuérdese que el recolector de basura es un proceso que se ocupa de eliminar de la memoria
todos los datos a los que ya no referencia ningún identificador.
....... ... ..
System.gc();
... ...... ..
* Los atributos que se acceden directamente a través del nombre de un tipo de dato
suelen representar datos de interés general y/o con un significado común para todos
los datos (en especial entre los datos de su propio tipo).
Integer.MAX_VALUE
Integer.MIN_VALUE
Autor : FernandoToboso Lara
... valor máximo que un dato del tipo primitivo int es
capaz de representar
... valor mínimo que un dato del tipo primitivo int es
capaz de representar
e-mail: [email protected]
Pag. 45
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
* Los métodos que se acceden directamente a través del nombre del un tipo de dato
suelen realizar acciones de interés general, relacionadas con el contexto del tipo desde
el que se utiliza.
System.currentTimeMillis ()
Math.sqrt(25.0)
double.
Math.random()
...
...
retorna
retorna un dato de tipo long con el tiempo en
milisegundos del sistema en el standard UTC.
la
raiz
cuadrada
(squar
root)
de
un
número
... retorna un número double al azar entre 0.0 y 1.0
Integer.toBinaryString(12)
... retorna el String con la representación en
binario del número int que se le da. (“1100”)
Integer.toHexString(12)
... retorna el String con la representación en
hexadecimal del número int que se le da. (“C”)
Integer.toOctalString(12)
... retorna el String con la representación en
octal del número int que se le da. (“14”)
Integer.parseInt(“-123”)
... retorna el dato int que represente el String
que se da. (-123)
El tipo String también dispone de un método de uso directo particularmente interesante
para facilitar la transformación de datos de tipos primitivos en su representación escrita.
Se trata del método valueOf() que da como resultado un nuevo dato String con la
representación con la que se escribe el dato que se le pone entre los paréntesis...
String.valueOf(34)
String.valueOf(3.04F)
String.valueOf(‘H’)
String.valueOf(true)
...
...
...
...
retorna
retorna
retorna
retorna
un
un
un
un
nuevo
nuevo
nuevo
nuevo
dato
dato
dato
dato
String
String
String
String
“34”
“3.04”
“H”
“true”
Si se trata de algún dato de tipo referenciado...
String.valueOf(<dato_ref>)
... retorna el nuevo dato String que retorne
<dato_ref>.toString() que es un método que ofrecen
todos los datos referenciados.
nota.- No confundir los datos de distinto tipo “34” y 34 , “true” y true , “H” y ‘H’ etc..., téngase en
cuenta que incluso cuando un dato se utiliza como constante sin identificador, tiene un tipo
específico.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 46
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Capitulo 5
Sintaxis
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 47
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Utilización de Comentarios
Los comentarios son textos descriptivos que se colocan entre el código de un programa
y que ayudan a comprender el funcionamiento de éste.
Son tremendamente útiles ya que permiten hacer anotaciones en los fragmentos que
merecen una especial atención.
En java, existen dos formas de escribir comentarios:
- Para cuando el comentario no excede de una línea, lo más práctico es precederla
de dos barras inclinadas seguidas ( // ).
Todo el texto que haya en dicha línea después de los dos barras, no será leído por
la máquina que ejecute el programa (cuando se compila, estas partes se omiten).
// esta parte es un comentario...
int i;
// ... y éste también, pero sólo a partir de las barras
- Si se trata de un comentario de varios renglones (sin instrucciones entremedias
para ejecutar) es más cómodo acotarlo entre los siguientes símbolos: al comienzo
(/*) y al final (*/).
/* esta parte es un comentario...
más adecuado para cuando existe más de una línea y ...
int i; ... en este caso la instrucción no es ejecutada por
quedar dentro del comentario */
Hacen más manejable la escritura de los programas, especialmente considerando que lo
que una persona escribe, lo continuarán, revisarán y corregirán probablemente otras
personas.
Es muy recomendable acostumbrarse a comentar el código desarrollado. El tiempo que
le dediques lo recuperarás a la larga sobradamente.
De todos modos, generalmente esto no basta para documentar el modo en que está
hecho un programa. La parte mas detallada y exhaustiva que explica cómo funciona un
programa se realiza aparte, pero estos comentarios in situ tienen el don de la inmediatez
al estar junto al código que explican.
nota.- Los comentarios que comienzan con dos asteriscos ( /** ) tienen una finalidad especial relacionada
con una utilidad llamada javadoc que genera automáticamente documentación en formato HTML
(web).
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 48
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Sentencias
En programación se denomina instrucción o sentencia, a una unidad de ejecución
completa, que describe algo con un sentido específico para la máquina que lo interpreta.
En java, cada sentencia se identifica por terminar con un signo punto y coma (;).
Podemos clasificarlas orientativamente, en tres grandes grupos (aunque a menudo se
escriban entremezcladas formando combinaciones de categorías mixtas):
-
Sentencias declarativas.
Acciones.
Independientes.
Expresiones.
frecuentemente se clasifican también como...
operación de entrada/salida (lectura/escritura).
operación de asignación.
otras operaciones.
-
Sentencias de control.
Sentencias declarativas.
Con éstas se especifican nuevos identificadores de datos y hemos tenido sobrados
ejemplos de ellas anteriormente.
. .. ..... ... ...
int edad;
//declaracion del identificador edad
String nombre;
//declaración del identificador nombre
nombre="Ana Rosa";
.. ... ..... ...
Las declaraciones puede escribirse en cualquier posición entre las llaves más externas
del programa (es decir las correspondientes a la palabra class, no sólo entre las llaves
del método main(...) ), sin embargo por ahora seguiremos utilizándolas de esta forma,
ya que esa otra clase de declaración se utilizará más adelante.
Tal como hemos visto, para los identificadores de tipos primitivos esto bastará para
poder manejar el dato, pero si se trata de un tipo referenciado, esto sólo creará la
referencia, sirviendo únicamente para identificar el dato que habrá de ser creado
separadamente (usando la palabra new).
Cuando se declara un identificador (primitivo o referenciado) en nuestro programa
main(...), éste queda inicialmente vacío (sin contenido) de manera que si se intenta
consultar sin antes haberle asignado explícitamente algún dato se producirá un error de
compilación al intentar traducir el código.
.... ..... . .. ...
int a,b;
String unNombre,otroNombre;
// ERROR los identificador otroNombre y b no pueden ser consultados
// puesto que no se la ha asignado aún ningún dato.
unNombre = otroNombre;
a = b;
... . .. ... ... .
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 49
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
A veces una declaración viene precedida por un modificador que matiza el modo en que
se va a comportar lo que declaramos. Por ahora sólo hemos utilizado el modificador
final, pero existen algunos más.
final
Recuerda que un identificador declarado con este modificador sólo permitirá que se le
asigne una vez el valor que se le quiere dar aunque esto no tiene por qué ser en el
mismo momento de la declaración.
Su utilidad evidentemente es la de declarar constantes con nombre (como PI por
ejemplo) lo que posibilita que el código haga referencia a un nombre en lugar de usar la
constante sin nombre en cada ocasión. Con lo que se obtienen dos ventajas:
- Se entiende mejor el código (siempre que se dé a la constante un nombre que
tenga que ver con lo que representa).
Resulta más clara la expresión:
2 * Math.PI * r
(perímetro de la
circunferencia) que si se usara directamente el valor 3.14···
nota.- PI es un atributo constante de tipo double (real con precisión) dentro del tipo Math.
- Facilita que el dato asignado al identificador constante pueda ser
cómodamente corregido por el programador (en la única asignación que
tendrá) siendo inmediatamente efectivo el nuevo valor para todos los sitios en
los que aparezca.
A veces no resultan tan ‘constantes’ como parecen. Los identificadores de tipo
referenciado, una vez que se les asigna dato no podrán ya dejar de referenciarlo, sin
embargo el dato al que referencian, sí puede cambiar.
final StringBuffer ciudad = new StringBuffer(“Albacete”);
....... ... ..... ... ......
Correcto
Incorrecto
ciudad.replace(0,8,”Las Palmas”);
/* Aunque el identificador es constante
el dato al que referencia sí cambia
*/
Autor : FernandoToboso Lara
ciudad = new StringBufer(“Las Palmas”);
/* El identificador no puede modificar
la referencia que contiene, al haber
sido declarado constante */
e-mail: [email protected]
Pag. 50
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Acciones
De todas las sentencias que hasta el momento se han utilizado en este libro, las que no
son declarativas, son acciones (hasta el momento no se ha utilizado ni una sola
sentencia de control).
Existen una gran variedad de ellas, pero podríamos decir genéricamente que siempre
realizan algún tipo de acción o cálculo.
Las subclasificaremos en dos tipos, sin resultado (o independientes) y
con resultado (o expresiones):
- Independientes: Siempre aparecen formando una sentencia completa. Se
caracterizan porque su tipo es void. Dicho de otra manera, no retornan ningún dato
como resultado.
Su ejecución realiza alguna acción, pero no se pueden combinar para construir
sentencias más complejas. Algunos ejemplos de este tipo serían:
. . .... ... ...
System.out.println(“Hola”);
// muestra - Hola - por la salida estándar
.... .... ..
StringBuffer texto = new StringBuffer(“Tanto”);
texto.setCharAt(1,’i’); //cambia la letra de la posición 1 del dato referenciado
//por texto, convirtiendolo en el StrinBuffer -Tinto...... .......
System.exit(13);
// termina la ejección del programa de manera repentina.
......... . ....
- Expresiones: Al ser ejecutadas dan como resultado algún dato.
Se dice que el tipo de una expresión es el tipo del dato que resulta cuando se
evalúan (se ejecutan).
Algunas de ellas también realizan al ejecutarse una acción (por lo que también
pueden constituir en solitario una sentencia completa), pero a diferencia de las
anteriores, pueden formar parte de otras sentencias.
Una expresión se define como uno o más datos combinados entre sí con
operadores. Dichos datos pueden ser:
- Constantes o variables.
- Con o sin identificador.
- O incluso ser resultado inmediato de la ejecución de un método.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 51
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Estas son algunas expresiones válidas, junto con el dato resultante y su tipo.
Presupondremos los siguientes identificadores:
........ ..
int x;
String frase=”Valor=”;
boolean esPoco;
... .... .....
anotaciónes
Expresión
Evaluación de expresión y resultado
suponemos que el
usuario introduce x =Entrada.readInt()+1
el número 5
Tipo
x=5+1 ¯ x=6 ¯ 6
Acción
int
asigna el 6 al
identificador x
int
nada
int
nada
Ojo, ahora x vale 6.
... y atención a la precedencia de operadores (de izquierda a derecha)
4+
x ⋅3
⋅5
2
(4 + x ) ⋅
4+x*3/2*5
3
2⋅5
(4+x)*3/(2*5)
4+18/2*5 ¯ 4+9*5 ¯
4+45 ¯ 50
10*3/(2*5) ¯ 30/(2*5) ¯
30/10 ¯ 3
esPoco = ++x > 6
esPoco = 7 > 6 ¯ esPoco = true
¯true
boolean
esPoco = x++ > 6
esPoco = 6 > 6 ¯ esPoco = false
¯false
boolean
frase=frase.concat(String.valueOf( frase.length() ))
frase=frase.concat( String.valueOf(6) ) ¯
frase=frase.concat( “6” ) ¯
frase= “Valor=6” ¯
String
la referencia al dato
“Valor=6” asignado
incrementa x en
uno y asigna true
a esPoco
incremente x en
uno y asigna
false a esPoco
asigna el nuevo
String al identif.
frase
Las expresiones tienen dos facetas, por una parte ejecutan los cálculos que se le piden
(lo que a veces produce acciones más allá de la propia evaluación de la expresión) y por
otra retorna un objeto con el valor resultante del cálculo.
Analicemos la siguiente expresión, paso a paso para ver los resultados intermedios.
int x=5;
String frase=”Valor=”;
System.out.println( frase = frase.concat(String.valueOf(x=x+15+frase.length())) );
20
6
26
26
“26”
“Valor=26”
“Valor=26”
nota.- Recuerda que la operación de asignación (=) retorna el dato que asigna.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 52
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
La evaluación se realiza en el orden que indican las líneas. Cuando finaliza la
evaluación se han producido dos acciones:
- Se ha asignado 26 al identificador variable x (que al principio valía 5).
- El identificador frase pasa a referenciar el nuevo dato String “Valor=26” creado a
partir de la concatenación del dato “Valor=” que antes referenciaba el
identificador frase y el dato “26” obtenido de convertir en String el número
resultante de las sumas.
Los datos creados para ser usados temporalmente (como la cadena “26” usada para
crear por concatenación otro String, o el entero 20 resultante de x+15) y que no se
asignan a ningún identificador, simplemente se pierden.
A menudo el último resultado de una expresión se desprecia, formando con ella una
sentencia independiente.
En el ejemplo, el String asignado al identificador frase es pasado al método println(...)
para que lo muestre por la salida estándar, pero podría no haberse utilizado println(...) y
haberlo escrito así...
.... ... .. .. ....
frase = frase.concat(String.valueOf(x=x+15+frase.length()))
.. .... .. . .. .. ..
Todo hubiese sucedido de la misma manera, excepto que la frase finalmente asignada
no se hubiese mostrado.
Otro posible criterio de catalogación: asignación, entrada, salida
La mayoría de los manuales de programación clasifican las sentencias de acciones en
sentencias de...
- Asignación: Aquellas por las que se asocia a un identificador un nuevo dato,
resultante de alguna expresión.
- Lectura:
Las que recogen datos desde un origen, como el teclado, un modem,
un fichero, el ratón, etc...
- Escritura:
Por las que el dato resultante de una expresión es enviado hacia un
destino, como la pantalla, un fichero, modem, una base de datos, una
impresora, un altavoz, etc...
Esto suele hacerse así porque ciertamente son éstas las más significativas en cualquier
programa (sin menosprecio del resto de instrucciones).
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 53
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Por lo que a java y a este manual se refiere, la asignación puede considerarse una
operación que forma parte del resto de expresiones tal como hemos visto.
En cuanto a las sentencias de entrada/salida (lectura/escritura) de datos, decir que
existen muchas dependiendo de cuál sea el origen/destino de la lectura/escritura, el tipo
de los datos y otras características que se estudiarán más adelante.
Por ahora, sólo se han utilizado sentencias para leer/escribir de la entrada estándar...
...de escritura, las siguientes sentencias independientes (de tipo void):
- Son estándar de java.
El método println(...) imprime al final un
carácter nueva línea y además acepta que
no se le dé dato.
System.out.print(<cualquier_tipo_de_dato>)
System.out.println(<cualquier_tipo_de_dato>)
System.out.println()
- Facilitados junto con este manual.
Son como los anteriores, pero se aseguran
de imprimir correctamente los caracteres
del alfabeto castellano.
Salida.print(<cualquier_tipo_de_dato>)
Salida.println(<cualquier_tipo_de_dato>)
Salida.println()
...de lectura, las siguiente expresiones (cada cual del tipo de dato que retorna) :
- Cada vez lee un dato int
- Cada vez lee un dato long
- Cada vez lee un dato float
- Cada vez lee un dato double
- Cada vez lee un dato char
- Cada vez lee un dato String, una línea entera.
- Cada vez lee un dato String, sólo una palabra.
Entrada.readInt()
Entrada.readLong()
Entrada.readFloat()
Entrada.readDouble()
Entrada.readChar()
Entrada.readString()
Entrada.readWord()
nota.- Ni el tipo Entrada, ni el Salida son estándar, pero se facilitan con este manual para hacer
simplificar la escritura/lectura de datos, que en java es bastante enredosa para quienes estén
empezando.
Bloque de Instrucciones
Antes de seguir más adelante, es conveniente que sepamos que a menudo un grupo de
instrucciones tiene que ser agrupado de modo que se comporten como si de una única
instrucción se tratase.
Para crear un bloque de instrucciones basta con acotarlas entre llaves.
... . .. .. .
{
instrucción_1;
instrucción_2;
.....
instruccion_N;
}
.. ... . ..... ..
Ten presente que los identificadores declarados dentro del un bloque de instrucciones
sólo existen ( y por tanto sólo se pueden usar ) dentro de dicho bloque.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 54
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Capitulo 6
Flujos de
Ejecución de un
Programa
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 55
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Sentencias de control de flujo.
Un programa es básicamente en una secuencia de instrucciones que se van ejecutando
en el orden en que aparecen escritas.
Si todo se limitase a eso, un programa realizaría idéntica tarea cada vez que se ejecutara.
Pero realmente un programa toma en cada paso una serie de pequeñas decisiones, que le
hacen elegir de manera “inteligente” cuál será la siguiente sentencia que va a procesar.
Estas decisiones las toma en base al resultado de expresiones (generalmente de tipo
lógico, boolean), que la sentencia de control deberá evaluar.
Estas sentencias de control las clasificaremos en cuatro tipos:
- Alternativas.
- Bucles.
- Ruptura de control.
- Excepciones. (éstas las veremos más adelante)
Alternativas
Esta categoría de sentencias permiten tomar decisiones relativas a si una instrucción (o
bloque de instrucciones) debe ser procesada, o no.
Java ofrece varias sentencias de alternativa lo cual permitirá elegir aquella que resulte
más apropiada para el tipo de opción que se desee expresar.
Los comportamientos de cada una de ellas se explicarán a continuación utilizando para
ello diagramas de flujo. Estos diagramas describen las posible vías de ejecución de una
serie de instrucciones.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 56
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Alternativa Simple
expresión
falso
Alternativa Doble
cierto
expresión
Alternativa Múltiple
falso
cierto
sentencia_B
expresión
si no
encaja
con
ningún
valor
sentencia_def_1
sentencia_def_2
··············
.....
if(expresión)
sentencia_A;
.....
if(expresión){
sentencia_A1;
sentencia_A2;
.....
sentencia_An;
}
.....
.....
if(expresión)
sentencia_A;
else
sentencia_B;
.....
if(expresión){
sentencia_A1;
.....
sentencia_An;
}
else{
sentencia_B1;
.....
sentencia_Bn;
}
.....
Las alternativas simple y doble son muy similares,
la primera es una simplificación de la segunda. En
ambos casos expresión tiene que devolver como
resultado un valor lógico.
• Si resulta true entonces se ejecuta sentencia_A
(o bloque de sentencias_A).
• En caso contrario sentencia_B (o bloque de
sentencias_B).
En la alternativa simple, si la expresión resulta
false no ejecuta nada.
nota.- En ambas sentencias alternativas para ejecutar más
de una sentencia, se pueden usar bloques de
instrucciones, haciendo que todas se comporten como
una única sentencia dependiente de la condición de la
alternativa.
Autor : FernandoToboso Lara
distintos casos
sentencia_0_1
sentencia_0_2
··············
sentencia_1_1
sentencia_1_2
··············
.. .. .. .. .. ..
sentencia_M_1
sentencia_M_2
··············
valor_0
valor_1
valor_M
.....
switch(expresión) {
case valor_0:
sentencia_0_1;
sentencia_0_2;
.......
break;
case valor_1:
sentencia_1_1;
sentencia_1_2;
.......
break;
.. .. .. .. ..
case valor_M:
sentencia_M_1;
sentencia_M_2;
.......
break;
default:
sentencia_def_1;
sentencia_def_2;
.......
break;
}
.....
Aquí los valores que acompañan a las
opciones case debe coincidir en tipo con
la expresión del switch.
Las sentencias que se ejecutarán son las
del case con el valor resultante de
e-mail: [email protected]
Pag. 57
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
del case con el valor resultante de
la expresión. Si no encaja con ninguno,
se ejecutan las de la opción default (en
caso de que ésta se haya escrito).
La sentencia switch tiene una gran
cantidad de posibilidades, tal como
veremos después.
Nótese cómo para hacer más fácil de leer:
- Se escribe (casi siempre) una instrucción por línea.
- Aquellas sentencias cuya ejecución depende de
una sentencia de control se escriben empezando un
poco más a la derecha, para que sea más fácil
reconocer la instrucción, o instrucciones, que
quedan bajo la influencia de la sentencia de control
(esto se conoce como sangrado de líneas).
Es evidente que tantas variantes no son imprescindibles, ya que por ejemplo con la
alternativa doble se podrían escribir las otras...
Alternativa Simple
Alternativa Múltiple
.....
.....
if(expresión)
sentencia_A;
else
; //sentencia vacía
if(expresión==valor_0)
{
sentencia_0_1;
sentencia_0_2;
....
.......
Nótese como un punto y coma siempre
marca el fin de una instrucción, si antes no
se pone nada la instrucción hace
exactamente eso... nada.
}
else if(expresión==valor_1)
{
sentencia_1_1;
sentencia_1_2;
.......
}
.......
.......
...sin embargo, el disponer de
más de una forma de expresar la
sentencia de control nos ofrece
la posibilidad de elegir en cada
situación aquella que más clara
resulte.
else if(expresión==valor_M)
{
sentencia_M_1;
sentencia_M_2;
.......
}
else
{
sentencia_def_1;
sentencia_def_2;
.......
}
Notese que aquí hemos usado bloques de instrucciones para
las secuencias de instrucciones mientras que en el switch no
era necesario.
Notese así mismo como las sentencias de control pueden a
su vez ser las sentencias (que se procesarán, o no) escritas
dentro de otras sentencias de control.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 58
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Los siguientes ejemplos quizás aclaren algunas dudas...
/**
Fichero: EjemploIf.java
Muestra un mensaje por la salida estándar mostrando la diferencia
de edad calculada entre los datos de dos personas.
**/
public class EjemploIf {
public static void main(String[] argumentos) {
StringBuffer mensaje;
int edad;
String nombre;
int otroEdad;
String otroNombre;
// Nombre y Edad de una persona
Salida.print("Cómo te llamas: ");
nombre=Entrada.readString();
Salida.print("Cuántos años tienes: ");
edad=Entrada.readInt();
// Nombre y Edad de la otra persona
Salida.print("Dime el nombre de otra persona: ");
otroNombre=Entrada.readString();
Salida.print("Y su edad: ");
otroEdad=Entrada.readInt();
Salida.println(nombre + " tiene " + edad + " años.");
Salida.println(otroNombre + " tiene " + otroEdad + " años.");
mensaje= new StringBuffer(nombre);
if(edad<otroEdad) {
mensaje.append(" es "+(otroEdad-edad));
mensaje.append(" años mas joven que "+otroNombre);
}
else
if(edad>otroEdad) {
mensaje.append(" es "+(edad-otroEdad));
mensaje.append(" años mayor que "+otroNombre);
}
else
mensaje.append(" tiene la misma edad que "+otroNombre);
Salida.println(mensaje);
}
}
nota.- Para componer por partes el mensaje que al final se mostrará, se utiliza un dato de tipo
StringBuffer que resulta más apropiado para modificarse con añadidos que un dato tipo String.
Como se puede observar , las expresiones lógicas que las sentencias if evalúan dan la
clave de cuál será el fragmento de código que se ejecutará.
Si edad es menor que otroEdad se ejecuta el bloque
cierto
falso
edad<otroEdad
que le sigue, continuando después por la última
línea del programa. Pero en caso contrario lo
que se ejecuta es la sentencia que sigue {
al primer else (que complementa al mensaje.append(" es "+otroE... cierto edad>otroEdad falso
mensaje.append(" años más…
primer if ), una nueva sentencia de }
alternativa doble con una nueva
{
expresión a evaluar:
mensaje.append(" tiene...
mensaje.append(" es "+otroEd...
• Si resulta cierta se ejecutan las sentencias del
bloque que viene a continuación,
mensaje.append(" años mayor…
}
• Si no la sentencia que sigue al nuevo else (que
complementa al segundo if ), continuando en
cualquier caso después por la última sentencia del programa.
A menudo se dice que la segunda sentencia de control está anidada en la primera
puesto que toda ella (con lo que dentro de ella haya) queda bajo el control de la anterior.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 59
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
/**
Fichero: EjemploSwitch.java
Escribe el nombre del mes correspondiente al numero que se
asigna al objeto mes
**/
public class EjemploSwitch {
public static void main(String[] args) {
int mes;
System.out.print(“Introduce un numero de mes: ”);
mes = Entrada.readInt();
switch (mes) {
case 1: System.out.println("Enero");
break;
case 2: System.out.println("Febrero");
break;
case 3: System.out.println("Marzo");
break;
case 4: System.out.println("Abril");
break;
case 5: System.out.println("Mayo");
break;
case 6: System.out.println("Junio");
break;
case 7: System.out.println("Julio");
break;
case 8: System.out.println("Agosto");
break;
case 9: System.out.println("Septiembre");
break;
case 10: System.out.println("Octubre");
break;
case 11: System.out.println("Noviembre");
break;
case 12: System.out.println("Diciembre");
break;
}
}
}
En este otro caso podemos ver cómo la expresión evaluada por la sentencia switch en
lugar de ser boolean, es un dato tipo int de cuyos posibles valores, nos interesan
solamente los que van del 1 al 12, los correspondientes a los doce meses.
La ejecución, al llegar al switch, continúa por la línea case cuyo número coincida con el
valor de la variable mes.
En este ejemplo no se ha incluido la opción default, por lo que si mes tuviese un número
que no apareciese en ninguno de los casos especificados, la ejecución saltaría
directamente al final de la sentencia de control, sin ejecutar ninguna opción.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 60
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Diagramas de Nassi-Scheiderman (o de Chapin)
Los diagramas de flujo, usados anteriormente para describir el funcionamiento de las
sentencias de control, resultan ideales para entender las diferentes vías de ejecución de
un fragmento pequeño de código. Sin embargo cuando el código que se quiere mostrar
de modo esquemático es un poco mayor, estos diagramas crecen muy rápido y resulta
muy complicado seguir la pista a las líneas de ejecución. Por esto resulta recomendable
el uso de otro tipo de diagramas más compactos.
Los diagramas de Chapin sirven igualmente para hacer mas claro el código, asociando a
los bucles y alternativas un esquema gráfico intuitivo y fácil de reconocer de un vistazo.
A diferencia de los diagramas de flujo cada gráfico tiene externamente una apariencia
rectangular que puede encajarse dentro de otra estructura de control (o colocarse a
continuación) y que aprovecha mejor el espacio en el que se dibuja.
Las estructuras descriptivas para cada variante de sentencia alternativa son las
siguientes:
····· ····· ···
.....
if(expresión condic.)
sentencia_A;
.....
expresión condicional
SI
sentencia_A
(o bloque de sentencias)
····· ····· ···
.....
if(expresión condic.)
sentencia_A;
else
sentencia_B;
.....
······· ···· ·· ······
expresión condicional
SI
sentencia_A
sentencia_B
(o bloque de sentencias)
(o bloque de sentencias)
······· ···· ·· ······
Autor : FernandoToboso Lara
e-mail: [email protected]
sentencias
por defecto
··········
sentencias_M
··········
sentencias_1
··········
e x p r e s i ó n
sentencias_0
··········
.....
switch(expresión) {
case valor_0:
sentencias_0
.......
break;
case valor_1:
sentencias_1
.......
break;
.. ... .... .
case valor_M:
sentencias_M
.......
break;
default:
sentencias_def.
.......
};
.....
Pag. 61
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Ejemplo de análisis para la resolución de una ecuación de 2º grado
Supongamos el siguiente planteamiento: resolver el problema de hacer un programa
que resuelva una ecuación de segundo grado, para todos los casos posibles (si la
solución es un número de tipo complejo supondremos que no tiene solución).
La forma genérica de una ecuación de segundo grado es: A ⋅ x 2 + B ⋅ x + C = 0
El programa leerá de la entrada estándar los valores de los coeficientes: A, B y C.
Después deberá encontrar la solución: para qué valor o valores de x, el resultado de
la función es igual cero.
SOLUCIÓN:
Lo primero que se debe hacer para resolver cualquier problema es analizarlo a fondo.
- Qué posibles casos se pueden presentar:
1.- Si A ≠ 0 tenemos el caso típico de una ecuación de segundo grado que se resuelve
2
aplicando la formula... x = − B ± B − 4 ⋅ A ⋅ C en la que pueden darse tres casos
2⋅ A
diferenciables, según sea el resultado de la parte (denominada discriminante) que
queda bajo la operación raíz cuadrada:
1.a.- Si el discriminante es negativo, el resultado necesita manejar números
complejos y no se calcularán (tal como indica la especificación del
planteamiento).
1.b.- Si es cero existe una solución x = − B ± 0 = − B
2⋅ A
2⋅ A
1.c.- Por último, si el discriminante es un número positivo, habrán dos soluciones
reales, una resultante de sumar la raíz cuadrada y otra de restarla.
2.- Si A = 0 la anterior fórmula no es válida, puesto que no se trata realmente de una
ecuación de segundo grado. Nuevamente nos podemos encontrar con dos casos:
2.a.- Si B ≠ 0 la ecuación que estamos resolviendo es f ( x ) = B ⋅ x + C = 0 donde la
única solución viene dada por la fórmula x = − C
B
2.b.- Pero si también B = 0 la anterior expresión tampoco es aplicable, porque la
ecuación se ajusta a la expresión f ( x ) = C = 0 donde como resulta evidente,
ya sólo caben dos opciones:
- Que C valga también cero, con lo cual la ecuación ( 0 = 0 ) se cumple
para cualquier valor de x. Es decir existen infinitas soluciones.
- Que C≠ 0 , con lo que la ecuación nunca es cierta para ningún valor de
x. Es decir no existe solución.
Una vez analizada la manera de resolver el problema y después de describirlo mediante
un algoritmo, pasamos a expresarlo de un modo más próximo al enfoque de los
lenguajes de programación. Sin utilizar aún ningún lenguaje concreto sino un
pseudocódigo genérico que facilite su posterior codificación usando el lenguaje que
resulte más apropiado o que por cualquier otra razón sea eligido (en nuestro caso java).
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 62
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
También es posible utilizar algún tipo de diagrama que haga más intuitivo el código que
vamos a codificar.
Discriminante, A, B, C .......................................................... variables tipo real
Leer Coeficientes (A,B,C)
A≠0
SI
NO
Discriminante
B≠0
B − 4 ⋅ A⋅C
2
SI
NO
C≠0
Discriminante>0
SI
Dos soluciones
− B + B 2 − 4 ⋅ A⋅C
2⋅ A
− B − B 2 − 4 ⋅ A⋅C
2⋅ A
NO
Una solución SI
NO
Discriminante==0
SI
NO
Una solución
−B
2⋅ A
Solución
Compeja
−C
B
No hay
solución
Infinitas
soluciones
Cualquier
número real
Escribir Solucion
El pseudocódigo es una buena opción para elaborar nuestros programas sin tener que
preocuparnos excesivamente de cuestiones propias de un lenguaje específico, algo así
como un pseudolenguaje de programación muy estructurado, cuya finalidad es la de ser
comprendido por personas, no por máquinas, por lo que puede permitirse cierta libertad
en la forma de expresar sus instrucciones.
Las soluciones del programa planteado, se puede expresar usando pseudocódigo de la
siguiente forma:
PROGRAMA
Segundo_Grado
ENTORNO:
a, b, c
VAR. TIPO REAL
x1, x2
VAR. TIPO REAL
discriminante
VAR. TIPO REAL
ALGORITMO:
SI a ≠ 0 ENTONCES
discriminante ← b * b - 4 * a * c
SI discriminante > 0 ENTONCES
x1 ← ( ( -b ) + sqrt( discr ) ) / (2 * a)
x2 ← ( ( -b ) - sqrt( discr ) ) / (2 * a)
Escribir “Solución una: ”, x1
Escribir “Solución dos: ”, x2
SINO
SI (discriminante = 0) ENTONCES
x1 ← (-b) / (2 * a)
Escribir “Una solución: ”, x1
** comienzan las declaraciones
** comienzan el algoritmo propiamente
** OJO...= en pseudocódigo, es
** la operación de comparación,
** la asignación se expresa
** con el signo flecha. (
←
)
SINO
Escribir “No existe ninguna solución real.”
FINSI
FINSI
** Solución compleja
SINO
SI b ≠ 0 ENTONCES
x1 ← ( -c ) / ( b )
Escribir “Una solución: ”, x1
SINO
SI c ≠ 0 ENTONCES
Escribir “No existe ninguna solución.”
SINO
Escribir “Infinitas soluciones.”
FIN
FINSI
FINSI
FINPROGRAMA
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 63
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Y por fin, implementarlo usando código java es muy sencillo...
// Fichero: Ecuacion2Grado.java
public class Ecuacion2Grado {
static void main (String [] arg){
double a,b,c, discriminante, x1,x2;
Salida.print("A= ");
a=Entrada.readDouble();
Salida.print("B= ");
b=Entrada.readDouble();
Salida.print("C= ");
c=Entrada.readDouble();
if(a!=0.0) {
discriminante=(b*b)-(4*a*c);
if(discriminante>0) {
x1=(-b+Math.sqrt(discriminante))/(2*a);
x2=(-b-Math.sqrt(discriminante))/(2*a);
Salida.println("Primer resultado de la ecuación: " + x1);
Salida.println("Segundo resultado de la ecuación: " + x2);
}
else
if(discriminante==0.0) {
x1=-b/(2*a);
Salida.println("Unico resultado de la ecuación: " + x1);
}
else
Salida.println("Resultado de tipo Complejo.");
}
else
if(b!=0.0) {
x1=-c/b;
Salida.println("Unico resultado de la ecuación: " + x1);
}
else
if(c!=0.0)
Salida.println("No hay solución.");
else
Salida.println("Infinitas soluciones. Vale cualquier número.");
}
}
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 64
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Bucles
También llamados iteradores, permiten que una sentencia (o bloque de sentencias) se
ejecute repetidas de veces.
Igual que en las alternativas, aquí se evalúa una condición sólo que ahora no se evalúa
una única vez. Mientras la condición resulte cierta, se repetirá la ejecución de las
instrucciones incluidas en el bucle volviéndose después a evaluar nuevamente la
expresión y así tantas veces como sea necesario hasta que resulte falsa.
Las variantes de que disponemos en java son tres:
Bucle while
Bucle do...while
Bucle for
inicialización
sentencia
expresión
falso
cierto
cierto
sentencia
expresión
expresión
falso
cierto
falso
sentencia
.....
while(expresión)
sentencia;
....
while(expresión){
sentencia_1;
sentencia_2;
....
sentencia_n;
}
....
Tras evaluar la
expresión, si resulta
true se ejecutará la
sentencia y después
vuelta a empezar.
Cuando la expresión
resulte false, la
ejecución pasará a la
instrucción que sigue
al bucle.
La sentencia, o
bloque de sentencias
contenidas dentro, se
ejecuta entre 0 y N
veces.
Autor : FernandoToboso Lara
.....
do
sentencia;
while(expresión);
....
do {
sentencia_1;
sentencia_2;
......
sentencia_n;
}while(expresión);
....
Primero se ejecuta la
sentencia y después se
evalúa la expresión,
volviendo a repetirse la
sentencia mientras que la
expresión sea true.
La sentencia, o bloque
de sentencias que
contiene dentro, se
ejecuta entre 1 y N
veces. Cuando se evalúa
la condición por primera
vez, ya se ha ejecutado
su contenido una
primera vez.
incremento
.....
for(inicialización;expresión;incremento)
sentencia;
....
for(inicialización;expresión;incremento){
sentencia_1;
sentencia_2;
.....
sentencia_n;
}
....
La parte inicialización contiene una instrucción (o
varias separadas por coma) que se ejecuta una sola
vez al comenzar el bucle.
La sentencia se ejecuta repetidas veces mientras la
expresión resulte true, pero además al final de cada
vuelta se ejecuta la instrucción (o instrucciones
separadas por coma) que se coloquen en la parte
incremento.
Este tipo de bucle se suele usar cuando se desea que
el bucle se repita un número de veces definido (la
condición suele depender del conteo con una
variable), mientras que los otros suelen ser más
adecuados a situaciones en las que la repetición deba
tener lugar dependiendo de una condición que pasará
a ser cierta en algún momento.
e-mail: [email protected]
Pag. 65
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
De modo similar a como ocurre con las sentencias alternativas, con un tipo de bucle
podrían simular los otros. Por ejemplo con un bucle tipo while podemos implementar
los otros...
Bucle do...while
.....
sentencia;
while(expresión)
sentencia;
....
sentencia_1;
sentencia_2;
....
sentencia_n;
while(expresión){
sentencia_1;
sentencia_2;
....
sentencia_n;
}
Bucle for
.....
inicialización;
while(expresión){
sentencia;
incremento;
}
....
inicialización;
while(expresión){
sentencia_1;
sentencia_2;
....
sentencia_n;
incremento;
}
....
....
Analicemos el siguiente ejemplo:
/**
Fichero: EjemploBucleWhile.java
Recorre una cadena de caracteres, copiando cada caracter en otra,
pero sólo mientras que no encuentra el caracter 'd'
Después muestra ambas cadenas, la original y la copia.
**/
public class EjemploBucleWhile {
public static void main(String[] args) {
String copiaDeAqui = "Copia esta cadena de caracteres hastas la letra 'd'.";
StringBuffer copiaEnEste = new StringBuffer();
int i = 0;
char c=‘ ’;
// La primera vez la condición del bucle será cierta
while (c != ‘d’) {
// ya que se inicializa c a un valor distinto de ‘d’
c = copiaDeAqui.charAt(i);
copiaEnEste.append(c);
i++;
}
System.out.println(copiaDeAqui);
System.out.println(copiaEnEste);
}
}
Cuando la ejecución llega a la sentencia while se evalúa la expresión c!=’d’ (que
compara el contenido de la variable c con el carácter ‘d’) y retorna como resultado true
o false. Mientras que el carácter que se asigna a la variable c sea diferente de ‘d’ la
condición será true y se ejecutarán por lo tanto las instrucciones del bloque, volviendo
después cada vez a evaluarse la condición. Cuando c valga ‘d’ el bucle finalizará,
pasando a la instrucción inmediatamente posterior al bucle.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 66
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Esto es lo que el programa habrá mostrado...
Copia esta cadena de caracteres hasta la letra 'd'.
Copia esta ca
_
Si no supieramos con certeza que el carácter ‘d’ está en el String original, lo condición
del bucle debería controlar también que la variable i nunca pueda exceder de la posición
tope del String, para evitar que se produzca un error.
while (c != 'd' && i<copiaDeAqui.length()) {
c = copiaDeAqui.charAt(i);
copiaEnEste.append(c);
i++;
}
nota.- Las posiciones válidas de los carácteres del dato
copiaDeAqui van desde 0 hasta copiaDeAqui.length()-1.
Es sensato pensar que dentro de las instrucciones del bucle, haya alguna que afecte a la
condición para que alguna vez pase a ser false y deje de repetirse (rara vez un programa
utiliza una condición que no deje de ser true, creando un bucle infinito, que nunca
acaba).
.... .. .... ..
while (true) {
.....
}
... .. .... ...
En el ejemplo dentro del bucle a cada vuelta se asigna el siguiente carácter del String
copiaDeAqui y cuando llega al carácter ‘d’ deja de cumplirse la condición y el bucle
finaliza su repetición.
Si en lugar de usar este bucle se hubiera utilizado el do..while, el bucle hubiera iterado
una vez más puesto que primero ejecuta la vuelta y después decide si repite.
.... .. ...
do
{
c = copiaDeAqui.charAt(i);
copiaEnEste.append(c);
i++;
} while (c != 'd');
.. .... .
Cuando detectase que c vale ‘d’ ya habría añadido el carácter al objeto copiaEnEste.
Por lo que al mostrar el objeto copiaEnEste podremos comprobar que contiene esto...
....
Copia esta cad
Autor : FernandoToboso Lara
... incluyendo el carácter ‘d’.
e-mail: [email protected]
Pag. 67
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Ejemplo de utilización de un bucle del tipo for( ; ; ).
/**
Fichero: EjemploBucleFor.java
Incrementa el valor del objeto variable num, desde 1 hasta 10
mostrando con cada valor los reglones de la tabla de multiplicar del 4
**/
public class EjemploBucleFor {
public static void main(String[] args) {
int num ;
System.out.println(" TABLA DE MULTIPLICAR DEL 4");
for( num=1 ; num<=10 ; num++ )
System.out.println(4 + " x " + num + " = " + 4*num);
}
}
Usualmente estos bucles se repiten un número determinado de veces. En nuestro caso
10 veces.
Para ello utiliza la variable num con la que va realizando el conteo de vueltas
(iteraciones), de modo que la condición que determina si se debe volver a repetir la
instrucción es la que comprueba que la variable (que se ha inicializado a valor 1) siga
siendo menor o igual que 10 (cuando num valga 11, la condición será false y terminará
el bucle).
- La parte inicialización se suele usar para dar un valor inicial a la variable
que cuenta. Se asigna el número 1 a num una sola vez.
- La parte de la expresión condicional se comporta de igual manera que en
el bucle while. Mientras sea true seguirá repitiéndose el bucle.
- La parte incremento para modificar al valor de num al final de cada vuelta,
normalmente incrementándola (o decrementándola) en una cantidad.
En las partes de inicialización e incremento, es posible incluir varias sentencias
separadas por coma, o dejarlas vacías según convenga.
Para hacerse una idea, observa que los siguientes bucles for modificados, hacen
exactamente lo mismo que hacían en el último ejemplo.
........
System.out.println(" TABLA DE MULTIPLICAR DEL 4");
num=1;
for( ; num<=10 ; ) {
System.out.println(4 + " x " + num + " = " + 4*num);
num++;
}
.. ... ....
........
for( System.out.println(" TABLA DE MULTIPLICAR DEL 4"), num=1 ;
num<=10 ;
System.out.println(4 + " x " + num + " = " + 4*num ), num++ );
.. ... ....
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 68
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
La expresión sin embargo debe ser única, puesto que debe dar un único resultado true o
false. Si se deja vacío el hueco de la expresión, se presupone resultado true, por lo que
el siguiente sería otra manera de codificar un bucle infinito.
.... .. .. ...
for(;true;) {
.....
}
... ... ....
// o bien simplemente: for( ; ; ) {
Diagramas de Nassi-Scheiderman (o de Chapin)
Del mismo modo que para las sentencias alternativas, se utiliza una estructura gráfica
para describir de manera intuitiva cada una de las variantes de bucle estudiados.
.....
while(expresión cond.)
sentencia;
....
··········
expresión condicional
sentencia
(o bloque de sentencias)
··········
.....
do
sentencia;
while(expresión cond.);
....
··········
sentencia
(o bloque de sentencias)
expresión condicional
··········
.....
for(inicialización ; expresión ; incremento)
sentencia;
....
··········
inicialización
expresión condicional
sentencia
(o bloque de sentencias)
··········
Autor : FernandoToboso Lara
e-mail: [email protected]
incremento
Pag. 69
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
En el siguiente diagrama de Nassi-Scheiderman se combinan sentencias alternativas y
bucle para representar la codificación de un programa que compara una serie de edades
introducidas por teclado con la edad de una tal Ana Rosa...
String Bloque;
int edad=24, tuEdad=1;
String nombre="Ana Rosa";
System.out.println(nombre+" tiene "+edad+" a±os de edad.");
System.out.println("Introduce edades (0 indica terminar)");
tuEdad!=0
// Pide tu edad
System.out.println("Que edad tienes tu: ");
tuEdad=Entrada.readInt();
tuEdad >= 0
SI
tuEdad>edad
mensaje:
edad>tuEdad
mensaje:
tuEdad-edad
"menor que tu” mensaje:
mensaje:
edad-tuEdad
"IGUAL EDAD”
"mayor que tu”
”Esto no
puede ser
una edad”
System.out.println(mensaje);
System.out.println(”Adios, hasta la próxima ejecución...”);
En estos diagramas, sólo se suelen escribir retazos de las sentencias del lenguaje o de
pseudocódigo.
Los fragmentos de código secuenciales se agrupan en rectángulos y dentro de cada
rectángulo (o a continuación de él) se colocan nuevos rectángulos representando el tipo
de sentencia de control que en cada caso convenga.
De este modo resulta bastante intuitivo hacer una lectura vertical (de arriba hacia abajo)
de las posibles vías de ejecución del programa.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 70
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Sentencias de Ruptura
Son sentencias muy simples cuya finalidad es modificar bruscamente la línea normal de
ejecución de las sentencias de control que hemos visto.
Son break , continue , label: , y return (esta útima se estudiará más adelante).
break
La más utilizada es break. Se utiliza en los bloques de instrucciones de las sentencias
switch y en los bucles. Hace que la secuencia de ejecución salga inmediatamente del
bloque y continúe por la instrucción inmediatamente posterior a la sentencia de control
en la que se encuentra.
Si se ejecuta dentro de un bucle, éste deja inmediatemente de repetirse sin intentar si
quiera volver a evaluar su condición.
Puede ser ésta por tanto una manera (un poco rebuscada y poco recomendable) de hacer
uso de un bucle aparentemente infinito, con una condición de finalización en algún
punto interior.
... . .. ...
while (true) {
.....
if(expresion condicional)
break;
.....
}
// tras ejecutar break sigue por aquí
.. .. .... ...
También, se puede usar en alternativas múltiples, donde como se a podido ver
anteriormente aparece con mucha frecuencia.
Contrariamente a lo que podría parecer por los ejemplos vistos hasta ahora, la sentencia
break al final de cada bloque case no es obligatoria.
El funcionamiento de switch es realmente más flexible de lo que inicialmente se dió a
entender.
Según el valor que resulte de la expresión evaluada, la ejecución salta para ejecutar por
las sentencias que siguen al case correspondiente, pero a no ser que se ejecute una
sentencia break (que llevaría la ejecución fuera del bloque de alternativas múltiples) la
ejecución continuaría ejecutando todas las instrucciones de los case siguientes.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 71
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Esto nos permite escribir estructuras de control con alternativas de ejecución mucho
más flexibles. Por ejemplo...
/**
Fichero: SwitchFlexible.java
Escribe el numero de dias que tiene un determinado mes
**/
public class SwitchFlexible {
public static void main(String[] args) {
int mes;
System.out.print(“Introduce un numero de mes: ”);
mes = Entrada.readInt();
System.out.println("Estos son los meses que teniendo igual numero de dias");
System.out.println(" que el mes " + mes + " van despues de el en el año.");
System.out.println("____________________________________________________");
switch (mes) {
case 2: System.out.println("Febrero");
break;
case 1: System.out.println("Enero");
case 3: System.out.println("Marzo");
case 5: System.out.println("Mayo");
case 7: System.out.println("Julio");
case 8: System.out.println("Agosto");
case 10: System.out.println("Octubre");
case 12: System.out.println("Diciembre");
break;
case 4: System.out.println("Abril");
case 6: System.out.println("Junio");
case 9: System.out.println("Septiembre");
case 11: System.out.println("Noviembre");
};
}
}
Cuyas posibles alternativas de
ejecución se describen en este
diagrama de flujo.
si no encaja
con ningún
valor
mes
distintos casos
case 2
case 1
case 3
case 5
.....
......
case 4
case 6
case 9
.....
......
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 72
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
continue
Utilizado dentro de cualquier bucle, hace que se dejen sin ejecutar las instrucciones
restantes de la actual vuelta, saltando al final (a la zona de incremento en el caso de los
bucles for) y a evaluar la condición para la siguiente vuelta.
Etiquetas
Se comportan como marcas que etiquetan las sentencias de control.
Tienen utilidad cuando etiquetan sentencias de control que a su vez contienen anidadas
(dentro de ellas) otras sentencias de control que en lugar de ejecutarse completas,
contengan rupturas de control (break o continue).
Estas sentencias de ruptura funcionarían en combinación con las etiquetas con la
finalidad de romper la secuencia no ya de la estructura de control más inmediata que las
contiene sino de otra más externa a la que identificarían por la etiqueta que se le haya
asociado.
La sentencia break, sin etiqueta haría que la ejecución saliese del bucle, o switch, más
próximo.
En este ejemplo sin embargo la hace salir del bucle precedido de la etiqueta que
especifica etiqueta_A.
Para etiquetar una sentencia de control a la
se que pretende marcar, se antepone un
identificador para la etiqueta y dos puntos.
Después las instrucciones de ruptura
pueden especificar a cuál de las sentencias
de control quieren afectar nombrando dicho
identificador.
.. .. ..... ..
etiqueta_A:
while (expresion_A) {
.....
while (expresion_B) {
.....
break etiqueta_A ;
.....
}
.....
}
// Tras ejecutar break, la ejecución
// sigue por aquí
..... ... . ....
De manera equivalente funcionaría si se utilizase con sentencias switch, y/o se tratase de
la sentencia continue.
Debe tenerse en cuenta que las rupturas de control producen programas no estructurados
y puede que resulte más difícil de entender el algoritmo que implementen. Sólo se
recomienda su utilización en fragmentos de código pequeños en los que su uso
simplifique de manera significativa el código.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 73
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Capitulo 7
Metodología
Básica
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 74
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Programar, a menudo resulta comparable a resolver crucigramas. Encontrar la
combinación de sentencias que paso a paso nos llevarán a la solución de un problema.
En realidad esta sería una definición de lo que ya antes hemos descrito como algoritmo.
La tarea de un programador es principalmente la de codificar esa secuencia de pasos que
el algoritmo describe, usando un lenguaje de programación que una máquina sea capaz
de comprender y ejecutar.
Para llegar a la codificación completa del programa será necesario resolver una gran
cantidad de pequeños problemas, para los que tendremos que echar mano de nuestro
propio ingenio, pero en lo más elemental podemos describir algunos elementos
fundamentales frecuentemente utilizados en cualquier código.
Algunas de estas estrategias, o algoritmos será lo que veremos a continuación.
Contador
Tan elemental como necesario, este elemento tiene la función de contar, habitualmente
mediante un número de tipo int.
Se reconoce por dos características:
- Parte de un valor inicial
especificado.
- Varía incrementándose, o
decrementándose en una
cantidad fija.
.. ...... ..
int peras;
peras=0; //cuenta desde cero
··········
<condición de un bucle>
·········
peras=peras+1
// o bien.. peras+=1;
// incrementa en uno el
// valor de peras cada
// vez que se ejecuta
·············
....... ...... . ....
No debe extrañarnos el hecho de que la variable peras aparezca antes y después del
signo ‘=’ ya que éste no expresa igualdad sino la operación de asignación (la igualdad
se expresa con dos signos ‘==’).
En esta sentencia como en cualquier operación de asignación, primero se evalúa el valor
de la expresión que va a ser asignada y finalmente el valor resultante se asigna a la
variable que precede a la asignación. Por consiguiente, en el ejemplo anterior
simplemente se asigna a peras el resultado de sumar el valor que contenía más 1.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 75
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Antes deberemos inicializar el contador con el valor desde el cual deseamos empezar el
conteo.
······· ···· ····
public static void main(String[] args) {
int num=0;
System.out.println(" Numeros pares del 0 a al 100....");
while(num<=100) {
System.out.print(num);
System.out.print(‘ ’);
num=num+2;
}
}
···· ··
Tal como hemos visto son habituales en los bucles tipo for en los que tienen un espacio
ideal para recibir su valor inicial y otro espacio perfecto para ser incrementados, o
decrementados.
En ellos la variable que hace de contador suele aparecer en la expresión condicional
indicando el valor que debe alcanzar para terminar las iteraciones (vueltas).
Acumulador
Se parece mucho a un contador pero se diferencia de éste en que sus incrementos no son
constantes.
Estas variables acumulan (como su
nombre indica) valores,
incrementando su propio valor con
las cantidades que se desean
acumular:
- Parte de un valor inicial
especificado.
- Varía incrementándose, o
decrementándose en una
cantidad variante que contiene
el valor a acumular.
.. ...... ..
int totalPeras;
int peras;
totalPeras=0; //cuenta desde cero
··········
<condición de un bucle>
·········
totalPeras+=peras;
// o bien totalPeras=totalPeras+peras
/*aumenta el valor de ‘totalPeras’
en la cantidad indicada por la
variable peras cada vez que se
ejecuta */
// la variable peras cambiará al nuevo
// valor que se quiera acumular antes
// de que se repita la sentencia de
// acumulación.
·············
....... ...... . ....
Tras ejecutarse repetidas veces la suma acumulativa de los valores que va conteniendo
la variable peras, ya que se van sumando a la variable totalPeras que empezó
inicializándose a 0.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 76
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Cálculo de la media de una serie de números enteros.
Media ( n1 , n2 ,L, nk ) =
n1 + n2 + L + nK
K
/**
Fichero: Media.java
Se pide que se introduzcan por teclado una serie de números
dando por acabada la lista de números cuando se introduzca
un dato no numérico
**/
import java.io.*;
public class Media {
public static void main(String[] argumentos) {
double sumaNumeros=0.0; // variable ACUMULADOR
double numero=0.0;
double media;
int cuantos=0;
// variable CONTADOR
System.out.println("Introduzca una lista de números (para acabar...");
while(numero!=999.0) {
numero=Entrada.readDouble();
if(numero!=999.0) {
sumaNumeros+=numero; // incremento del ACUMULADOR
cuantos+=1;
// (o bien...cuantos++;) incremento del CONTADOR
}
}
media=sumaNumeros/cuantos;
System.out.println("La media es "+media);
}
}
Pero la acumulación no siempre es sumativa, a veces los valores que se añaden se van
multiplicando. En tal caso la variable que acumula, se suele inicializar a 1 para que el
primer valor se acumule tal como es.
·· ······ ······
acumulador=1; //
//
//
//
···
Inicia la cuenta desde 1.
De manera que 1 multiplicado por
un número calquiera, acumulará
inicialmente el propio número.
<condición de un bucle>
·········
acumulador*=valores /*acumula multiplicativamente el
valor de ‘valores’ en la variable
acumulador cada vez que se ejecuta */
···· ·· ··· ····
// ‘valores’ cambiará a un nuevo valor en cada iteración
·············
....... ...... . ....
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 77
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Cálculo del factorial de un entero positivo.
Factorial ( n ) = n ⋅ ( n − 1) ⋅ ( n − 2) ⋅ K ⋅ 1
( Factorial (0) = 1)
/**
Fichero: Factorial.java
Se pide un número entero y se calcula su factorial.
El factorial de un numero es el resultado de multiplicar los
números que hay desde el uno hasta el numero. Y el factorial de
cero es uno. Sólo se aplica a enteros positivos.
**/
import java.io.*;
public class Factorial {
public static void main(String[] argumentos) {
long factorial=1L; // variable ACUMULADOR
long num,numOriginal; // variable CONTADOR
System.out.println("Introduzca un número entero: ");
numOriginal=Entrada.readLong();
num=numOriginal; // inicialización del CONTADOR
if(num>=0) {
while(num>1) {
factorial*=num; // incremento del ACUMULADOR
num--; // decremento del CONTADOR
}
System.out.println( "Factorial(" + numOriginal + ")=" + factorial );
}
else
System.out.println("El numero debe ser positivo..");
}
}
Lectura Anticipada
En el programa (anteriormente expuesto) que calculaba la media de una serie de
números, el bucle while se repitía mientras el número introducido por teclado no fuese
el 999.0 que se interpreta como la señal de que se desea finalizar (desgraciadamente,
este número no se podrá introducir para participar en la media).
- Para asegurarse de que la primera vez que se evalúa la condición del bucle, resulte
cierta (y por tanto se ejecute la primera vuelta), se inicializa la variable numero al
valor 0.
- Además, para no tener en cuenta el número (marca de final) 999.0 en el cálculo de la
media, el incremento del acumulador y del contador se hace sólo si se comprueba
que el número introducido aún no es éste.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 78
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Un modo más adecuado de codificar esto sería hacer una lectura anticipada.
Esta técnica es útil cuando en un bucle se lee al comienzo de cada vuelta un dato que
también aparece en la expresión condicional del bucle, interviniendo en la decisión de si
se realiza una nueva vuelta, o no.
SIN LECTURA ANTICIPADA
CON LECTURA ANTICIPADA
Inicializar DATO para que empiece cumpliendo,
la condición del bucle
Leer DATO
Mientras (DATO cumpla alguna condición) Hacer
Mientras (DATO cumpla alguna condición) Hacer
Leer DATO
Si DATO cumple la condición
Procesar DATO
.
SI
Procesar DATO
Leer DATO
NO
---
Como vemos en el esquema, se trata simplemente de desdoblar la lectura del
subsodicho dato, realizándola ...
... una vez antes de entrar en el bucle de modo que la primera vez que se evalúe la
condición, ya se disponga del dato.
... y otra al final del bucle para que cada vez que empiece una nueva vuelta se disponga
de un nuevo dato, leído inmediatamente antes de volver a evaluar la condición.
El programa de la media, usando lectura anticipada quedaría de esta forma:
···· ··· ·········
public class Media {
public static void main(String[] argumentos) {
double sumaNumeros=0.0; // variable ACUMULADOR
double numero; // Ya no necesita inicializarse aquí.
double media;
int cuantos=0;
// variable CONTADOR
System.out.println("Introduzca una lista de números (para acabar...");
numero=Entrada.readDouble();
while(numero!=999.0) {
sumaNumeros+=numero;
// incremento del ACUMULADOR
cuantos+=1; // (o bien...cuantos++;) incremento del CONTADOR
numero=Entrada.readDouble();
}
media=sumaNumeros/cuantos;
System.out.println("La media es "+media);
}
}
Con esta táctica además nos ahorramos tener que inicializar la variable numero a un
valor que realmente nunca se iba a utilizar, al tiempo que la introducción del número
‘centinela’ 999.0 es detectada antes de que el dato sea acumulado, por lo que ya no hay
necesidad de usar la sentencia if que duplicába la condición del while.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 79
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Interruptor
Es un uso característico que se da a veces a una variable que alterna su contenido,
normalmente entre dos opcionales. Algo así como encendido y apagado. De modo que
el estado de la variable induce a que una o varias sentencias se ejecuten en turnos
alternantes.
............ .... ..
boolean encendido;
.............
encendido = true; // valor inicial del interruptor.
<condición de un bucle>
if(encendido)
SI
// sentencias que se ejecutan
// cuando encendido es cierto
·· ····· ····
// sentencias que se ejecutan
// cuando encendido es falso
··· ······ ··
encendido = !encendido;
/* invierte el valor de encendido al valor opuesto
de modo que en cada vuelta, las sentencias
que se ejecutan se van turnando. */
...............
El siguiente ejemplo es similar al Objeto.java (el que vimos para mostrar cómo se usan
los datos referenciados).
// Fichero: Interruptor.java
class Interruptor {
public static void main(String[] args) {
Cromo cromo = new Cromo();
System.out.println("");
System.out.println(" *** Con esta ventana activa (no la ventana del cromo) ***");
System.out.println(" ***
pulsa cualquier tecla
***");
System.out.println(" *** para desplazar en diagonal (escalonado) el cromo ***");
int i;
boolean desplazaHorizontal = true;
for(i=0;i<20;i++) {
Entrada.readPulsacion(); // Para forzar una pausa por cada vuelta.
// elemento interruptor.
if(desplazaHorizontal)
cromo.derecha();
else
cromo.abajo();
desplazaHorizontal = !desplazaHorizontal;
}
cromo.dispose();
System.exit(0);
}
}
En este caso hemos usado un dato de tipo referenciado llamado Cromo, para desplazarlo
por la pantalla... hacia la derecha ... y hacia la abajo ...alternando a cada vuelta del
bucle.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 80
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Intercambio
Es un algoritmo muy característico y elemental, que podemos usar en nuestros
programas con la finalidad especifica de intercambiar el contenido de dos variables.
Sean A y B, dos variables del mismo tipo cuyos contenidos deben ser intercambiados.
La situación no es complicada pero mejor no precipitarse.
Si se comienza por asignar el contenido de B a A, se pierde al valor contenido en A,
y si procedo a la inversa (asignando A a B) se pierde el valor de B.
El modo correcto de hacerlo es simple:
A
- Primeramente necesitamos disponer de una
variable del mismo tipo que A y B. Supongamos
que se la llama AUX (por lo de auxiliar).
B
2º
1º
AUX
1.- asignar temporalmente el contenido de A a
AUX, para no perderlo.
3º
2.- asignar el valor de B a A.
3.- asignar AUX a B.
nota.- Lo mismo dará si se hace a la inversa,
empezando por asignar B a AUX .
···· ······
// El tipo de dato es indiferente.
<tipo> A,B;
<tipo> AUX;
// Se supone que ambas variables
// tiene ya algún dato asignado.
······· ··· ··
// INTERCAMBIO
AUX = A;
A = B;
B = AUX;
·······
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 81
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Búsqueda del máximo y del mínimo
Otro de estos algoritmos básicos es el de buscar el valor máximo (... o mínimo) de entre
una serie de valores.
La estrategia que se suele usar consiste en elegir el primer valor de la serie como el
máximo (o mínimo) temporalmente, procediendo después a compararlo con todos y
cada uno de los restantes valores. Cada vez que se encuentre un valor mayor (o menor)
que el temporalmente seleccionado, pasa a ser el mayor (o menor) elegido hasta el
momento.
El que al final permanezca seleccionado será el mayor (o menor) de todos los datos.
···· ····· ····
leer primer_dato;
maximo = primer_dato;
···· ····· ····
leer primer_dato;
minimo = primer_dato;
leer siguiente_dato;
leer siguiente_dato;
--- mientras queden datos ---
--- mientras queden datos --siguiente_dato
>
maximo
SI
siguiente_dato
NO
N
A
D
A
maximo = siguiente_dato;
leer siguiente_dato;
·· · ·· ··· ····
// maximo es el mayor dato leído.
<
minimo
SI
NO
minimo = siguiente_dato;
N
A
D
A
leer siguiente_dato;
·· · ·· ··· ····
// minimo es el menor dato leído.
/**
Fichero: MaxMin.java
Se pide que se introduzcan por teclado una serie de números dando por acabada
la lista de números cuando se introduzca un dato no numérico y nos dice cuál de
todos ellos es el mayor y cuál el menor.
**/
import java.io.*;
public class MaxMin {
public static void main(String[] argumentos) {
double maximo,minimo;
double numero;
System.out.println("Introduzca una lista de números (para acabar ... ");
numero=Entrada.readDouble();
if(numero!=999.0) {
maximo=minimo=numero; // El primer valor es el mayor y el menor (por ahora).
numero=Entrada.readDouble();
while(numero!=999.0) {
if(numero>maximo)
// Si el nuevo valor es mayor que maximo
maximo=numero;
// este es el nuevo maximo.
else if(numero<minimo) // y si no, puede que sea menor que minimo
minimo=numero;
// entonces este es el nuevo valor minimo.
numero=Entrada.readDouble();
}
// Finalmente maximo contiene el valor mas grande y minimo el menor.
System.out.println("El mayor de todos es " + maximo);
System.out.println("El menor de todos es " + minimo);
}
else
System.out.println("No me has dado ni un sólo número");
}
}
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 82
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Una alternativa a lo anterior, consiste en inicializar las variables que almacenan el valor
temporalmente mayor (o menor) a los siguientes valores...
- La variable maximo, al valor más pequeño posible.
- Y la variable minimo, al valor más grande posible.
...de modo que cuando se comparen con el primer dato de la lista, sea cual sea éste,
resultará siempre mayor que maximo y menor que minimo (pasando en este momento a
ser el máximo y el mínimo).
De este modo evitamos tener que escribir la instrucción de lectura dos veces antes de
llegar al bucle:
·· ··· · ····
maximo=Double.NEGATIVE_INFINITY; // Cualquier valor será mayor que éste.
minimo=Double.POSITIVE_INFINITY; // Cualquier valor será menor que éste.
numero=Entrada.readDouble();
while(numero!=999.0) {
if(numero >maximo)
// Cada nuevo valor que sea mayor que máximo
maximo= numero; // pasará a ser el nuevo máximo.
if(numero <minimo)
// Cada nuevo valor que sea menor que mínimo
minimo= numero; // será el nuevo valor mínimo.
numero=Entrada.readDouble();
}
if(maximo!=Double.NEGATIVE_INFINITY || minimo!=Double.POSITIVE_INFINITY) {
System.out.println("El mayor de todos es " + maximo);
System.out.println("El menor de todos es " + minimo);
}
else
System.out.println("No me has dado ni un sólo número");
}
}
Y el resultado final será idéntico, al del ejemplo anterior.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 83
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Capitulo 8
Tablas de Datos
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 84
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Arrays de datos
Un array (o tabla) es una estructura de datos, capaz de almacenar consecutivamente un
número finito de valores todos ellos del mismo tipo y a los que se puede acceder
indicando la posición que ocupan.
Las tablas en java son datos de tipo referenciado por lo que al declarar identificadores
con los que manejarlas, no creamos la estructura propiamente dicha sino que, habrá que
crear el dato tabla separadamente usando la instrucción new. Ciertamente constituyen un
tipo de dato muy peculiar por la manera de utilizarse.
El número de datos que es capaz de albergar una tabla se establece en el momento en
que se crea y a partir de ese momento su longitud es fija.
Ese tamaño puede ser expresado mediante una variable (cuyo valor bien pudiera haberse
obtenido en tiempo de ejecución, leído por ejemplo del teclado) de modo que se podrán
crear tablas con tamaños distintos cada vez que el programa se ejecute.
Para acceder a los datos que guarda, se debe indicar la posición que estos ocupan dentro
de la estructura...
tabla ‘t’
t [0]
t [1]
t [2]
t [3]
t [N-1]
D1
D2
D3
D4
DN
...empezando por la posición cero.
La siguiente terminología es habitual en todos los lenguajes con tablas:
- Se dice que el tipo de la tabla (o array) es el tipo de datos que almacena.
- Se llama elemento a cada una de las casillas (posiciones) que albergan datos.
- Por tamaño de la tabla se entiende número de elementos que tiene
nota.- Nótese que es distinto al hueco de memoria que ocupa, pues éste
dependerá también del tamaño de memoria que ocupe cada elemento.
La sentencia declarativa para el identificador que referenciará a la tabla tendrá el
siguiente formato:
<tipo>[] <identificador>;
Pero para crear la tabla y asignar su referencia al identificador declarado, escribiremos:
<identificador> = new <tipo>[<tamaño>];
Esto creará una tabla cuyos elementos quedarán inicializados a un valor por defecto que
dependerá del tipo dado a la tabla.
Si es un tipo primitivo numérico (short, int, float, ...) quedará inicializada con ceros.
Si es char, con el carácter cuyo código Unicode es cero.
Si boolean, a valor false.
Si es de un tipo referenciado, cada casilla contendrá null.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 85
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
En todos los casos siempre se inicializa con el valor “neutro” de cada tipo de dato.
No obstante en el momento de declarar el identificador de la tabla (y sólo en ese
momento), podemos inicializar su contenido al tiempo que la creamos asignándole los
datos que contendrá, separados por comas y agrupados entre llaves.
<tipo>[] <identificador>= {<dato_1>,<dato_2>,···,<dato_N>};
Esto será equivalente a crear la tabla y asignar a cada posición el dato especificado entre
las llaves.
Una vez creada la tabla podremos acceder a los datos que almacena, escribiendo el
identificador seguido de un par de corchetes con la posición a la que queremos acceder.
...
<identificador>[<posición>]
...
Esto nos permitirá tanto consultar, como modificar el datos almacenado en dicha
posición.
··· ······ ····
int[] tabla = {10,11,12,13,14,15};
int primerNum;
int ultimoNum;
primerNum = tabla[0];
ultimoNum = tabla[5];
····· ···
tabla[0] = 25;
tabla[1] = 5 + tabla[4];
·· ·· · ·
nota.- No te confundas, si la longitud de la tabla es 6,
sus posiciones válidas van desde 0 hasta 5
Java permite que se consulte el tamaño dado a una tabla, escribiendo...
<identificador>.length
A continuación vemos un ejemplo en el que se piden los datos de una serie de personas.
Sus nombres se van almacenando en una tabla de tipo String y sus edades en otra tabla
tipo int.
Finalmente el programa listará los nombres y edades de los mayores de 18 años.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 86
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
/**
Fichero: MayoresEdad.java
Primero pide que se le introduzca las edades de las personas,
cuyos nombre se van especificando, para luego mostrar un listado
por la salida estándar de los mayores de 18 años (o con 18 años cumplidos). **/
import java.io.*;
public class MayoresEdad {
public static void main(String[] argumentos) {
// Tabla de tamaño 5 de tipo int (sin inicializar los datos contenidos)
int[] edades=new int[5]; // Declara el identificador y se crea el
// objeto en una sola instruccion.
/* A continuación en una sola sentencia se crea la tabla y los
5 objetos String que contiene y se asigna la referencia de la tabla
al identificador declarado 'nombre' */
String[] nombres = {"Ana Rosa","Juan","Fernando","Carolina","Aitor"};
int i;
boolean error=false; // Hace función de interruptor...
// ...la activamos si introduce alguna edad erronea.
// Pide las edades de las 5 personas.
for(i=0;i<nombres.length;i++) {
System.out.print("Que edad tiene "+nombres[i]+' ');
// Lee la edad, guardandola en la posición i-ésima de la tabla edades
edades[i]=Entrada.readInt();
if(edades[i]<0) {
System.out.println("Error: eso no puede ser una edad...");
error=true; // se activa la variable de error a cierto.
break; // se interrumpe en bucle 'for'
}
}
if(!error) { // si ha habido error no se ejecuta el resto.
/* Repasa las tablas listando los contenidos 'nombre...edad' de las
posiciones que tiene una edad mayor o igual a 18 años. */
System.out.println("");
System.out.println("- Los mayores de edad son: ");
for(i=0;i<nombres.length;i++)
if(edades[i]>=18)
System.out.println(nombres[i]+" tiene "+edades[i]+" años.");
}
}
}
El tipo de la tabla nombres es String (los datos que guarda son de este tipo). Esto
implica que nombres[x], para cualquier posición x válida, contiene una referencia a un
dato de tipo String, o bien una referencia nula (null) si no se le ha asignado ningún dato.
El identificador nombres[x] puede considerarse que es, a todos los efectos, una variable
que guarda el dato String, al margen de que realmente sea una de las casillas que forman
parte de la estructura tabla.
Por ejemplo, la siguiente sentencia ejectaría un método que es característico de los datos
String.
nombres[x].toUpperCase();
Y retorna como resultado un nuevo dato String como el que hay en la posición x de la
tabla nombres, pero con sus caracteres alfabéticos en mayúsculas.
De modo que si pretendemos que el String de dicha posición pase a estar en mayúsculas
podríamos ejecutar...
nombres[x] = nombres[x].toUpperCase();
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 87
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Este sería el gráfico que describe la acción que se llevará a cabo...
re
mb
no
s[0
]
[
res
mb
no
1]
[
res
mb
no
“Ana Rosa”
“Juan”
“Fernando”
2]
[
res
mb
no
3]
]
s[ 4
bre
m
no
“Aitor”
El dato String que ya
no es referenciado por
ningún identificador deja
de existir automaticamente
“Carolina”
“CAROLINA”
Dato resultante
de la ejecución de
nombres[3].toUpperCase
En el anterior programa, al declarar el identificador nombres, creamos simultáneamente
las datos de tipo String (...“Ana Rosa”, “Juan”, ... ) y la tabla que los contenía...
String[] nombres = {<String>, <String>, ... };
Pero cuidado de no cometer el siguiente error...
·······
String[] nombres = new String[5]; // Se crea un array de 5 String
// (pero no los datos String)
for (i = 0; i < nombres.length; i++)
// ERROR: las posiciones ‘nombres[i]’ no referencian ningún dato.
System.out.println(nombres[i]);
···········
Recuérdese que cada dato de tipo referenciado debe ser creado específicamente. Aquí
hemos creado una tabla de datos, pero no los datos que almacenará.
Si los datos contenidos en la tabla son de un tipo primitivo, no tendremos este problema,
pues sólo será necesario declararlos, pero sin crearlos por separado.
·······
char[] letras = new char[5]; // Se crea un array de 5 caracteres.
for (i = 0; i < letras.length; i++)
System.out.println(letras[i]); // CORRECTO.
···········
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 88
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Recorridos de una tabla
Ésta es una operación fundamental de las tablas. Consiste en realizar una acción con
todos sus elementos (o una parte de ellos), siguiendo un orden elegido (típicamente
secuecial).
El esquema es similar para cualquier tipo de tabla. La acción se realiza por turnos sobre
cada uno de los elementos a los que se accede usando como índices de posición,
variables que uno o más bucles se encargarán de incrementar (o decrementar) de modo
que la acción se realice repetidas veces y cada vez sobre un elemento diferente de la
tabla.
El siguiente fragmento, muestra cómo se realizaría el recorrido competo de una tabla,
desde su primer elemento hasta el último.
···········
<tipo>[] tabla = new <tipo>[dim1];
for(i=0; i<dim1; i++)
// o bien... for(i=0; i<tabla.length; i++)
···· ···
ACCION ( tabla[i] );
·· · ····· ···
·······················
La ACCION se coloca dentro del bucle que incrementa el índice i, de modo que a cada
vuelta la ACCION se aplica al elemento cuya posición indica i.
El bucle se repite mientras i sea menor que el tamaño de la tabla, que puede ser obtenido
indistintamente consultando la propiedad length del dato tabla, o escribiendo
directamente su tamaño si lo conocemos.
/**
Fichero: Inversos.java
Calculo de la función inversa de los números reales del 1.0 al 10.0. **/
public class Inversos {
public static void main(String[] argumentos) {
double[] tablaNumeros = {
1.0, 2.0, 3.0, 4.0, 5.0,
6.0, 7.0, 8.0, 9.0, 10.0 };
double[] inversos=new double[10];
int casilla;
// ACCION: Invertir cada numero... (1/x)
for(casilla=0 ; casilla<10 ; casilla++)
inversos[casilla]= 1.0 / tablaNumeros[casilla];
// ACCION: Mostrar resultados.
for(casilla=0 ; casilla<10 ; casilla++)
System.out.println("Inverso(" + tablaNumeros[casilla] + ")=" + inversos[casilla]);
}
}
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 89
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
El resultado de este programa será este:
Inverso(1.0)=1.0
Inverso(2.0)=0.5
Inverso(3.0)=0.3333333333333333
Inverso(4.0)=0.25
Inverso(5.0)=0.2
Inverso(6.0)=0.16666666666666666
Inverso(7.0)=0.14285714285714285
Inverso(8.0)=0.125
Inverso(9.0)=0.1111111111111111
Inverso(10.0)=0.1
Resulta muy conveniente planificar separadamente, la elaboración del recorrido y la
acción que se desea aplicar a los elementos recorridos.
Los recorridos no siempre incluyen la totalidad de elementos de la tabla. En ocasiones
solamente interesará recorrer parte de ellos. También es posible que el orden en que se
recorran influya en el resultado de la acción que se aplica.
El problema de implementar un recorrido puede simplemente verse como la estrategia
por la que se genera en una variable (que será índice de la tabla) la secuencia de valores
correspondientes a las posiciones de los elementos sobre los que queremos aplicar la
acción. Para ello suele bastar con un simple bucle for que modifique adecuadamente el
índice.
A continuación vemos algunos ejemplos de recorrido sobre una tabla de tamaño 6 cuyos
índices, no lo olvidemos, van desde 0 hasta 5 ( es decir, hasta tabla.length-1 ).
···· ···
for(i=0; i<tabla.length; i=i+2)
ACCION( tabla[i] );
···· ···
···· ···
for(i=tabla.length-1; i>=0; i--)
ACCION( tabla[i] );
···· ···
···· ···
for(i=tabla.length-2; i>=2; i=i-1)
ACCION( tabla[i] );
···· ···
···· ···
for(i=tabla.length-2; i>0; i=i/2)
ACCION( tabla[i] );
···· ···
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 90
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Arrays Bidimensionales
Una situación particularmente interesante es la que se produce cuando el tipo de dato
contenido en la tabla es otra tabla (del tipo que sea). Se da el caso de que, puesto que
una posición cualquiera i-ésima referenciará una tabla, se pueden usar nuevamente
corchetes con otro índice que indique la posición j-ésima deseada dentro de la otra tabla.
T … identificador de la tabla
T[0]
T[1]
T[2]
T[0][0] T[0][1]
T[m]
T[m][0] T[m][1]
T[0][2]
T[0][n]
T[1][0] T[1][1]
T[2][0] T[2][1]
Este tipo de tablas se dice que son de dos dimensiones o bidimensionales puesto que
para referirse a un elemento de la tabla es necesario usar dos índices.
Por los valores de los índices para cada elemento es habitual suponer que los elementos
se disponen en filas y columnas.
T[0][0] T[0][1]
T[0][2]
T[0][n]
T[1][0] T[1][1]
T[1][2]
T[1][n]
T[2][0] T[2][1]
T[2][2]
T[2][n]
T[m][0] T[m][1] T[m][2]
T[m][n]
Las tablas bidimensionales son muy útiles para organizar la información agrupándola
conceptualmente en filas y columnas. El primer índice suele representar la fila y el
segundo la columna.
Sin embargo, es necesario tener presente que las tablas bidimensionales de java, no
siempre se pueden representar usando un casillero de m x n elementos, ya que las tablas
pueden tener cada fila de una longitud diferente, si así se decide en el momento de
crearlas.
· ···· ·····
tipo [][]
identificador;
// Declara el identificador para referenciar
// una tabla bidimensional de tipo ‘tipo’
identificador = new tipo [<numero_filas>][];
// Crea el dato tabla que referenciará
// otras tablas (aún no creadas)
······· ····· ···
for(i=0; i<identificador.length; i++)
// Crea cada fila, cada cual con
// su propio tamaño.
identificador[i] = new tipo [<tamaño_fila_iésima>];
· ·· ····
······· ·······
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 91
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Ya que es necesario crear todas y cada una de las tablas, tanto la tabla que referenciará a
las filas, como las propias tablas correspondientes a cada fila que serán referenciadas
por las casillas de la primera.
No obstante, es más frecuente que todas las filas sean de igual longitud y en tal caso se
crean todas simultáneamente en una única sentencia new.
··········
<tipo> [][]
identificador; // Declara el identificador como antes
// Y a continuación crea tanto el array principal como los que éste referencia.
identificador = new <tipo> [<numero_filas>][<tamaño_de_las_filas>];
·······
Si se desea inicializar los contenidos de estas tablas, podemos expresarlo de manera
similar a como se hizo en las de una sola dimensión, asignando (en el momento de la
declaración del identificador que las referenciará) los valores separados por comas y
entre llaves.
Y... puesto que en este caso los valores son a su vez tablas serán nuevamente
agrupaciones de sus elementos entre llaves.
··········
// Y a continuación crea tanto el array principal como los que éste referencia,
// inicializando los valores contenidos en cada elemento (o fila).
tipo [][] identificador= { {<valor_00>, <valor_01>, <valor_02>, ...., <valor_0a>},
{<valor_10>, <valor_11>, <valor_12>, ...., <valor_1b>},
········
·········
·····
{<valor_m0>, <valor_m1>, <valor_m2>, ...., <valor_mx>} };
·······
Donde como puede observarse, el número de filas (m) así como el tamaño de cada una
de las filas (a,b...,x) dependerá directamente del número de valores usados para
inicializar.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 92
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Arrays Multidimensionales
Generalizando lo hasta ahora visto, llamaremos dimensión de una tabla al número de
índices que es necesario utilizar para acceder a uno de sus elementos.
Si el tipo de la tabla bidimensional fuera nuevamente otra estructura tipo tabla, habría
que utilizar un índice más para hacer referenciar a sus elementos individualmente.
Todo lo expuesto para tablas bidimensionales se puede aplicar de modo similar a las
tablas de cualquier dimensión.
Recorridos de una Tabla bidimensional
Este fragmento realiza un recorrido completo de la tabla, desde su primera fila hasta la
última y en cada una de ellas de principio a fin y como puede observarse para ello lo
único que hace es generar la secuencia de combinaciones válidas para ser usadas como
índices de la tabla.
···········
<tipo> [][] dosDimensiones = new <tipo>[dim1][dim2];
···· ···
for(fila=0; fila<dosDimensiones.length;fila++)
//ó bien... for(fila=0; fila<dim1; fila++)
for(colum=0;colum<dosDimensiones[fila].length;colum++)
//ó bien... for(colum=0;colum<dim2;colum++)
····· ····· ······
ACCION( dosDimensiones[fila][colum] );
··· ····· ····· ··········
····· · · ······ ···· ······
Al estar el segundo bucle anidado dentro del primero, por cada vuelta que da el primero,
el segundo ejecuta todas sus repeticiones.
El segundo bucle repite ‘dim1 veces’ sus ‘dim2 vueltas’, ejecutando un total de ‘dim1 x
dim2 vueltas’, para las cuales los valores que tomarán la variables fila y colum serán....
1ª vuelta del bucle exterior -- 2ª vuelta del bucle exterior------------------ 3ª vuelta del bucle exterior -----------------------
fila
colum
0 0 ...
0
0 1 ... dim2-1
1
0
1
0
...
1
... .... dim1-1
... dim2-1 ... ...
0
dim1-1
1
...
...
dim1-1
dim2-1
Como podemos ver, cada vez que empieza una nueva vuelta del bucle exterior, el
contador del bucle interior colum se inicializa de nuevo a 0, ya que se ejecuta desde el
principio y ejecuta por tanto nuevamente la parte de inicialización del for interior.
Y así es como se generan todas las combinaciones de las posiciones en la tabla
bidimensional que queremos recorrer.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 93
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
No obstante, si las filas de la tabla dosDimensiones no fueran todas de igual tamaño,
para la condición... “hasta donde debe llegar el conteo de colum”, en el bucle interior,
no valdría la condición colum<dim2, al ser dim2 distinto en cada fila.
Necesariamente habría que consultar el atributo length para saber el tamaño de cada fila
(dosDimensiones[fila].length), de modo que los recorridos de cada fila se ajusten a los
tamaños de cada cual.
···········
<tipo> [][] dosDimensiones = new <tipo>[dim1][];
// sólo se crea la tabla de
// tamaño dim1 de tipo <tipo>
/* A continuación se crea cada fila. Cada una con un tamaño distinto */
for(fila=0;fila<dosDimensiones.length;fila++) // ó bien for(fila=0;fila<dim1;fila++)
dosDimensiones[fila] = new <tipo>[<distintos_tamaños_cada_vez>];
···· ···
/* Y ahora se recorre cada fila. Cada una con su tamaño */
for(fila=0;fila<dosDimensiones.length;fila++)
//ó bien for(fila=0;fila<dim1;fila++)
for(colum=0; colum<dosDimensiones[fila].length; colum++)
// colum<dim2 AQUI NO SIRVE.
ACCION( dosDimensiones[fila][colum] );
·· ····· ··· ·· ·· ·····
Al igual que en los recorridos de las tablas de una dimensión, en ocasiones nos
interesará recorrer sólo una porción de tabla y siguiendo algún orden concreto.
A continuación podemos ver algunos ejemplos:
···· ···
for(fila=1; fila<dosDimensiones.length-1; fila++)
for(colum=1; colum<dosDimensiones[fila].length-1; colum++)
ACCION( dosDimensiones[fila][colum] );
···· ···
fila
1
1
1
1
2
2
2
2
-
colum
1
2
3
4
1
2
3
4
-
···· ···
for(fila=0; fila<dosDimensiones.length; fila++)
for(colum=dosDimensiones[fila].length-3; colum>=0; colum--)
ACCION( dosDimensiones[fila][colum] );
···· ···
fila
0
0
0
0
1
1
1
1
2
2
2
2
3
3
3
3
-
colum
3
2
1
0
3
2
1
0
3
2
1
0
3
2
1
0
-
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 94
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
···· ···
for(fila=0; fila<dosDimensiones.length; fila++)
ACCION( dosDimensiones[fila][fila] );
···· ···
fila
0
1
2
3
-
colum
0
1
2
3
-
···· ···
for(colum=0; colum<dosDimensiones[fila].length; colum++)
for(fila=0; fila<dosDimensiones.length; fila++)
ACCION( dosDimensiones[fila][colum] );
···· ···
fila
3
2
1
0
3
2
1
0
3
2
1
0
3
2
1
0
3
2
1
0
3
2
1
0
-
colum
5
5
5
5
4
4
4
4
3
3
3
3
2
2
2
2
1
1
1
1
0
0
0
0
-
Recorridos de una Tabla multidimensional (con N índices)
Generalizando de los casos antes vistos, para recorrer todos y cada uno de los elementos
de una tabla de dimensión N se usará N bucles anidados unos dentro de otros, con los
que obtendremos todas la serie de combinaciones correspondientes a las posiciones
válidas.
Dependiendo del orden en que se coloquen los bucles encargados de incrementar los
distintos índices, el recorrido pasará por los elementos en orden u otro.
···········
<tipo> [][]...[] nDimensiones = new <tipo>[dim1][dim2]...[dimN];
········ ···· ·····
for(i1=0; i1<dim1; i1++) //ó bien for(i1=0; i1<nDimensiones.length;i1++)
for(i2=0; i2<dim2; i2++) //ó bien for(i2=0; i2<nDimensiones[i1].length;i2++)
···········
············
for(iN=0; iN<dimN; iN++) //ó for(iN=0;iN<nDimensiones[i1][i2]...[iN-1].length;iN++)
ACCION( nDimensiones[i1][i2]...[iN] );
·······················
Todo lo anteriormente expuesto para las tablas bidimensionales se puede extrapolar a
tablas cualquier dimensión.
En el ejemplo se ve cómo para recorrer toda la tabla se generan todas las combinaciones
posibles de los N índices, de modo que la acción se aplique a todos los elementos.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 95
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
En el siguiente ejemplo, los nombres de los alumnos de una guardería se guardan en una
tabla multidimensional, agrupados en función de la clasificación que se describe en el
comentario con el que comienza el código:
nota.- El gráfico que aparece después, describe la tabla de tres dimensiones usada en el programa para
clasificar los nombres de los alumnos.
/**
Fichero: Guarderia.java
Declara
nombres
-
una tabla de 3 dimensiones y la inicializa con los
de los niños de la guardería agrupados convenientemente.
Sexo. (0 - Niño , 1 - Niña)
Curso en el que están. (0,1,2, ó 3)
Después hace un listado de las niñas y niños de primer curso.
**/
import java.io.*;
public class Guarderia {
public static void main(String[] argumentos) {
// Declaración e inicialización de la tabla
// el 1er indice indica sexo, el 2º indica el curso,
// y el 3º la posición de los diferentes nombres dentro de cada categoria.
String[][][] alumnos={
{ {"Pepin", "Carlitos" },
{"Juanote", "Anibal","Gabriel" },
{"Eduardito"},
{"Mateo", "Chen", "Andresito" } },
//niños
//niños
//niños
//niños
{ {"Mª Pino", "Anita"},
{"Tula", "Ariadnna", "Eva", "Carolinita"},
{"Julia", "Rita"},
{"Alba", "Llanetes"} } };
de
de
de
de
curso cero
1er curso
2º curso
3er curso
//...y niñas
int sexo, curso=1,i;
System.out.println();
System.out.println("Alumnas de primer curso");
sexo=1; //niñas
for(i=0;i<alumnos[sexo][curso].length;i++)
System.out.println(' ' + alumnos[sexo][curso][i]);
System.out.println("Alumnos de primer curso");
sexo=0; //niños
for(i=0;i<alumnos[sexo][curso].length;i++)
System.out.println(' ' + alumnos[sexo][curso][i]);
}
}
En el ejemplo se puede observar cómo al expresar los valores de inicialización se crean
las estructuras tabla que se van a referenciar mediante el identificador alumnos:
•
•
•
•
los datos String.
las tabla de Strings.
las tablas de tablas de Strings.
y la tabla de tablas de tablas de Strings.
Aquí puede comprobarse cómo cada subtabla puede tener un tamaño distinto, por lo que
la condición del bucle, necesita consultar cada tamaño haciendo uso de la expresión
alumnos[sexo][nivel].length para conocer el número nombres de alumnos (es decir,
cuántos datos String) que existen para cada combinación de los índices sexo y nivel.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 96
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
La siguiente representación gráfica quizás ayude (eso espero) a entender cómo se
referencian los datos del ejemplo usando siempre como punto de partida el identificador
alumnos.
“Pepin”
“Juanote”
alumnos[0][1][0]
alumnos[0][1][1]
alumnos[0][1][2]
“Anibal”
“Gabriel”
“Eduardito”
“Mateo”
alumnos[0][3][0]
alumnos[0][3][1]
alumnos[0][3][2]
alumnos
no s
n os
n os
nos
alum ] alum ] alum ] alum ]
[1
[2
[3
[0
]
]
]
]
[0
[0
[0
[0
“Chen”
“Andresito”
“Mª Pino”
alumnos[1][0][0]
alumnos[1][0][1]
“Anita”
“Tula”
alumnos[1][1][0]
alumnos[1][1][1]
alumnos[1][1][2]
alumnos[1][1][3]
alumnos[0]
alumnos[1]
nos
n os
n os
n os
alum ] alum ] alum ] alum ]
2
0
1
][
][
][
][
[1 3
[1
[1
[1
* *
“Ariadnna”
“Julia”
alumnos[1][2][0]
alumnos[1][2][1]
“Carolinita”
“Rita”
alumnos[1][3][0]
alumnos[1][3][1]
“Alba”
“Llanetes”
N I Ñ A S
alumnos[0][2][0]
Ñ O S
“Carlitos”
N I
alumnos[0][0][0]
alumnos[0][0][1]
Si quisieramos por ejemplo, intercambiar a las alumnas del curso 2 (Julia y Rita) con
las del curso 3 (Alba y Llanetes) sólo sería necesario intercambiar las referencias que
contienen las casillas marcadas con asteriscos del siguiente modo...
···········
// Algoritmo de INTERCAMBIO (el mismo de siempre)
String [] auxiliar; // identificador auxiliar para no perder las referencias.
auxiliar= alumnos[1][2];
alumnos[1][2] = alumnos[1][3];
alumnos[1][3] = auxiliar;
/* Nótese que puesto
tabla de Strings,
auxiliar usado en
····· ···
·· · ···
que lo que se intercambia son las referencias a estructuras
éste será el tipo usado para declarar el identificador
el intercambio */
···
...para intercambiar sus contenidos y quedando las estructuras de este modo:
* *
nos
nos
nos
n os
alum ] alum ] alum ] alum ]
[1][3
[1][1
[1][0
[1][2
alumnos[1][3][0]
alumnos[1][3][1]
“Julia”
“Rita”
alumnos[1][2][0]
alumnos[1][2][1]
“Alba”
“Llanetes”
Esto es una muestra de cómo las tabla pueden ser una forma de clasificar datos (no sólo
de almacenarlos) en función del significado que se le atribuya a cada uno de sus índices.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 97
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Algoritmos de búsqueda
Un problema clásico al manejar secuencias de datos, consiste en comprobar si un dato
está presente y en tal caso localizar su posición (si está repetido, sólo vamos a
considerar su primera aparición).
nota.- Si lo que necesitasemos fuese encontrar un elemento repetidas veces, simplemente se trataría de
hacer un recorrido donde la acción consistiera en contabilizarlo, o mostrar las posiciones en las que
lo encontramos, o lo que fuese preciso en cada caso.
Para resolverlo, debemos distinguir dos posibles situaciones: que los datos no estén
ordenados, o que si lo estén.
Tablas no ordenadas
Iniciaremos un recorrido de la tabla que finalizará en el momento en que el dato sea
encontrado.
Sobre una tabla de una dimensión:
···········
i=0;
while(i<tabla.length-1 && tabla[i] != DATO_BUSCADO)
i++;
if(tabla[i] == DATO_BUSCADO)
SI
NO
··· ···· ····
// el elemento Está.
····· ··· ···
·· ·· ··· ···
// el elemento No está.
··· ·· ··· ···
······ ····· ······ ·· ···
Es interesante observar la condición del bucle con que se recorren los elementos de la
tabla y que es cierta mientras que...
... no se encuentre el dato buscado.
... y el índice i no haya alcanzado la posición del último elemento.
La condición típica de cualquier recorrido hubiera sido:
... i<tabla.length...
( o bien i<=tabla.length-1 )
Sin embargo aquí el bucle finaliza antes, para evitar que al terminar el bucle, i pueda
sobrepasar la última posición válida de la tabla.
Será después (al salir del bucle) cuando se compruebe si el índice i señala o no el dato
buscado.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 98
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Tablas de más de una dimensión
Aplicado al caso genérico de una tabla de N dimensiones, la solución sigue siendo
recorrer la tabla hasta encontrarlo. Sólo que ciertamente resulta más simple haciendo
uso de una sentencia de ruptura de control break, aunque ello haga que el código no sea
puramente estructurado.
···········
salir:
for(i1=0; i1<nDimensiones.length;i1++)
for(i2=0; i2<nDimensiones[i1].length;i2++)
···········
············
for(iN=0; iN< nDimensiones[i1][i2]...[N-1]. length;iN++)
if(nDimensiones[i1][i2]...[iN] == DATO_BUSCADO)
break salir;
if(i1
//
//
else
//
< nDimensiones.length)
Está: porque habrá salido del 1er bucle for cuando aún es cierta su condición
la posición la señalan los índices (i1,i2, ... ,iN).
No está.
·······················
Por razones obvias, no es normal que atribuir orden al contenido de una tabla de varias
dimensiones, puesto que habría que concretar en qué orden hay que recorrer los
elementos ( por filas, por columnas, ...) para encontrar orden en su contenido.
En esta tabla de numeros enteros y de dos dimensiones los elemento
están ordenados si se recorren por columnas, (1,2,3,4,5,6,7,8,9) pero si se
recorren por filas como hemos hecho en los ejemplos anteriores, sus
elementos no están ordenados (1,4,7,2,5,8,3,6,9).
Autor : FernandoToboso Lara
e-mail: [email protected]
1 4 7
2 5 8
3 6 9
Pag. 99
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Tablas ordenadas
En el supuesto de que nuestra tabla lineal (unidimensional) contenga datos ordenables y
suponiendo que efectivamente estén ordenados hay alternativas más eficaces de
búsqueda.
nota.- No todos los datos son ordenables, piensa por ejemplo en los vectores, los colores, las ciudades y
otros muchos tipos de información...
Supongamos una tabla con los elementos ordenados de menor a mayor, una primera
mejora elemental consistirá en abandonar la búsqueda si encontramos un dato que sea
mayor que el buscado (de existir, ya hubiéramos debido encontrarlo).
···········
i=0;
while(i<tabla.length-1 && tabla[i]
<
DATO_BUSCADO)
i++;
if(tabla[i] == DATO_BUSCADO)
SI
NO
····· ·····
// el elemento Está.
··· ·· ··· ··
····· ·· ···
// el elemento No está.
····· ···· ······
······· ····· ············ ··
nota.- A continuación en todos los casos se supondrá que los datos se ordenan de menor a mayor. Para
que el orden sea decreciente, simplemente habrá que invertir los operadores relacionales.
búsqueda dicotómica (o binaria)
Pensemos en la situación de buscar la página 352 en un libro.
Si éste tiene los números de página impresos en cada una de las hojas, dudo que a
alguien se le ocurriese pasar las páginas una por una, hasta alcanzar la deseada.
Probablemente probaría una página al azar aproximadamente por la mitad del libro y
después repetiría la búsqueda con la primera mitad si la que busca esta antes, o bien con
la segunda mitad en caso contrario.
Esta misma estrategia aplicada a una tabla ordenada es la usada en el algoritmo de
búsqueda binaria, también llamado de búsqueda dicotómica.
···········
int izq=0, der=tabla.length-1;
int medio=(der+izq)/2;
while(tabla[medio]!=DATO_BUSCADO && izq<der) {
if(DATO_BUSCADO<tabla[medio])
der=medio-1;
else
izq=medio+1;
medio=(der+izq)/2;
}
if(tabla[medio]==DATO_BUSCADO)
// Está en la posición que indica medio
. . .. ..
else
// No está.
.. . ..
················
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 100
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
El resultado de este algoritmo
es mucho más eficaz que el
anterior.
unaDimension
DATO BUSCADO: 66
-87 -2 -1 10 17 29 50 66 82
La clave está en que por cada
intento, desestima la mitad de
los elementos de la parte de
tabla en la que ha buscado
(desde izq hasta der).
Primera
izq
medio
Segunda
Tercera
der
i
m
d
im d
Algoritmos de ordenación
La acción de ordenar una serie de datos no es una acción elemental. Cuando una
persona realiza una ordenación, a menudo no se para a pensar en la forma en que actúa
y es sorprendente la cantidad de comparaciones que se realizan de un sólo vistazo y las
estrategias más o menos rebuscadas que utiliza hasta dar con la solución.
Para un programa informático la cosa es diferente. Solamente es capaz de decidir cuál
de entre dos datos es el menor y cuál el mayor o si ambos son iguales, usando para ello
una expresión de tipo boolean que los compare.
Para que realice la operación de ordenar una tabla de datos es necesario especificar
detalladamente todas y cada una de las instrucciones que realizará y puesto que siempre
seguirá escrupulosamente ese procedimiento, será mejor que se analicen algunos
algoritmos que podemos implementar para elegir el que más convenga.
Por ahora se estudiarán los algoritmos conocidos como:
- Selección Directa.
- Inserción Directa.
- Intercambio Directo (o de la Burbuja).
Y alguna de sus variantes que mejora los resultados de tiempo necesario para ordenar
una cantidad de datos N.
Otros algoritmos de ordenación que podemos mencionar (pero que no trataremos aquí
por su mayor grado de dificultad) son Quicksort, Shell, RadixSort, etc... , además las
respectivas variaciones que de cada uno de ellos se han escrito.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 101
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Selección directa
Se recorren las casillas desde el principio para ir poco a poco colocando en cada una el
dato que le corresponde.
Para cada casilla se selecciona el menor de entre los elementos que queden por situar y
una vez hallado se intercambia por el que actualmente ocupa el sitio que le corresponde.
Elementos
aún no
colocados
·············· ·····
<tipo>[] tabla = new <tipo> [<tamaño>];
int min,i,j;
<tipo> aux;
······ ···
·····
//LECTURA DE DATOS EN LA TABLA
······ ···
·····
// Recorre las casilla para las que
// buscamos el elemento menor
-2* 17 -2
Primera
for(i=0; i<tabla.length-1; i++)
-3 17 -2 -2* 69 50
// Busca el minimo desde la
// posicion i hasta el final
min=i;
Segunda
for(j=i+1; j<tabla.length; j++)
Tercera
-3 -2 17 -2* 69 50
if(tabla[j]<tabla[min])
SI
NO
min=j;
-3 -2 -2* 17 69 50
Cuarta
-3 -2 -2* 17 69 50
//nada
// Intercambia
aux=tabla[i];
tabla[i]=tabla[min];
tabla[min]=aux;
-3 69 50
Quinta
-3 -2 -2* 17 50 69
·········· ······ ···
Mientras con un bucle se recorren las casillas de la tabla, con otro anidado inicia
simultáneamente un recorrido a partir de la actual casilla del bucle externo, para
buscar el dato que deberá colocarse en la casilla que indica dicho bucle externo.
El bucle que incrementa el índice i, recorre sólo hasta el penúltimo elemento, puesto
que evidentemente cuando todas las casillas menos una tengan el elemento que les
corresponde según la secuencia ordenada, la última necesariamente también lo tendrá.
En éste como en el resto de los ejemplos que se van a analizar nos limitaremos al caso
de ordenar las tablas de manera creciente.
Para invertir el orden de los datos, basta con modificar el código correspondiente al
criterio de ordenación. En nuestro ejemplo...
//Ordenación creciente
· ···· ··
if(tabla[j] < tabla[min])
min=j;
··· ···· ··
//Ordenación decreciente
· ···· ··
if(tabla[j] > tabla[min])
max=j;
··· ···· ··
...de manera que se seleccionen los datos mayores para las primeras posiciones.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 102
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Respecto a los criterios de ordenación en java (tanto para este algoritmo como para los
que después se verán) es fundamental tener en cuenta que los operadores de
comparación de los tipos primitivos no sirven para comparar datos de tipos
referenciados.
- Los operadores < , > , <= , >= entre datos referenciados producirán un
error de sintaxis al intentar compilar el código.
- Los operadores == , != aunque son válidos darán resultados que quizás no
esperemos, ya que lo que realmente van a comparar son las referencias a los
datos y no los propios datos.
· ···· ··
String nombre_1 = “Fermín Referenciadéz Peréz”;
String nombre_2 = “Fermín Referenciadéz Peréz”;
String nombre_3;
float altura_1 = 1.56;
float altura_2; = 1.78;
nombre_3 = nombre_2;
..... .... ..
Fermín Referenciadéz Peréz
Fermín Referenciadéz Peréz
nombre_1
nombre_2
altura_1
1.56
Expresión
Resultado
nombre_1 != nombre_2
nombre_1 == nombre_2
nombre_2 != nombre_3
nombre_2 == nombre_3
true
false
false
true
nombre_3
altura_2
1.78
Los datos de tipos referenciados ordenables suelen disponer de un método llamado
compareTo(...) con el que pueden ser correctamente comparados entre sí.
Este método da como resultado un valor tipo int, cuyos significados son:
<UnDato>.compareTo(<OtroDato>)
< 0 si UnDato es menor que OtroDato
0 si UnDato es igual que
OtroDato
> 0 si UnDato es mayor que OtroDato
Veamos a través de un ejemplo, la ordenación de una tabla de datos String según dos
criterios de ordenación distintos...
// Fichero: OrdenaSeleccionDirectaTablasStrings.java
public class OrdenaSeleccionDirectaTablasStrings {
static void main (String[]arg){
String [] nombres= { "Angel","Ana","Brito",
"Alberto","Celeste","Pablo",
"Paula","Alfonso","Almudena"};
int i,j,sel;
String auxNombre;
for (i=0;i<nombres.length-1;i++) {
sel=i;
for (j=i+1;j<nombres.length;j++)
if(CRITERIO_DE_ORDENACIÓN)
sel=j;
auxNombre=nombres[i];
nombres[i]=nombres[sel];
nombres[sel]=auxNombre;
}
System.out.println("Nombres ordenados.");
System.out.println("-----------------");
for (i=0;i<nombres.length;i++)
System.out.println (nombres[i]);
}
}
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 103
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Dependiendo del <CRITERIO_DE_ORDENACIÓN> utilizado en la sentencia if ,
la ordenación será...
..... ...... .. ..
if(nombres[j].compareTo(nombres[sel])<0)
........ .. ..
... . .....
Nombres ordenados.
----------------Alberto
Alfonso
Almudena
Ana
Angel
Brito
Celeste
Pablo
Paula
(ALFABETICAMENTE)
...ésta, si se comparan los datos String
alfabéticamente...
..... ...... .. ..
if(nombres[j].length()<nombres[sel].length())
........ .. ..
... . .....
Nombres ordenados.
----------------Ana
Angel
Brito
Pablo
Paula
Alberto
Celeste
Alfonso
Almudena
(SEGÚN SU LONGITUD)
...o bien ésta, si se comparan las longitudes
de los datos String...
O cualquier otra, dependiendo del criterio por el que decidamos ordenar los datos.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 104
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Inserción directa
Se comienza presuponiendo que existe un fragmento de tabla cuyos elementos ya están
ordenados...¡concretamente y con seguridad está ordenado los primeros UN elementos!
Se recorren por lo tanto desde el segundo todos los elementos del resto de la tabla y para
cada uno de ellos se busca la posición en la que deben ir colocados dentro del fragmento
ordenado.
Para poderlos insertar, todos los elementos de la parte ordenada que queden detrás del
nuevo se desplazan una posición ocupando la casilla del que pasa a la parte ordenada.
La tabla quedará ordenada cuando se inserten todos los elementos y el fragmento
ordenado sea la tabla completa.
·········
int i,j;
<tipo>[] tabla = new <tipo>[<tamaño>];
<tipo> aux;
······ ···
·····
-3 10 -2*
17 50 -2
-3 10 -2*
Primera
//LECTURA DE DATOS EN LA TABLA
······ ···
·····
Segunda
for(i=1; i<tabla.length; i++)
-2 17 50 -3 10 -2*
aux=tabla[i];
j=i-1;
Tercera
-3 -2 17 50 10 -2*
// Abre hueco hacia atrás hasta
// encontrar el sitio de aux, o
// alcanzar el principio
Cuarta
-3 -2 10 17 50 -2*
while(tabla[j]>aux && j>0)
tabla[j+1]=tabla[j];
j=j-1;
Quinta
-3 -2 -2* 10 17 50
// Si aux va el primero de todos
// hay que hacer un hueco más, ...
if(tabla[j]>aux)
SI
50 17 -2
NO
//si no, asigna aux
tabla[j+1]=tabla[j];
//al hueco ya abierto.
tabla[j]=aux;
tabla[j+1]=aux;
El bucle externo recorre los
elementos que vamos a insertar.
·· · ····· ······
Cada uno de estos elementos, se reserva temporalmente en la variable aux.
A continuación se inicia un recorrido inverso (hacia atrás usando el índice j) del
fragmento ya ordenado desplazando sus elementos para ir abriendo hueco hasta
encontrar la posición que corresponde al elemento que se insertará (o bien hasta llegar al
principio).
A cada vuelta del bucle for el fragmento de tabla ordenada crece con el nuevo elemento
insertado.
Cuando se inserta el último, toda la tabla queda ordenada.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 105
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Si al ir buscando el hueco de un elemento se alcanza el principio ( j vale 0 ), el bucle
while finaliza (al no ser j mayor que 0 ).
Esto se hace para evitar que se vuelva a decrementar j alcanzando valor -1 , lo cual haría
que al volver a evaluase la condición del bucle se intentase consultar una posición
negativa de la tabla (que sería evidentemente erróneo).
··· ·· · ····
·· ····· ··· ······
while(tabla[-1]>aux ....
······· ··· ······
// ERROR
A pesar de esto, bien pudiera ocurrir que fuese necesario desplazar también el primer
elemento de la tabla (caso de que aún sea cierta la expresión tabla[j]>aux ), para dejar
libre el primer hueco, si este es el que corresponde al dato que se va a colocar.
Por esto al salir del bucle while la sentencia if hace que se trate por separado este caso.
En algunos lenguajes (como java) es posible una mejor implementación, aprovechando
la forma de funcionar del operador && ( operador Y de corte).
Recordemos que este operador no evalúa toda la expresión si la primera parte (al ser
false) obliga a que el resultado de la expresión sea necesariamente false:
·········
int i,j;
<tipo>[] tabla = new <tipo>[<tamaño>];
<tipo> aux;
······ ···
·····
//LECTURA DE DATOS EN LA TABLA
······ ···
·····
for(i=1; i<tabla.length; i++)
Y por tanto nunca se intentará evaluar la
expresión errónea tabla[-1].
aux=tabla[i];
j=i-1;
// Abre hueco hacia atrás hasta
// encontrar el sitio de aux, o
// alcanzar el principio
while(j>=0 && tabla[j]>aux)
tabla[j+1]=tabla[j];
j=j-1;
Tal como se ha escrito la expresión del
bucle ahora, si j valiese -1, como la
primera parte de la expresión ( j>=0 ) es
false el resto no se evaluaría (puesto que
la expresión completa va a ser false,
valga lo que valga la parte restante).
Sin embargo no todos los lenguajes
disponen de este operador específico,
muchos de ellos evalúan sus expresiones
completas, siempre.
// Se asigna el dato de aux
// al hueco ya abierto.
tabla[j+1]=aux;
·· · ····· ······
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 106
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Una característica destacable es el hecho de que cuando la tabla posee datos iguales,
este algoritmo no los intercambia en ningún momento respetando así su orden relativo,
al contrario que de lo hubiese hecho el algoritmo de selección directa.
Por ejemplo, supóngase que disponemos de dos tablas cuyas casillas de contienen los
datos -edad y nombre- de unas personas almacenados en similar posición.
EDADES
23
65
23
65
12
23
..
...
NOMBRES
Casilda Monilda Palenque
Felipe de los Santos Mascado
Froilán Churruquez Palmitas
Jacinta Cortés Cheng
Juanito Badana Pelana
Tatiana Pilingui Burrez
.... ....... ... . . . . .
. . . . . . . . ... .. .. .....
Es decir, la casillas con igual posición en ambas tablas contienen los datos de la una
misma persona.
Y supóngase asimismo que están previamente ordenadas según el dato nombre.
Si a continuación se ordenan (manteniendo los datos asociados en la misma posición)
según la edad...
...con este algoritmo los datos con igual
edad mantendrían el orden relativo en
sus nombres.
EDADES
12
23
23
23
65
65
..
...
...usando selección directa es posible
que nos encontramos con este otro
resultado.
NOMBRES
Juanito Badana Pelana
Casilda Monilda Palenque
Froilán Churruquez Palmitas
Tatiana Pilingui Burrez
Felipe de los Santos Mascado
Jacinta Cortés Cheng
.... ....... ... . . . . .
. . . . . . . . ... .. .. .....
EDADES
12
23
23
23
65
65
..
...
.
NOMBRES
Juanito Badana Pelana
Tatiana Pilingui Burrez
Casilda Monilda Palenque
Froilán Churruquez Palmitas
Felipe de los Santos Mascado
Jacinta Cortés Cheng
.... ....... ... . . . . .
. . . . . . . . ... .. .. .....
.
Podremos ver este ejemplo a continuación implementado con el siguiente algoritmo que
trataremos y que también respeta el orden relativo de los datos de igual valor.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 107
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Intercambio directo (de la burbuja)
Recibe el apodo de algoritmo de la burbuja porque su estrategia se parece al
comportamiento de una burbuja.
Consiste en realizar sucesivos recorridos en la tabla comparando cada elemento con el
siguiente, si el orden entre ellos no es el deseado se intercambian.
En cada recorrido se consigue arrastrar hasta el final el valor mayor de todos (seguimos
ordenando de menor a mayor) por lo que cada recorrido será más corto que el anterior.
El último recorrido realizará sólo una iteración comparando el primero con el siguiente
(lo otros ya habrán alcanzado su puesto) para dejar la tabla finalmente ordenada.
·········
int i,j;
<tipo>[] tabla = new <tipo>[<tamaño>];
<tipo> aux;
······ ···
·····
//LECTURA DE DATOS EN LA TABLA
······ ···
·····
Una burbuja de aire en el agua asciende
porque el espacio que ella ocupa,
lo desocupa el agua que tenía
encima (baja el agua y sube el aire
intercambiándose.
for(i=2; i<=tabla.length; i++)
for(j=0;j<=tabla.length-i; j++)
if(tabla[j] > tabla[j+1])
SI
NO
aux=tabla[j];
tabla[j]=tabla[j+1];
tabla[j+1]=aux;
De igual forma en cada recorrido que el
algoritmo realiza sobre la tabla el
elemento mayor (de entre los no
situados) alcanza su posición final
N
A
D
A
·· ···· ····· ·······
50
-3
-2*
17
17
17
17
17
-3
-2*
10
10
10
-2
17
-3
-2*
-2*
-2*
10
-2
10
-3
-2
-2
50
10
-2
-2
-3
-3
Quinta
50
Cuarta
50
Tercera
50
Segunda
50
Primera
-2*
La eficacia de este algoritmo se apoya en el hecho de que en cada recorrido algunos
elementos se acercan poco a poco a su posición de orden (véase el avance del 10 en el
segundo recorrido), por lo cual suele suceder que el algoritmo a menudo ordena los
elementos en menos pasadas de las imprescindibles.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 108
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Un pequeño cambio en este algoritmo para aprovechar esta circunstancia, mejorará su
eficacia sustancialmente.
Si en un recorrido se detecta que no se ha producido ningún intercambio, será porque
todos los elementos están ya ordenados y por lo tanto no será necesario seguir haciendo
recorridos.
·········
int i,j;
<tipo>[] tabla = new <tipo>[<tamaño>];
<tipo> aux;
// Inicializado a true para que entre
// dentro del bucle externo la primera vez.
boolean hubo_intercambio =true;
······ ···
·····
//LECTURA DE DATOS EN LA TABLA
······ ···
·····
for(i=2; i<=tabla.length && hubo_intercambio; i++)
// Al empezar cada recorrido
hubo_intercambio=false;
for(j=0;j<=tabla.length-i; j++)
if(tabla[j] > tabla[j+1])
SI
NO
aux=tabla[j];
tabla[j]=tabla[j+1];
tabla[j+1]=aux;
// Sólo si hay algún intercambio
hubo_intercambio =true;
N
A
D
A
·· ···· ····· ·······
La variable hubo_intercambio hace de centinela (señal, bandera...). Al principio de cada
recorrido se inicializa a false y sólo se cambiará de valor a true en caso de que se
produzca algún intercambio, lo cual sería señal de que la tabla aún no está ordenada.
Si al finalizar uno de los recorridos, el valor de hubo_intercambio continúa siendo false
será indicativo de que en el último recorrido ya estaban todos los elementos ordenados.
por lo cual el bucle for externo dejará de iterar, dando por concluida la ordenación.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 109
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Algoritmo de la sacudida (variante de la burbuja)
La mejora aplicada anteriormente sobre el algoritmo de la burbuja, tiene un caso que
podríamos calificar como “... el peor caso con el que se puede enfrentar”, es el caso de
que estando la tabla casi ordenada, se necesiten hacer todos los recorridos para
ordenarla. Un ejemplo sería el siguiente caso:
t [0]
t [1]
t [2]
t [3]
t [4]
t [5]
t [6]
t [7]
t [8]
t [9]
t [10]
t [11]
-34
-12
-5
8
12
15
15
18
23
34
65
-40
En cada recorrido se lleva el mayor elemento hasta el final, pero el -40 que en este caso
es el único dato fuera de orden, sólo consigue acercarse una posición hacia su sitio por
cada vuelta.
Recorridos para ordenar la tabla
1º
-34
-12
-5
8
12
15
15
18
23
34
-40
65
2º
-34
-12
-5
8
12
15
15
18
23
-40
34
65
3º
-34
-12
-5
8
12
15
15
18
-40
23
34
65
4º
-34
-12
-5
8
12
15
15
-40
18
23
34
65
····
····
····
····
····
····
····
····
····
····
····
····
10º
-34
-40
-12
-5
8
12
15
15
18
23
34
65
11º
-40
-34
-12
-5
8
12
15
15
18
23
34
65
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 110
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
La variante de la sacudida aplica otra mejora pensada para estos casos, que consiste en
hacer los recorridos alternando el sentido de las pasadas (una hacia el final arrastrando
el elemento mayor al final y la siguiente en sentido opuesto arrastrando el más pequeño
hasta el principio.
·········
int ini, fin, i;
<tipo>[] tabla = new <tipo>[<tamaño>];
<tipo> aux;
boolean hubo_intercambio =true;
······ ···
·····
//LECTURA DE DATOS EN LA TABLA
······ ···
·····
ini=0;
fin=tabla.length-1;
while(ini<fin && hubo_intercambio)
// Comienza el recorrido de inicio al fin
hubo_intercambio=false;
for(i=ini; i<fin; i++)
if(tabla[i] > tabla[i+1])
SI
NO
aux=tabla[i];
tabla[i]=tabla[i+1];
tabla[i+1]=aux;
hubo_intercambio =true;
N
A
D
A
// fin de recorrido de inicio a fin
// decrementa marca de final, se arrastró el mayor hasta
// su posicion y ya no será necesario modificarlo.
fin--;
if(hubo_intercambio)
SI
NO
// será porque aún no se terminó la ordenación
// Empieza recorrido inverso
hubo_intercambio=false;
for(i=fin-1; i>=ini; i--)
if(tabla[i] > tabla[i+1])
SI
NO
aux=tabla[i];
tabla[i]=tabla[i+1];
tabla[i+1]=aux;
hubo_intercambio =true;
N
A
D
A
N
A
D
A
// fin de recorrido inverso, de fin hasta ini
// incrementa marca de inicio, pues ya se
// arrastró el menor hasta su posicion.
ini++;
·· ···· ····· ·······
En el siguiente ejemplo podemos ver este algoritmo implementado para ordenar
simultáneamente dos tablas correspondientes a una serie de nombres y las edades que a
cada nombre corresponden.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 111
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
/** Primero pide que se le introduzca las edades de las personas,
cuyos nombre se van especificando, para luego mostrarlos ordenados
según la edad. **/
import java.io.*;
50 17
}
}
2
i
0
“Z
ai d
a”
“D
ie g
o”
“C
a
oco
”
nt i
“A
“A
n
aR
o sa
”
// Algoritmo de la Sacudida.
int ini=0,fin=edades.length-1;
boolean hubo_intercambio = true;
int auxEdad;
String auxNombre;
while(ini<fin && hubo_intercambio) {
hubo_intercambio=false;
for (i=ini; i<fin; i++)
if (edades[i]>edades[i+1]) {
// intercambio de la tabla edades
auxEdad=edades[i];
edades[i]=edades[i+1];
edades[i+1]=auxEdad;
// intercambio de la tabla nombres
auxNombre=nombres[i];
nombres[i]= nombres[i+1];
nombres[i+1]=auxNombre;
ini
hubo_intercambio=true;
}
0
// fin del recorrido de ini hasta fin
fin--; // decrementa marca de final, se ha arrastrado el mayor hasta
// su posicion y ya no sera necesario modificarlo.
if(hubo_intercambio) {
// Como aun no esta ordenada, comienza recorrido hacia atras
hubo_intercambio=false;
for (i=fin-1; i>=ini; i--)
if (edades[i]>edades[i+1]) {
// intercambio de la tabla edades
auxEdad=edades[i];
edades[i]=edades[i+1];
edades[i+1]=auxEdad;
// intercambio de la tabla nombres
auxNombre=nombres[i];
nombres[i]= nombres[i+1];
nombres[i+1]=auxNombre;
hubo_intercambio=true;
}
// fin del recorrido de fin hasta ini
ini++; // incrementa marca de inicio, se ha llevado el menor hasta
// su posicion y ya no sera necesario modificarlo.
} // fin de if
} // fin del while
r lo
ta ”
class OrdenaEdades {
public static void main(String[] argumentos) throws IOException {
int[] edades=new int[5];
String[] nombres={"Ana Rosa","Antioco","Carlota","Diego","Zaida"};
int i;
for(i=0;i<nombres.length;i++) {
// Pide las edades de las 5 personas.
System.out.print("Que edad tiene "+nombres[i]+' ');
// Lee y guarda la edad, en la posición i-ésima de la tabla edades
edades[i]=Entrada.readInt();
if(edades[i]<0)
// Lanza excepción para que el programa termine con error
// ya que ninguna sentencia se ocupa de tratar la excepcion.
throw new IOException("Sólo se admiten numeros positivos");
}
10
1
fin
4
// Listando los contenidos de las tablas ...nombre...edad.
// agrupados por bloques de igual edad.
int anteriorEdad=-999; // para que la primera vez entre en el 'if'
for(i=0;i<nombres.length;i++) {
if(edades[i]!=anteriorEdad) {
Salida.println("__ Con "+edades[i]+" años. __________ ");
anteriorEdad=edades[i];
}
Salida.println("
- " + nombres[i]);
}
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 112
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Capitulo 9
Modularidad
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 113
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Subprogramas (Funciones)
Un programa tal como lo conocemos (a esta altura del curso) es un bloque de
instrucciones que se ejecutan desde la primera a la última incluyendo expresiones,
sentencias declarativas y sentencias de control.
Esto era lo habitual en los primeros tiempos de la informática, en los que predominába
un estilo de programación que después se denominó programación monolítica.
Hoy en día esta estrategia es impensable para hacer una aplicación con un mínimo nivel
de complejidad.
Lo más sensato con un problema grande es resolverlo por partes, es decir dividiéndolo
en problemas más manejables. Este enfoque da lugar, a la implantación de una
metodología llamada programación modular que se caracteriza, como ya comentamos,
por el uso de subprogramas.
Recuerda que en el ciclo de vida de una aplicación existen una serie de fases de análisis
previas al momento que se realiza su codificación. En ellas se analizan las necesidades
que se desean cubrir, el modo en que el trabajo se va a llevar a cabo y una serie de
tareas que darán como resultado (en caso de que el proyecto resulte viable) la
especificación detallada de todas las partes de que constará la aplicación, qué hará
cada una de ellas y cómo deben interactuar entre sí, así como otros muchos
documentos descriptivos que servirán de guía para el programador.
En la fase de codificación (que es la que nos ocupa) se centra la atención en cada una de
las partes analizadas, hasta ir consiguiendo que cada una de ellas haga correctamente la
tarea que para ella se haya especificado.
A cada uno de estos fragmentos de la aplicación lo llamaremos indistintamente función,
subprograma, o método y poseerá en esencia las mismas características que un
programa completo, sólo que al ser más pequeño, será más sencillo y manejable.
Pongamos un ejemplo: - para hallar el número de combinaciones distintas de formar
grupos de A elementos tomándolos de entre un conjunto de B elementos (donde B tiene
que ser obviamente mayor o igual que A), se aplica la formula matemática del número
combinacional:
Factorial ( A)
A ⋅ ( A − 1) ⋅ ( A − 2) ⋅ ... ⋅ 1
 A
=
=
 =
[
(
)
(
)
⋅
−
⋅
(
−
1
)
⋅
... ⋅1] ⋅ [( A − B ) ⋅ ( A − B − 1) ⋅ ... ⋅ 1]
B
Factorial
B
Factorial
A
B
B
B
 
=
A ⋅ ( A − 1) ⋅ ( A − 2) ⋅ ... ⋅ ( A − B + 1)
[B ⋅ ( B − 1) ⋅ ... ⋅1]
En la que se utiliza el concepto de factorial, que queda definida por la fórmula:
si n ≠ 0 ⇒ n ⋅ ( n − 1) ⋅ ( n − 2) ⋅ K ⋅ 1
Factorial ( n ) = 
 si n = 0 ⇒ 1
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 114
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Un programa que tuviese que hacer estos cálculos podría aplicar directamente el final de
la fórmula donde los cálculos están simplificados, de hecho este caso es tan simple que
probablemente fuese la mejor elección.
Sin embargo, otra posibilidad más clara sería optar por utilizar la fórmula inicial
resolviendo el problema en dos fases:
1- Desarrollar un subprograma (que llamaremos Factorial) que calcule el factorial de
un número cualquiera que le sea dado. Un problema más elemental y sencillo.
2- Implementar el programa, que aplicando la fórmula con factoriales, solicite al
subprograma Factorial que le calcule: - el factorial del valor A.
- el factorial del valor B.
- y el factorial del valor A-B.
Solución Monolítica
Solución Modular (con Subprogramas)
Combinacional(A,B) {
long acumNum=1, acumDenom=1;
int cont;
long solucion;
for(cont=A; cont > A-B; cont--)
acumNum *= cont;
for(cont=B; cont > 1; cont--)
acumDenom *= cont;
solucion = acumNum / acumDenom ;
RESULTADO solucion;
}
Combinacional(A,B) {
long Num, Denom;
long solucion;
Num=Factorial(A);
Denom=Factorial(B)*Factorial(A-B);
solucion = Num / Denom ;
RESULTADO solucion;
}
Factorial(N) {
long acumSoluc=1;
int cont;
for(cont=N; cont > 1; cont--)
acumSoluc *= cont;
RESULTADO acumSoluc;
Estos subprogramas son en sí mismos programas que se ocupan de una porción del
problema, pero de entre todos ellos sólo uno es llamado principal y tiene como
característica diferenciadora la de ser el primero que se ejecuta, de manera que sus
instrucciones invocarán para que se ejecuten (en su caso) las sentencias de los demás
subprogramas realizando entre todos y de manera coordinada la tarea especificada para
aplicación completa.
Podemos dar una primera definición de función (subprograma) diciendo que es una
agrupación de instrucciones a las que se ha asociado un identificador a través del cual
se pueden ejecutar conjuntamente cada vez que se requiere.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 115
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Una función...
... pueden recibir (cuando se solicita su ejecución) los datos iniciales que precise
para realizar la tarea que se le encomienda,
... también puede generar un dato resultante como si de una función matemática se
tratase. En estos casos la solicitud de ejecución de la función se puede utilizar
dentro de una expresión, de manera que su resultado pasará a formar parte de la
evaluación de la expresión participando como un dato más.
nota.- Una función es a todos los efectos una sentencia de tipo acción...
... si genera un resultado, podrá ser una expresión.
... si no, una sentencia independiente.
En la siguiente sentencia perteneciente a la función Combinacional se ejecutan dos
veces las instrucciones que la función Factorial agrupa, pero en cada ocasión el
resultado es distinto porque los datos iniciales que se le pasan son diferentes: la primera
vez recibe B , y la otra A-B.
·········
Denom = Factorial(B) * Factorial(A-B);
····· ·····
Los factoriales respectivos resultantes se multiplican dando como resultado el valor del
denominador de la fórmula, un número que se asigna a la variable Denom, en la que se
almacena temporalmente para los siguientes cálculos de las siguientes líneas.
Ésta es aproximadamente la sintaxis que describe como se codifica una función en java:
<otros_modif> static <tipo_dato_resultado> <identif_función> (<datos_iniciales>) {
·· ·· ····· ···
<instruciones_de_la_funcion> ;
·· · ··· ··· ··
return <dato_resultante>;
}
// Conviene que sea la última sentencia para
// (aunque esto no es obligatorio)
Sintaxis
- La primera parte (primera línea) se conoce como declaración, o cabecera de la
función. Allí podemos entcontrar el formáto que describe cómo llamarla: su nombre,
los datos iniciales y el posible resultado de su ejecución.
- El resto (entre llaves) cuerpo de la función y en él se sitúan las sentencias que se
ejecutan.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 116
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Si se observa con detenimiento la sintaxis de una función se comprobará que en todos
los programas que hemos realizado hasta ahora, aparece una línea que encaja con lo
descrito como declaración o cabecera de una función.
public static void main ( String[] argumentos ) {
·· ··· ······ ········
}
Del mismo modo que el resto puede ser tomado por el cuerpo de dicha función.
public static void main ( String[] argumentos ) {
·· ··· ······ ········
}
.
Esto no es casual, ya hemos comentado que de entre las funciones de un programa una
es la principal y en java esa función se llama main.
De hecho, todas las sentencias hasta ahora utilizadas en los ejemplos de los programas
realizados, tienen idéntica validez para ser utilizadas en el cuerpo de cualquier
función, sea o no la principal.
Sentencia return
- La palabra return, siguiendo con el análisis de la sintaxis expuesta, pertenece a la
categoría de sentencias de ruptura de control (que en su momento solamente se
mencionó). Interrumpe inmediatamente la ejecución del subprograma en el que se
ejecuta.
Además el resultado de la expresión que acompañe a la palabra return, será lo que la
función retorne como resultado (si es que la función tiene resultado).
Generalmente va al final de las funciones, con el único fin de especificar el resultado
que retorna la función y no para romper la secuencia de ejecución.
Si la función no tiene que retornar resultado (si es void), no será necesario escribir la
sentencia return. A no ser que se la utilice como ruptura de control, sin acompañarla
de dato_resultante.
nota.- Recuérde, que para que el código sea estructurado, no deberían utilizarse sentencias de ruptura de
control y que en todo caso no se debe abusar de su uso.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 117
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
En estos ejemplos, se muestra cómo es el código de una función (distinta de la
principal).
En el primer caso sin resultado y en el otro con un resultado de tipo char.
// Fichero: Separador.java
// Escribe una línea separadora de guiones.
public class Separador {
// función Principal
static void main(String[] argumentos) {
System.out.println(" ENCIMA de la raya");
separar();
System.out.println(" DEBAJO de la raya");
}
// otra función
static void separar() {
System.out.println("---------------------");
}
}
Este otro programa
utiliza la función
leerLetra()
para
asegurarse de que
sólo
recibirá
caracteres
alfabéticos.
La función se
encargará de leer
caracteres de la
entrada estándar y
desestimar todos
los que no sean
letras.
Desde la función principal
main(...), se invoca a la
función separar() para que
ejecute sus sentencias (en
este caso su única sentencia).
La invocación a la función
parecería una sentecia más
si no fuese por los
paréntesis que siempre le
acompañan.
Fichero: LecturaDeLetra.java
public class LecturaDeLetra {
// función Principal
static void main(String[] argumentos) {
char[] letras= new char[500];
int i=0,j;
Salida.print("Introduce los caracteres que quieras...");
Salida.println("yo sólo tendré en cuenta las letras.");
Salida.println(" Para terminar introduce la letra Ñ");
letras[i]=readLetra();
while(letras[i]!='Ñ' && i<letras.length-1) {
i++;
letras[i]=readLetra();
}
Salida.println("Las letras del texto escrito son estas...");
for(j=0; j<i; j++)
Salida.print(letras[j]);
}
// otra función
static char readLetra() {
char caracter;
caracter=Entrada.readChar();
// si no es letra, la
while(!Character.isLetter(caracter)) // desestima y vueve a
caracter=Entrada.readChar();
// leer.
return caracter;
}
}
En cualquier caso, el dato retornado por una función debe ajustarse estrictamente al tipo
especificado delante del nombre de la función. Si la función no retorna ningún dato
(como en el primer ejemplo) el tipo especificado para la función será void, que indica
tipo hueco, o vacío.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 118
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Identificadores y datos locales
Una función puede crear en su ejecución tantos identificadores y datos locales como
necesite, usando las sentencias declarativas habituales. En ellos habrá que tener en
cuenta que:
- Los identificadores (primitivos o referenciados) sólo son accesibles dentro del
cuerpo de función en el cual se declaran.
Los identificadores de una función se dice que son locales a ésta, porque son
inaccesibles desde otras funciones.
- Los datos primitivos mueren cuando muere su identificador, al finalizar la
ejecución de la función.
- Los datos referenciados dejan de existir cuando ningún identificador los
referencia. Si se crea uno localmente en una función, pero un identificador
externo a la función (declarado fuera del cuerpo de la función) lo referencia, el
objeto puede sobrevivir a la propia función que lo creó (más tarde se verán
algunos ejemplos de este caso).
Recuérdese que el contenido inicial de las variables locales declaradas será
indeterminado, por lo que es necesario asignarles algún valor conocido antes de que
puedan ser consultadas.
Parámetros
A los <datos_iniciales> cuyo formato se describe en la cabecera de la función se les
llama parámetros formales, describen el tipo de datos que deben incluirse cuando se
invoque a la función para que se ejecute y su estructura es similar a la de cualquier
declaración de identificadores, sólo que es obligado indicar el tipo de cada parámetro,
aún cuando sus tipo coincidan.
<datos_iniciales>
<datos_iniciales>
<tipo> <identificador_1> , <tipo> <identificador_2>, ... ;
<tipo> <identificador_1> , <identificador_2>, ... ; // ERRÓNEO
Podrán haber tantos como sean precisos, o bien no haber ni un sólo parámetro.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 119
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Para las sentencias de la función, los identificadores declarados como parámetros (datos
iniciales) son como cualquiera de los identificadores locales de los ejemplos vistos,
excepto en que: cuando la función comienza su ejecución, los parámetros no están
indeterminados puesto que deben contener los datos iniciales incluidos en la llamada.
En el siguiente ejemplo tenemos tres subprogramas:
-
La función main(...) que es la principal, la que primero se ejecuta que se ocupa
de pedir los datos por la entrada y de mostrar en un mensaje formateado los
resultados que la función Combinacional(...) le retorna como resultado.
-
La función Combinacional(...).
-
Y la función Factorial(...).
Fichero: Combinacional.java
class Combinacional {
// Principal
static void main(String [] arg) {
long num1,num2;
long agrupa;
Salida.print("Primer número: ");
num1=Entrada.readLong();
Salida.print("Segundo número: ");
num2=Entrada.readLong();
agrupa=combinacional(num1,num2);
Salida.println(num1+" elementos tomados de "+num2+" en "+num2+
" permiten "+agrupa+" agrupaciones distintas.");
}
static long combinacional(long a, long b) {
long numerador, denominador;
numerador= factorial(a);
denominador= factorial(b)*factorial(a-b);
return numerador/denominador;
}
static long factorial(long num) {
long resultado;
resultado=1L;
while (num>1) {
resultado=resultado*num;
num--;
}
return resultado;
}
}
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 120
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Si tal como se a dicho, el formato para los datos iniciales de una función se describe
mediante los parámetros formales...
static long combinacional(long a, long b) {
··· ····
static long factorial(long num) {
·· ····· ····
... a los datos iniciales que se incluyen en cada llamada a una función se les llama
parámetros actuales ...
····· ······ ···
agrupa=combinacional(num1 , num2);
···· ··· ····
numerador = factorial(a);
denominador = factorial(b)*factorial(a-b);
··· ··········
... y pueden ser cualquier expresión, siempre y cuando su resultado sea del tipo
especificado por los parámetros formales de la función.
Las expresiones usadas como parámetros actuales pueden contener constantes,
variables, llamadas a funciones, e incluso otros parámetros que la función llamadora
haya recibido.
En cualquier caso, el dato inicial pasado como parámetro será el valor resultante de
evaluar la expresión.
Cada vez que una función llama a otra (la invoca, la nombra para que se ejecute), se
detiene su propia ejecución hasta que la función invocada termina, momento en el que
su propia ejecución continúa por el punto inmediatamente posterior al momento en que
se llamó a la función. Si la función es parte de una expresión, ésta continúa su
evaluación con el valor que haya retornado la ejecución de la función
Comienzo del programa
main
//función principal
····· ··· ······
agrupa = combinacional(num1,
···· ·········
num2);
funcion factorial
Final
····· ··· ······
··· ·········
funcion combinacional
····· ··· ······
numerador=factorial(a);
denominador=factorial(b)
···· ·········
Autor : FernandoToboso Lara
* factorial(a-b);
e-mail: [email protected]
Pag. 121
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Si una función se invoca varias veces, como es el caso de la función factorial(...), la
línea de ejecución pasa varias veces por ella, sin embargo sus resultados no tienen por
que ser los mismos cada vez puesto que dependerán entre otras cosas de los parámetros
actuales que se le pasen en cada llamada.
Los identificadores de los parámetros se comportan como identificadores normales
salvo como ya hemos dicho por que no se crean indeterminados. Desde que empieza la
ejecución de una función, cada dato inicial se asigna al identificador del parámetro que
corresponde según su orden de colocación.
<identificador_del_primer_parámetro_formal> = <valor_del_primer_parámetro_actual>
<identificador_del_segundo_parámetro_formal> = <valor_del_segundo_parámetro_actual>
<identificador_del_tercer_parámetro_formal> = <valor_del_tercer_parámetro_actual>
······· ······ ······ ······ ··· ········· ··············
······· ····· ······· ····· ········ ····· ···· ·········
A esto se le suele conocer como paso de parámetros y es importante tener en cuenta
que:
- Si los parámetros son de tipo primitivo... los datos recibidos son copias de los
valores incluidos en la llamada.
- Si son de tipo referenciado... se reciben copias de las referencias a los datos, pero
los datos a los que se referencia son los originales.
Este matiz es importante, porque mientras que los parámetros que recibimos de tipos
primitivos no nos permiten modificar los datos de inicialización originales, los
referenciados sí.
Supongamos los siguientes casos donde un función pretende modificar el dato original
que se le pasa...
... void modifica(int numero) {
numero=0;
// esto resulta inutil.
}
Si la llamamos dando como parámetro una
variable de tipo int con el dato inicial,
comprobaremos que después de su ejecución
el valor que tenía permanece, no cambia.
... void modifica(BufferString frase) {
frase.replace(0,frase.length(),”Nueva frase”);
}
Aquí sí modificamos el dato original cuya
referencia se recibió en el parámetro frase,
reemplazando su contenido por uno nuevo.
... void modifica(BufferString frase) {
frase=new StringBuffer(“inutil”);
}
No se confundan, aquí no se modifica el dato
original de inicialización. Asignamos la
referencia de un nuevo dato al identificador
del parámetro, pero el dato original queda tal
y como estaba.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 122
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Una característica importante de las funciones, sobre la que trataremos en profundidad
más adelante, es que lo que realmente las hace identificables no es solamente su nombre
sino éste en combinación con el tipo de parámetros que debe recibir.
Esto hace que las siguientes cabeceras correspondan a funciones diferentes.
declaración (header o cabecera)
ejemplo de llamadas a la función
void nombreFunc() .................... nombreFunc( );
void nombreFunc(int a) ............... nombreFunc( 4 );
........... nombreFunc( 2+7 );
...................................... int num=3;
void nombreFunc(int a, int b) ........ nombreFunc( num , 2+7 );
void nombreFunc(String a) ............ nombreFunc( “frase” );
void nombreFunc(int a, BufferString b) nombreFunc( 3 , new BufferString(“frase”) );
nombreFunc(-9 , new BufferString(“1,2,3,4”) );
void nombreFunc(BufferString a, int b) nombreFunc( new BufferString(“frase”) , 3 );
nota.- El tipo de dato retornado no se tiene en cuenta para la identificación de la función.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 123
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Parámetros de la función principal
... main ( String [ ] ... )
Existe un caso especial en el paso de parámetros, el de la función principal main(...).
Esta función se ejecuta al comenzar la aplicación, pero no se la llama desde ninguna
otra función java sino desde la interface de comandos del sistema (o desde un entorno
gráfico).
Está claro que los sistemas en los que se ejecute la aplicación no tienen por qué conocer
los tipos de datos de java. Así pues el modo de pasar datos iniciales a la función main(),
de la aplicación ha de ser especial.
Para el paso de argumentos (parámetros de un programa) se utiliza un estándar según el
cual estos deben escribirse detrás del nombre de la aplicación que se desea ejecutar,
separados entre sí por uno o más espacios.
INDICADOR DE COMANDOS >
java
NombrePrograma
(M
aq I
ui nt
na er
Vi pre
rtu
al)
J
Pr
og
ra
m
av
a
a
234
Pr
im
er
tururu 32.02
Se
gu
nd
o
ar
gu
m
en
to
Te
rc
er
o
uno,
Cu
ar
to
o
dos.
Q
ui
nt
o
Se
xt
o
La función principal de las aplicaciones java recibe un único parámetro de tipo tabla de
String, que contiene los argumentos que le hayan pasado en la línea de comandos al
ejecutar la aplicación.
argumentos
argumentos[0]
argumentos[1]
argumentos[2]
argumentos[3]
argumentos[4]
argumentos[5]
“234”
“tururu”
“32.02”
“uno,”
“o”
“dos.”
···· · ·· ·····
public static void main(String[] argumentos) {
int primero;
String segundo;
float tercero;
primero=Integer.parseInt(argumentos[0]);
segundo=argumentos[1];
tercero=Float.parseFloat(argumentos[2]);
System.out.println("1er argumento: "+primero);
System.out.println("2: "+segundo);
System.out.println("3: "+tercero);
System.out.println("4: "+argumentos[3]);
System.out.println("5: "+argumentos[4]);
System.out.println("6: "+argumentos[5]);
}
· · ····· ··
El formato de los argumentos es indiferente puesto que se considerarán cadenas de
caracteres (String), dejando que sea la propia aplicación la que los transforme a datos de
alguno de sus tipos dependiendo del uso que les vaya a dar.
La ejecución del programa del ejemplo, tendría este aspecto:
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 124
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
C:\Mis documen ...\> java Argumentos 234 tururu 32.02 uno, o dos.
1er argumento: 234
2: tururu
3: 32.02
4: uno,
5: o
6: dos.
C:\Mis documen ...\> _
Recursividad
Tal como hemos visto, es usual que una función llame a otra para hacer que se ejecuten
sus instrucciones.
Y tal como hemos visto cuando la función llamada finaliza, la función que la llamó
continúa su propia ejecución por donde la dejó.
Para ello cada función llamadora almacena la información del sitio por el que
interrumpe su ejecución, en una estructura donde los datos se van apilando de modo que
la última información guardada queda sobre las demás y será la primera que después se
extraiga.
Cada vez que termina una
función y la ejecución vuelve
a la funcion que la llamó
…
..
..
...
Cada vez que se llama
a una función
Dirección de la sentencia de la función F4( ) por la que
se debe proseguir cuando termine la función actual
Dirección de la sentencia de la funcion F3( ) por la
que se debe proseguir cuando termine la función F4( )
Dirección de la sentencia de la funcion F2( ) por la
que se debe proseguir cuando termine la función F3( )
…
..
..
...
Dirección de la sentencia de la funcion F1( ) por la
que se debe proseguir cuando termine la función F2( )
Si la función llamada llama a su vez a otra función, nuevamente apila en la misma
estructura la información del sitio por el que ella deberá continuar su propia ejecución.
De este modo cada vez que una función termina, el programa saca de esta pila los datos
que le indicarán el punto por el qué se quedó ejecutando la función llamadora.
En la memoria del ordenador se reserva para cada función que esté a mitad de ejecución
un espacio de memoria para sus datos propios, locales.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 125
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Espacio de memoria
para los datos de la
función F4( )
Espacio de memoria
para los datos de la
función F3( )
Espacio de memoria
para los datos de la
función F2( )
Espacio de memoria
para los datos de la
función F1( )
Este espacio se le respeta incluso mientras se encuentra detenida esperando a que
finalice la función a la que haya invocado. De otro modo no sería posible para las
funciones continuar su normal ejecución después de llamar a otras.
Sólo cuando la ejecución de una función termine se liberará el espacio de memoria que
se le había reservado, quedando disponible para otros fines.
En principio, cualquier función puede llamar a cualquier otra. Pero de entre todas las
llamadas que una función puede realizar, existe una posibilidad muy peculiar: que una
función se llame a sí misma.
El resultado sería que existirían simultáneamente varías ejecuciones en memoria de la
misma función, cada cual con sus propios datos locales.
A esto se le llama recursividad.
Por ejemplo, en nuestro primer programa modular (con funciones) implementábamos la
función matemática factorial, cuya definición era:
si n ≠ 0 ⇒ n ⋅ ( n − 1) ⋅ ( n − 2) ⋅K ⋅1
Factorial( n ) = 
 si n = 0 ⇒ 1
A veces expresado de este modo...
si n ≠ 0 ⇒ n ⋅ Factorial ( n − 1)
Factorial ( n ) = 
 si n = 0 ⇒ 1
.
En casos como éste, una implementación usando recursividad, sería inmediata y muy
simple.
·· · · ·····
static long factorial(long num) {
if(num > 0)
return num * factorial(num-1);
else
return 1;
}
···· ··· ··
Cada ejecución recursiva recibe como dato inicial un número una unidad menor que el
recibido por la anterior ejecución de sí misma.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 126
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
llamada a … Factorial(4)
La traza de la ejecución de las llamadas recursivas de la función factorial(4) podría ser
algo así:
función
Factorial(...)
que recibe 4
de parámetro
función
Factorial(...)
que recibe 3
de parámetro
detenida hasta que
termine la
función a la
que ha llamado...
Factorial(num-1)
detenida hasta que
termine la
función a la
que ha llamado...
Factorial(num-1)
num== 4
retorna
4·(3·2·1)
función
Factorial(...)
que recibe 2
de parámetro
num== 3
num== 2
función
Factorial(...)
que recibe 1
de parámetro
num== 1
detenida hasta que detenida hasta que
termine la
termine la
función a la
función a la
que ha llamado... que ha llamado...
Factorial(num-1) Factorial(num-1)
llamada a
Factorial(3)
llamada a
Factorial(2)
retorna
3·(2·1)
retorna
2·(1)
llamada a
Factorial(1)
retorna
1
función
Factorial(...)
que recibe 0
de parámetro
num== 0
no se vuelve a
llmar, si no que
retornará 1
llamada a
Factorial(0)
retorna
1
Como puede observarse, resulta fundamental que en algún momento se alcance un
situación en que la función deje de auto llamarse, para que puedan ir finalizando todas
sus ejecuciones pendientes.
Aquí la condición num>0 deja de ser cierta en la función que recibe el parámetro 0,
por lo cual no se vuelve a autoejecutar.
A la implementación de la función que vimos al princípio, se le suele denominar
solución iterativa porque se desarrolla usando bucles, en vez de llamarse a sí misma.
Generalmente las soluciones iterativas son preferibles a las recursivas porque hacen un
uso más eficaz de la máquina en la que se ejecutan.
Sin embargo algunos algoritmos complejos resultan mucho más sencillos si se plantean
recursivamente, justificando sobradamente su utilización.
Errores de ejecución
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 127
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
En ocasiones los programas se encuentran ante situaciones de excepción que requieren
una solución especial. Se trata de errores de ejecución que se pueden producir en los
programa que se están ejecutando.
Por ejemplo cuando intentan utilizar un fichero al cual tienen restringido el acceso, al
hacer una operación inválida como intentar dividir entre cero, cuando el programa no
dispone de suficiente memoria para ejecutarse...
Al codificar la implementación de un algoritmo, hay que tener en cuenta la posibilidad
de que estos errores se produzcan.
El código debería incluir sentencias que se ocuparan de resolver dichos errores porque
de otro modo abortarían la ejecución del programa (harían que terminase bruscamente)
con efectos imprevisibles sobre los datos que estuviese manejando.
Sin embargo no parece muy adecuado mezclar las sentencias que resuelven estas
situaciones excepcionales con las del código fundamental del programa, el que
representa el algoritmo, ya que lo complicaría y lo haría menos claro.
El modo en que algunos lenguajes como java resuelve estas situaciones tiene mucho que
ver con los subprogramas.
Como ya hemos visto un programa normalmente se compone de varios fragmentos de
programa. Si uno de ellos intenta ejecutar una sentencia que por cualquier motivo
produce uno de estos errores, tendrá dos alternativas:
- Solucionarlo por sí mismo, de modo que la parte del programa que solicitó
la ejecución de la función no se entera de que ha habido un error, porque
cuando acaba la ejecución del módulo ,el error ya ha sido corregido.
- O bien, no hacer nada al respecto. En este caso la función finaliza
repentinamente dejando que el error llegue a la parte del programa desde la
que se solicitó su ejecución. La función (como si de una sentencia se tratase)
será la que produce el error desde el punto de vista de la parte que pidió su
ejecución. El código que ahora recibe el error estará contenido nuevamente
en una función (la principal u otra cualquiera) con estas mismas dos
alternativas: Solucionar el error, o no hacer nada al respecto y dejar que
trascienda a la anterior función llamadora.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 128
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Manejo de Errores
El modo en que una función puede intentar resolverlos consiste en acotar los fragmentos
de código que contienen sentencias susceptibles de producir esos errores. Esto se realiza
agrupando dichas instrucciones en un bloque de instrucciones precedido de la palabra
try. A continuación de estos bloques se colocan uno o más bloques catch conteniendo
las sentencias que se deben ejecutar para dar respuesta a cada una de las posibles
excepciones que puedan generar las instrucciones bajo la vigilancia dentro del bloque
try.
try {
instruccion(s)
// vigiladas por si producen excepciones
}
catch (tipo_de_Error_o_Excepcion identificador) {
instruccion(s)
// que se ejecutan si se produce un error o
// excepción del tipo arriba indicado.
}
catch (otro_tipo_de_Error_o_Excepcion identificador) {
instruccion(s)
// que se ejecutan si se produce un error o
// excepción del tipo arriba indicado.
}
··· ····· ······
·· ····· ····
finally {
instruccion(s)
// que deben ejecutarse siempre, pase lo que pase.
}
Puede haber más de un bloque catch, de modo que cada uno dé respuesta a uno de los
diferentes tipos de error que se puedan producir (de ello se tratará más adelante).
Si un bloque catch(...) se ejecuta, el error se considerará atrapado y resuelto (incluso
cuando el bloque catch no haga nada para resolverlo), por lo que la ejecución de la
función continuará por la sentencias siguientes al bloque try...catch.
nota.- La ejecución no vuelve al punto donde se produjo el error.
Puede también ocurrir que las instrucciones del bloque catch al no poder resolver la
situación de error, opten por la ejecución de una instrucción que finalmente interrumpa
la ejecución del módulo, para evitar que continúe su curso normal.
Tanto si esto ocurre como si no y haya habido error o no lo haya habido, si a
continuación se ha incluido un bloque finally, sus instrucciones se ejecutarán siempre.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 129
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
El siguiente gráfico ilustra todas estas alternativas de ejecución explicadas.
// Comienzo del módulo o subprograma
····· ··· ······
try {
// sentencias que pueden crear situaciones de error
..............
}
catch( tipo de error ) {
// sentencias para resolver este tipo de error.
}
catch( tipo de error ) {
// sentencias para resolver este tipo de error.
}
··· ··· ····· ······
finally {
// sentencias para ejecutarse siempre
//
con o sin error.
}
·········· ··· ·········
// Si se produce error y no se atrapa o aun atrapandolo
// si en el catch se ejecuta una sentencia de ruptura,
// LAS SENTENCIAS AQUI ESCRITAS NO SE EJECUTARAN
·········· ··· ·········
// Final del módulo o subprograma
Termina su ejecución
sin arrastar ningún error
Termina su ejecución
produciendo error
Para cada uno de los posibles errores, en java existe un tipo de dato referenciado que lo
representa, por esto es usual incluir un bloque catch por cada tipo de error que se desee
prevenir.
Si una instrucción produce un error de algún tipo para el cual no se haya escrito el catch
correspondiente, el error se propagará al módulo que haya llamado a la función y tal
como se explicó anteriormente desde la perspectiva de este nuevo módulo, será la
función cuya ejecución solicitó quien produce el error. Es decir este módulo puede
decidir colocar la llamada a la función dentro de un bloque try.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 130
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Si ninguna función atrapa el error, éste se propagará hasta la función principal
(main(...)) haciendo que la aplicación aborte inmediatamente su ejecución.
nota.- Es posible que ya se hayan encontrado alguna vez al ejecutar un programa que utilice tablas, un
mensaje de error como éste... ArrayIndexOutOfBoundsException . Este tipo de error indica que se ha
intentado acceder a un posición fuera de los límites de una tabla (probablemente indique que hay que
revisar los valores que toma algún índice) y el error no ha sido atrapado.
En el siguiente ejemplo, se está presuponiendo que el número de socio que lee la
función principal será un dato adecuado, pero puede que no sea así. La función
consultarSocio(...) puede producir un error del tipo ArrayIndexOutOfBoundsException
cuando intenta consultar el contenido de la tabla nombres si el número que se le pasa en
el parámetro pos es demasiado grande o es negativo.
Si nadie atrapa el error
// Fichero: Excepciones.java
public class Excepciones {
static void main (String[] arg) {
int codigoSocio;
Salida.print("Que número de socio desea consultar (-1 para acabar): ");
codigoSocio=Entrada.readInt();
while(codigoSocio!=-1) {
Salida.println("...el socio solicitado es " + consultarSocio(codigoSocio));
Salida.println();
Salida.print("¿Otro socio? : ");
codigoSocio=Entrada.readInt();
}
}
static String consultarSocio(int pos) {
String[] nombres={
"Juana Ruperéz","Lupe Catelmo",
"Jeniffer Tris","Jose Angel Pandero",
"Pedro Gluta", "Rita Fastio",
"Jaime Reyezuelo","Angel Polcholo",
"Joxé Ripiao", "Santiago Sampaio",
"Antonia Cartilla","Felix Ganzúa"};
return nombres[pos];
// Si pos no está entre 0 y 11 produce un error que
// se propaga a la función llamadora, que este caso
// es la principal main(...).
}
}
Como la función consultaSocio(...) no atrapa el error que puede producir nombres[pos]
ese error le llegará a la función main(...) como si la propia función consultaSocio(...)
fuese la que produce el error.
Como la función principal main(...) tampoco se ocupa de atrapar el posible error, si este
se produce la ejecución de la aplicación finalizará repentinamente por error de
ejecución.
Esto puede resolverse sin modificar apenas el código básico del programa. Simplemente
poniendo bajo vigilancia la función consultaSocio(...) y atrapar el tipo de error que
sabemos que puede dar.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 131
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Si se atrapa el error que la función puede retornar
.... ..... ...
while(codigoSocio!=-1) {
try {
Salida.println("el socio solicitado es "+consultarSocio(codigoSocio));
} catch(ArrayIndexOutOfBoundsException e) {
if(codigoSocio<0)
Salida.println("¡ay mi amol! ese código no es válido");
else
Salida.println("¡pero que tu crees! aquí no hay tantos socios");
}
Salida.println();
Salida.print("¿Otro socio? : ");
codigoSocio=Entrada.readInt();
··· ·· ·····
Si
la
función
consultarSocio(...)
termina
con
el
error
ArrayIndexOutOfBoundsException, éste es atrapado por el bloque catch(...) que lo
‘soluciona’ advirtiendo al usuario con el mensaje adecuado al caso.
Si la función produce error, al ser atrapado no sólo se muestra un mensaje al usuario
sino que, lo que es más importante, al darse por solucionado el error el programa
continúa su ejecución normal (por la siguiente línea después del catch).
El bloque catch(...) se comporta como una función (pero no es una función).
Cada bloque catch especifica entre sus paréntesis el tipo de error que atrapa y entre las
llaves el código que ejecuta para resolver el error.
Si cuando se realiza la función consultarSocio(...) se decide que de ningún modo debe
producir este error, su propio código puede atraparlo y resolverlo apropiadamente.
En tal caso habrá que decidir que valor retorna cuando no exista el dato que se le
solicita, en nuestro ejemplo el String vacío “” sin un sólo carácter.
En tal caso en el código primeramente implementado añadiríamos dentro de la función
los bloques try y catch correspondientes, quedando de esta forma...
Si la función atrapa el error, impidiendo que se propague
······· ···· ····· ·······
"Antonia Cartilla","Felix Ganzúa"};
try {
return nombres[pos];
} catch(ArrayIndexOutOfBoundsException e) {
return "";
}
}
}
Esto debería ser tenido en cuenta por la función llamadora de manera que si recibe el
String vacío suponga que no existe socio.
nota.- Como este módulo no se ocupó de solicitar al usuario el código de socio para consultar, ni muestra
por sí mismo la información solicitada, tampoco resulta adecuado que se ocupe de mostrar ningún
mensaje de error, a no ser que se especifique expresamente que debe hacerlo.
Existen varias razones por las que resulta ventajosa esta estrategia para el manejo de
errores.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 132
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
- Resulta infinitamente más claro separar las instrucciones fundamentales del programa
de aquellas que se ejecutan sólo cuando ocurren situaciones excepcionales de error.
- Por otra parte cuando un fragmento de código produce error, esta estrategia permite
que se agrupen las instrucciones que se van a aplicar en función del tipo de error de
que se trate.
nota.- Más tarde se verán los diferentes tipos que tiene java para representar los diferentes errores
clasificados por categorías.
- Además de este modo se puede elegir entre resolver el problema, o dejarlo pasar para
que sea otro fragmento del programa el que lo solucione.
nota.- En el ejemplo anterior, aunque el sitio en donde el error pueda aparecer sea dentro de la función
consultarSocio(...) concretamente en la expresión ...nombre[pos]... , quizás es más adecuado
dejarlo pasar para que lo resuelva la función main(...), que al fin y al cabo fue la que leyó del
usuario el número inválido de socio que al ser pasado como parámetro a la función causa el
error.
Excepciones (marcadas y sin marcar)
Java distingue entre dos categorías principales de error en tiempo de ejecución:
- El tipo Error entendido como aquel que se producen en situaciones verdaderamente
anómalas e imprevisibles. Como cuando el sistema se queda sin memoria suficiente
para seguir ejecutando el programa y otros errores de índole desconocida.
Normalmente no son tenidos cuenta en las aplicaciones más que para prevenirlos
advirtiendo en las especificaciones de la documentación los requerimientos mínimos
de ejecución, o para la correcta instalación de la aplicación.
- El tipo Exception (excepción) para representar los casos de error que pueden ser
previsibles en determinados casos de la ejecución de una aplicación, como en el caso
de los ejemplos del apartado anterior (recuerda que se trataba un tipo concreto de
excepción llamada ArrayIndexOutOfBoundsException).
Como hemos podido ver, es posible prever que en determinados casos una función
pueda producir excepciones.
Por otra parte, la cabecera de cualquier función juega un papel fundamental para que
cualquier programador conozca la especificación de cómo debe usarse... su nombre,
tipos de dato iniciales que se le deben pasar, tipo de dato que retornará... y por qué no,
a veces también el tipo de excepción o excepciones que en dicha función se pueden
producir.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 133
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Ciertamente resulta muy recomendable que cualquier función advierta en su cabecera
qué excepciones pueden propagar (al dejarlas pasar sin atrapar) de manera que cuando
cualquier programador las utilice pueda decidir entre vigilar su ejecución en un bloque
try, o si prefiere dejar que el error pase, propagándose al siguiente módulo.
No obstante, no se puede obligar a que cada función declare todas y cada una de las
excepciones que podría lanzar. Resultaría muy poco eficaz para el compilador
comprobar que esto se cumple y muy complicado para el programador cumplirlo.
Por esto, en java existen dos grandes grupos de excepciones:
- No marcadas ...de un tipo llamado RuntimeException (generadas por operaciones
aritméticas sin solución, por intentar acceder a datos referenciados aún no creados
con new, por usar valores de índice ilegales en tablas, etc... ).
No es necesario que las funciones adviertan que pueden generarlas, pues son
demasiado numerosas y genéricas.
- Y marcadas ...todas las excepciones que no pertenezcan al grupo de las anteriores.
Las funciones deben declararlas en su cabecera, cuando pueda darse el caso de que
las generen.
De las excepciones no marcadas ya hemos visto el ejemplo analizado en la función
consultarSocio(...).
A continuación vemos una variante de un ejemplo anterior, el programa Saluda.
En él, se utilizará el tipo de dato Salida (cuyo código se estudiará más adelante), que
dispone de funciones como hacia(...) y println(...) con las que es posible grabar un texto
en un fichero de un modo sencillísimo.
// Fichero: Saluda1.java
/**
Este es un programa que simplemente muestra por
la salida hacia la que se ha redirigido la salida
estándar del sistema, el mensaje --- Hola Persona --**/
import java.io.FileNotFoundException;
class Saluda1 {
public static void main(String[] argumentos) throws FileNotFoundException {
// Llama a la funcion escribe(...)
escribe("Hola Persona");
}
public static void escribe(String mensaje) throws FileNotFoundException {
// Redirige el canal de salida del programa
// hacia el fichero especificado.
Salida.hacia("Saludo.txt");
// Muestra su mensaje por la salida redirigida...
Salida.println(mensaje);
}
}
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 134
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
La sentencia Salida.hacia(“Saludo.txt”) en la función escribe(...) permite fácilmente
modificar la salida estándar (habitualmente la pantalla) para hacer que el programa
escriba en el fichero llamado Saludo.txt .
Este fichero se creará nuevo si no existe y si ya existe se eliminará todo su contenido y
se escribirá en él, el texto: Hola Persona.
Sin embargo hay que tener en cuenta que la sentencia Salida.hacia(...) puede lanzar una
excepción de tipo FileNotFoundException en caso de que encuentre dificultades para
manipular el fichero en cuestión.
Supón por ejemplo, que el fichero ya existe y que tiene permisos de sólo lectura, es
decir no se podrá modificar su contenido.
Esto hará que Salida.hacia(“Saludo.txt”), (entre cuyas tareas se incluye la de preparar
el fichero para escribir en él) falle y lance una excepción del tipo
FileNotFoundException que es del grupo de las ‘marcadas’, de las que deben:
... o bien ser atrapadas, haciendo lo que se estime necesario para que la
ejecución pueda continuar sin problemas.
... o bien ser declaradas en la cabecera de cada una de las funciones que
dejen pasar el error sin resolverlo.
En el ejemplo, tanto la cabecera de la función escribe(...) como la de la función main(..),
la declaran puesto que dejan pasar el error, que finalmente produciría un error de
ejecución que abortará la ejecución del programa, dando un mensaje descriptivo de las
funciones a través de las que se ha ido propagando el error...
C:\Mis documen ...\> java Saluda1
Exception in thread "main" java.io.FileNotFoundException: Saludo.txt (Acceso denegado)
at java.io.FileOutputStream.open(Native Method)
at java.io.FileOutputStream.<init>(FileOutputStream.java:102)
at java.io.FileOutputStream.<init>(FileOutputStream.java:62)
at Salida.hacia(Salida.java:23)
at Salida.hacia(Salida.java:28)
at Saluda.escribe(Saluda1.java:17)
at Saluda.main(Saluda1.java:11)
C:\Mis documen ...\> _
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 135
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Si la función principal atrapase la excepción...
// Fichero: Saluda2.java
import java.io.FileNotFoundException;
class Saluda2 {
public static void main(String[] argumentos) {
// Llama a la funcion escribe(...)
try {
escribe("Hola Persona");
} catch(FileNotFoundException e) {
System.out.println("Error...no se pudo redirigir hacia el fichero Saludo.txt");
}
}
.... ....... ....
... . ..
... ...
}
... o si la propia función escribe(...) la atrapa impidiendo que se propague...
import java.io.FileNotFoundException;
class Saluda3 {
.... ....... ....
... . ..
... ...
public static void escribe(String mensaje) {
// Redirige el canal de salida del programa
// hacia el fichero especificado.
try {
Salida.hacia("Saludo.txt");
} catch(FileNotFoundException e) {
System.out.println("Error no se ha podido preparar el fichero Saludo.txt");
return;
// Aquí termina la función para que no se ejecute la siguiente
// sentencia, la que imprime mensaje por Salida.
}
// Muestra su mensaje por la salida redirigida...
Salida.println(mensaje);
}
}
... el programa completaría correctamente su ejecución (en nuestro caso mostrándose
mensajes que advierten de la imposibilidad de realizar la tarea encomendada).
Cuando se diseña qué es lo que deben hacer los módulos de una aplicación, también se
especifica en cuales de ellos se deben hacerse cargo de atrapar los posibles errores.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 136
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Capitulo 10
Clases y Objetos
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 137
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Introducción
Las clases son unos elementos capaces de permitirnos especificar nuestros propios tipos
de datos, con los que poder crear datos referenciados cuyo comportamiento se ajuste a
lo que de ellos necesitemos.
No son propiamente datos sino algo así como los moldes con los que después se podrán
crear (con new) tantos datos como se necesiten.
Definiendo nuevas clases se pueden diseñar nuevos tipos de datos referenciados, del
estilo de los que hemos venido usando, o adaptar y ampliar los tipos referenciados ya
existentes.
Efectivamente los tipos de datos referenciados hasta ahora vistos y los que a
continuación iremos viendo están definidos como clases.
Es decir, donde hasta ahora decíamos que ...
... “Hola Caracola” es un dato de tipo referenciado String.
... ahora diremos ...
... “Hola Caracola” es un dato de la clase String.
A los datos de cualquier tipo referenciado (clase), a partir de este punto, les pasaremos
a decir objetos, que es como realmente se les llama en el contexto de la programación
orientada a objeto.
De modo que en lugar de decir...
... “Hola Caracola” es un dato de la clase String.
... será más apropiado decir ...
... “Hola Caracola” es un objeto de la clase String.
Ya tratamos anteriormente sobre cómo java distingue entre dos categorías de tipos de
dato:
- Primitivos.- (int, char, ...) de ellos es suficiente conocer cómo declararlos, qué
constantes representan sus posibles valores y en qué operaciones intervienen.
- Referenciados.- (clases) algo más complicados, por ahora sabemos de ellos que al
margen de se declare algún identificador para que los referencie es necesario crear el
dato separadamente y algunas otras nociones básicas de cómo se deben manejar,
aprendidas principalmente a través de las clases String, StringBuffer y tablas que
hemos venido utilizando en los programas de ejemplo.
nota.- Las estructuras tabla se comportan como clases (tipos referenciado) de datos pero su
manejo es un tanto especial, por eso fueron analizadas en un apartado especial.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 138
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
A continuación conoceremos las clases (tipos referenciados) más a fondo.
• Empezaremos por analizar sus atributos. Datos que forman parte de otros objetos,
como en el ejemplo del CromoMovil podía verse esta sentencia:
····· ····· ·····
cromo.x = Entrada.readInt();
······ ········ ·· ···
En la que se asigna un valor numérico tipo int al atributo x que forma parte del objeto
de la clase CromoMovil, referenciado por el identificador cromo.
• Después continuaremos con sus métodos. Funciones que forman parte de objetos,
como en la sentencia del mismo ejemplo:
····· ····· ·····
cromo.CargaCromo(“imagenfinal.gif”);
······ ········ ·· ···
En la que se ejecuta la función CargaCromo(...), que forma parte igualmente del
objeto referenciado por cromo con la finalidad de que se le asocie una nueva imagen.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 139
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Atributos (datos agrupados)
Supongamos que nuestro programa necesita manejar como datos, las posiciones
geométricas de algo, es decir sus coordenadas. Con lo que hasta ahora sabemos
procederíamos declarando dos variables de tipo numérico (x,y) para cada coordenada.
Si lo que se necesita es almacenar una lista de coordenadas podemos optar por usar dos
tablas de números, una para las coordenadas x y otra para las correspondientes y.
coordenadas
(X N
(X 2
(X 1
YN )
Y2 )
Y 1)
Y0 )
y
(X 0
x
La entidad coordenada aparecería repartida de un modo muy poco claro.
Será mucho más claro declarar una clase que describa que la entidad coordenada está
formada por dos datos numéricos, de modo que podamos utilizar una tabla cuyo tipo sea
la clase coordenada.
(X 1
[2]
[N]
(X N
[1]
(X 2
[0]
(X 0
coordenadas
YN )
Y2 )
Y 1)
Y0 )
Para declarar la especificación de cómo será esta nueva clase que agrupa datos, se
utiliza la siguiente sintaxis:
<otros_modif> class <identificador_de_la_clase> {
<tipo> <identificador_para_cada_atributo>;
············
};
class es una palabra reservada que precede al identificador con el que denominaremos a
la estructura constituida por los datos (primitivos y referenciados) que se declaran entre
las llaves { } y finalizando toda la instrucción declarativa con el inevitable punto y
coma.
A esos datos contenidos en la clase los llamaremos atributos de la clase ya que sus
valores describen en cierto sentido los atributos o características de las cosas que los
objetos de esa clase representan.
ejemplo.- Cuando se vayan a almacenar una serie de datos sobre árboles, quizás interese implementar una
clase de objetos Árbol cuyos atributos (altura, edad, nombreCientifico, fechaÚltimaPoda,
cuidadosNecesarios, ...) permitan almacenar lo que se necesite conocer de cada árbol.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 140
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Para el caso de las coordenadas escribiríamos:
class Coordenada {
float x,y;
};
tipo de dato
Coordenada
x y
Es importante empezar entendiendo que una clase no es un dato sino la especificación
que describe cómo son los objetos de una clase.
Una vez que se define una clase, servirá (salvo casos especiales que se estudiarán más
adelante) para crear mediante la instrucción new objetos (datos referenciados) que se
ajusten a la descripción que en dicha clase se realiza.
Después de codificar la anterior definición de la clase Coordenada, podemos crear una
tabla de objetos Coordenada ...
········· ·· ·····
/* Declaración y creación del array de coordenadas
(sólo se crea el array, no los objetos coordenada
que se referenciarán desde cada casilla de la tabla) */
Coordenada[] tabla = new Coordenada[<tamaño_de_la_tabla>];
// Creación de los objetos de tipo Coordenada.
for(i=0; i<tabla.length; i++)
tabla[i] = new Coordenada();
········ ···· ··
nota.- al tratarse de una tabla de objetos, es preciso crear por un lado la propia
tabla y por otro lado los objetos que contiene.
Llegados a este punto ya disponemos de una tabla de objetos Coordenada y por lo que
sabemos, para cada valor de i válido, coordenadas[i] referencia (identifica) a un objeto
de clase Coordenada, pero ¿cómo acceder a los atributos de los objetos que la tabla
alberga?.
La respuesta es fácil porque se hace del mismo modo que hemos venido utilizando para
acceder a las partes de cualquier objeto.
<identificador_del_objeto> . <identificador_de_la_parte>
Supongamos que queremos dar a todas las coordenadas de la tabla el mismo valor
inicial 12.1 para la x, 4.01 para la y.
····· ··
for(i=0; i<tabla.length; i++) {
tabla[i].x = 12.1;
tabla[i].y = 4.01;
}
··· ······
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 141
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Echémosle un vistazo al siguiente programa que implementa una carrera de ‘puntos’.
nota.- El programa es un simple juego en el que cada vez que se pulsa una tecla, todos los puntos avanzan
una cantidad al azar.
Para ejecutarlo será necesario disponer también de una clase llamada Pista, de este manual.
En él, como veremos, hay tres funciones:
void main(...) .- La función principal por la que comienza la ejecución y que
simplemente se limita a crear un objeto de la clase Pista.
Este objeto se encargará de la parte gráfica y de hacer que las
funciones que veremos a continuación se ejecuten cuando
convenga. Solo nos servirá de soporte así que por ahora nada más
que decir respecto a él.
void preparadosListos(...) .- Que se ejecutará una sola vez al empezar para preparar
a los objetos Punto que van a competir.
void avance(...) .- Que se ejecuta cada vez que se pulsa una tecla (excepto la tecla
Escape, que hará finalizar el programa).
nota.- El objeto Pista creado por la función main(...) se ocupa de llamar a las funciones
preparadosListos(...) y avance(...). en los momentos precisos.
En ellas se manejan objetos de la clase Punto, una clase de la que iremos viendo
diferentes versiones y sobre la cual centraremos realmente nuestra atención.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 142
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
import java.lang.Math.*;
public class Carrera {
public static void main(String[] args) {
new Pista();
}
// Clase que describe como es cada punto
// *************************************
public static class Punto {
int diametro;
int x,y;
}
// Recibe un objeto tabla de Puntos vacia para que aquí se creen
// los objetos tipo Punto, inicializados con las posiciones
// de salida y distintos tamaños
// **************************************************************
public static void preparadosListos(Punto[] competidores) {
for(int i=0; i<competidores.length; i++) {
competidores[i] = new Punto();
competidores[i].diametro = 5+i*2;
competidores[i].x = 20-competidores[i].diametro; // alinea su lado derecho
competidores[i].y = 30+i*15;
}
}
// avance de puntos incrementando su coordenada x.
public static void avance(Punto[] competidores) {
for(int i=0;i<competidores.length; i++)
if(competidores[i].x+competidores[i].diametro<430)
competidores[i].x += (int)(10*Math.random());
else
competidores[i].x=432;
}
};
El programa utiliza una tabla de objetos de clase Punto, clase con la que se gestiona de
manera más clara las características de cada punto en la pantalla, su posición y su
diámetro.
Tal como se irá comprobando, los objetos de clases definidas por nosotros se comportan
como objetos de tipo referenciados... y es que ciertamente eso es exactamente lo que
son. Sólo que en este caso somos nosotros quienes diseñamos cómo es el objeto por
dentro definiendo la clase que lo describe.
·········
public static class Punto {
int diametro;
int x,y;
}
·········
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 143
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
preparadosListos(...)
Esta función recibe como dato inicial la referencia a una tabla de tipo Punto ya creada,
pero sin contener todavía ningún objeto Punto.
Cada casilla contiene null (una
referencia nula, ... a ningún objeto).
Las instrucciones del cuerpo de esta
función se ocupan de crear los puntos
dándoles a cada uno un tamaño propio y
una posición de salida para que todos
ellos queden alineados por la parte de la
derecha.
competidores
Como tales datos referenciados, los objetos Punto se crean con la instrucción new. Sólo
después de crearlos tiene sentido asignar valores a sus atributos.
····· ··· ··
for(int i=0; i<5; i++) {
competidores[i]=new Punto();
competidores[i].diametro=5+i*2;
competidores[i].x=20-competidores[i].diametro;
competidores[i].y=30+i*15;
}
.... ... ...
En el recorrido de la tabla con el contador i como índice, la acciones realizadas para
cada elemento son:
- Crear cada objeto Punto.
( competidores[i].x , competidores[i].y )
- Asignar a cada uno, un diámetro distinto.
nota.- Calculamos el diámetro sumando al
tamaño que se dará al punto más pequeño
(5), una cantidad que a cada vuelta del
bucle aumenta (i*2).
x
- Asignar a todos la misma posición
horizontal.
( x - <su propio diámetro> ) .
diámetro
( competidores[i+1].x , competidores[i+1].y )
x
diámetro
nota.- Se les resta su propio diámetro porque se
dibujarán usando las coordenadas de su
esquina superior izquierda y para la salida
todos los puntos deben estar alineados por
su lado derecho.
- Asignar a cada uno, una posición vertical
para el atributo y , distinta. De modo que
cada punto esté a una altura diferente en
la ventana que será pista de competición.
Cuando la función acaba su ejecución, los puntos están preparados y listos para
competir en línea de salida y referenciados por la tabla que se facilitó como parámetro.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 144
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
avance(...)
La función recibe igualmente como dato inicial la referencia a una tabla, pero ahora ya
debe contener datos... los puntos preparados en la anterior función.
···· ······· ··
for(int i=0;i<competidores.length; i++)
if(competidores[i].x+competidores[i].diametro<430)
competidores[i].x += (int)(10*Math.random());
else
competidores[i].x=432; // en la meta
···· ······· ·· ··· ·····
Cada vez que esta función se ejecuta (cosa que ocurrirá cada vez que se pulse una tecla),
se incrementa la coordenada x de cada punto en una cantidad al azar calculada por la
expresión (int)( 10*Math.random( ) ) que devuelve como resultado un número entero
aleatorio al azar entre 0 y 9.
nota.- Math.random() da como resultado un número aleatorio (al azar) mayor o igual que 0.0 y menor
estrictamente que 1.0. Al multiplicarlo por 10 quedará entre 0.0 y 9.99999, de donde al truncar sus
decimales, resultará finalmente el entero entre 0 y 9.
A no ser que su posición más su diámetro, ya no sean menores que 430 (lugar donde se
ha posicionado la línea de meta), caso en que se les coloca de manera estable en la
posición de llegada.
Poco a poco todos los puntos irán llegando a la meta, ¿cuál será el primero? eso no se
sabrá hasta que no termine cada la carrera que se ejecute.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 145
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Métodos (o funciones miembro) de una clase de objetos
Los atributos de las clases son elementos habituales en cualquier lenguaje de
programación estructurado de alto nivel (C, Pascal, ...) para facilitar la agrupación de
datos referidos a una misma entidad de modo que describan su estado actual, sin
embargo las clases de las que aquí hablamos permiten algo más,... permiten crear una
serie de instrucciones o acciones propias de los objetos de la clase definida, que a
menudo actúan modificando la información que el propio objeto contiene.
Dicho de otro modo en una clase se pueden agrupar atributos (datos que describen esa
clase de objetos) y métodos (funciones que describen cómo se comportan, que acciones
realizan).
Las sentencias del cuerpo de las funciones miembro de un objeto, tienen posibilidad de
trabajar, no sólo con las variables que declaran localmente y los parámetros que reciben
como datos iniciales sino también con los atributos del objeto del que son miembros.
Volviendo al ejemplo de la clase Punto, añadiremos a la clase un método que se
encargará (de manera “inteligente” del avance del objeto. El método desplazará el punto
(incrementará su atributo x) un número al azar de posiciones, teniendo en cuenta un
parámetro que le indicará con qué fuerza máxima debe avanzar.
public static class Punto {
int diametro;
int x,y;
// Hace avanzar su x hasta la meta un número
// al azar, entre 0 y potencia-1. Y dice
// si avanza, o no por haber llegado a su fin
boolean avanzar(int potencia) {
boolean aunNoHaLlegado;
if(x+diametro<430) {
x+=(int)(potencia*Math.random());
aunNoHaLlegado=true;
}
else {
x=432;
aunNoHaLlegado=false;
}
return aunNoHaLlegado;
}
};
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 146
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
competidores
x
y
etro
diam
x
y
… avanzar(...) {
····· ···· ···· ··· ··
·· ····· ········ ····
etro
diam
… avanzar(...) {
}
}
····· ···· ···· ··· ··
·· ····· ········ ····
x
· ······ ·····
·· ······ ·····
·· ····· ·· ···
y
diam
etro
… avanzar(...) {
····· ···· ···· ··· ··
·· ····· ········ ····
}
Viendo la representación de los objetos Punto, se entiende que cuando en el método
avanzar(...) se refiere a x , se está refiriendo al atributo x del objeto al que pertenece.
Pero ¿y a cuál pertenece?, pues depende de cuál de los objetos esté ejecutando el
método. Si se invoca su ejecución con esta sentencia...
competidores[0].avanzar(10);
... se ejecutará el método avanzar(...) del primer objeto referenciado por la tabla. Por lo
que, cuando sus sentencias manejan x, están accediendo al atributo x de dicho objeto
competidores[0] y cuando nombren cualquier otro identificador de la clase, se
referirán igualmente a elementos de este mismo objeto.
Con la clase así definida, cualquier objeto de clase Punto, como los referenciados por
competidores[i], es un competidor que sabe por sí mismo avanzar por la pista, con lo
que la función avance(...) de nuestro programa se puede simplificar quedando así:
····· ····· ··
public static void avance(Punto[] competidores) {
for(int i=0;i<competidores.length; i++)
// avance de puntos con potencia de avance 10.
competidores[i].avanzar(10);
}
· ······· ···
El método avanzar(...) miembro de los objeto Punto les confiere una acción
característica de ellos y una manera propia de comportarse. Para hacerles avanzar
simplemente habrá que solicitar la ejecución de su método para que el propio objeto
modifique su posición del modo que indica el método definido en la clase.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 147
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
objeto_a
Atributos:
diametro
x 20
y 110
10
avanzar(....) {
·· ··· ······
····· ···· ····
··· ·· ·····
}
Entonces...
····· ···· ···
Punto objeto_a = new Punto();
Punto objeto_b = new Punto();
objeto_a.x=20;
objeto_a.y=110;
objeto_a.diametro=10;
objeto_b.x=61;
objeto_b.y=10;
objeto_b.diametro=50;
···· ··· ····
objeto_a.avanzar(15);
··· ··········
objeto_b.avanzar(10);
····· ···· ······
objeto_b
Atributos:
diametro
x 61
y 10
50
avanzar(....) {
·· ··· ······
····· ···· ····
··· ·· ·····
}
¿una función de un objeto no puede acceder a los
identificadores de otros objeto (sean o no de su misma clase)?
Por supuesto que sí pueden, siempre y cuando dispongan de un identificador que
referencie el objeto que se quiere utilizar.
Un método podría por ejemplo recibir a través de un parámetro, la referencia a algún
otro objeto distinto de aquel al que pertenece y mediante está referencia acceder a los
identificadores que contenga.
Algo así ocurre en un método llamado paint(...) perteneciente al objeto Pista y usado
para la ejecución de las carreras de Puntos (cuyo código fuente completo se omitirá por
ser todavía demasiado complejo).
interior del objeto Pista
····· ···· ···
public void paint(Graphics g) {
super.paint(g);
g.drawLine(meta,0,meta,altoVentaa);
// dibuja en cada momento los puntos en liza......
// **************************
for(int i=0;i<5; i++)
g.drawOval(tabla[i].x, tabla[i].y, tabla[i].diametro,
tabla[i].diametro );
}
····· ···· ······
g objeto de la
clase Graphics
Atributos:
/* los que sean */
······ ···
drawLine(...) {
··· ·····
}
drawOval(...) {
··· ·····
}
·········· ····
Este método se ejecuta cada vez que se precisa pintar el contenido de la ventana (objeto
Pista) donde los puntos compiten.
Para dibujar las circunferencias que representan a los puntos, usa los métodos de otro
objeto de tipo distinto cuya referencia debe recibir en el parámetro.
Véase cómo accede a los métodos drawLine(...) y drawOval(...) de un objeto de la clase
Graphics.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 148
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Poco a poco podemos apreciar cada vez mejor la equivalencia entre las clase definidas
por un programador y las que java aporta de manera predefinida que hasta ahora
habíamos venído llamando tipos referenciados.
clase String
Atributos capaces de
almacenar frases
Objeto de tipo String
Métodos() para
manejarlas
atributos:
valor...
“una frase”
Objeto de tipo String
métodos() para
manejar el objeto.
atributos:
valor... “otra frase”
Atributos para
referenciar los
objetos insertados
en la tabla
Métodos() para
gestionar los
objetos un la tabla.
Atributos:
x 20
diagonal
y 110
10
Métodos() para
acciones típicas
de los puntos.
métodos() para
manejar el objeto.
clase array de
objeto tipo Puntos
Objeto tipo Punto
Objeto tipo Punto
Objeto tipo Punto
Atributos:
diagonal
x 12
y 11
15
Atributos:
x 220 diagonal
y 120
30
métodos() para
controlar este
objeto punto.
métodos() para
controlar este
objeto punto.
Atributos para
describir puntos:
x , y , diagonal
Métodos() para
acciones típicas
de los puntos.
Objeto tipo Punto
Atributos:
x 105 diagonal
y 115
45
métodos() para
controlar este
objeto punto.
objeto de tipo array de
objeto tipo Puntos
Atributos:
CLASE
OBJETO
clase Punto
Objeto tipo Punto
Atributos:
diagonal
x5
y8
5
métodos() para
controlar este
objeto punto.
Objeto tipo Punto
Atributos:
x 350 diagonal
y 30
18
métodos() para
gestionar los
objetos de esta tabla.
métodos() para
controlar este
objeto punto.
Obsérvese cómo las clases actúan como moldes para crear objetos.
Hasta el momento no hemos usado más que una mínima parte de las clases predefinidas
que están disponible en java para que cualquier programador pueda aprovecharlas y
quizá aún no se comprenda totalmente su forma de actuar. Pero cualquiera puede ya
distinguir a estas alturas que en ...
“Hola Carola”.length()
... estamos ejecutando el método length() perteneciente a un objeto de clase String (que
como ya vimos se puede crear, sin usar new) que retorna como resultado el número de
caracteres del objeto String desde el cual se le llama.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 149
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Identificadores estáticos
Supongamos que en el ejemplo de la carrera de puntos, quisiéramos que los objetos tipo
Punto tuvieran un mismo diámetro, igual para todos los objetos de la clase.
Seguramente no sería adecuado que cada objeto almacenase su propio atributo
diametro, no sólo por ser innecesario sino porque al duplicar la información de su
común diámetro en cada objeto Punto podría ocurrir que, por error, alguno acabase
teniendo un tamaño diferente a los demás.
En estos casos es donde cobra sentido el modificador static, que cuando precede a la
declaración de cualquier elemento de una clase, hace que éste sea único y común para
todos los objetos instanciados de la clase. Es decir, no se crea una copia del
identificador en cada objeto sino que existirá una sola vez dentro de la propia clase.
public static class Punto {
static int diametro;
int x,y;
····· ·· ···· ·· ··
···· ···· ··· ·· ·
}
clase Punto
Declaración de identificadores
y atributo (estatico): diametro
10
declaración del identificador
del método avanzar() típica
de los objetos tipo Punto.
objeto_b
objeto_a
Atributos:
x 20
y 110
Métodos:
Atributos:
x 250
y 10
Métodos:
avanzar()
avanzar()
Esto hace posible que el identificador pueda ser referenciado a través del nombre de la
propia clase (además de a través de cualquiera de los objetos instanciados de esa clase).
En el ejemplo de la carrera de puntos, la función preparadosListo(...) ya no tendría que
asignar un valor de diámetro a cada punto. Con hacerlo una sola vez, inicializa el
atributo estático diámetro compartido de todos los competidores.
······ ··· ·· · ··· ·········
public static void preparadosListos(Punto[] competidores) {
// Una sola vez antes de que se utilice. Con tamaño 10 para todos los puntos.
Punto.diametro = 10;
//o bien competidores[<algún_índice_válido>].diametro = 10;
for(int i=0; i<competidores.length; i++) {
competidores[i] = new Punto();
competidores[i].x = 20-competidores[i].diametro; // alinea su lado derecho
competidores[i].y = 30+i*15;
}
}
·· · ······ ·· ·······
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 150
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
De igual modo la función avance(...) original (la del primer código) se podría sustituir...
·-·-·
competidores[i].diametro
·-·-·
... allí donde aparezca y en su lugar escribir...
·-·-·
Puntos.diametro
·-·-·
... pues lo mismo dará poner lo uno que lo otro.
Generalizando, cualquier identificador de un elemento perteneciente a alguna clase
puede ser declarado estático y esto implica que aún siendo miembros de la clase, no
forman parte de los objetos instanciados (creados) de ella.
La principal ventaja de asociar identificadores a la clase en lugar de a cada objeto
instanciado es la posibilidad de acceder a ellos, usando el nombre de la clase que los
contiene, sin que sea necesario que exista ni un sólo objeto instanciado de la clase.
Anteriormente hemos tenido ya ocasión de usar identificadores estáticos.
Por ejemplo de la clase System su atributo out, o su método exit(...).
- out es un identificador estático de la
clase System que representa la salida
estándar (habitualmente la pantalla)
por la que el programa escribe sus
mensajes. Referencia un objeto de una
clase que dispone de funciones como
print(...), println(...), etc...
- De forma similar el método estático
exit(...) se usa directamente desde la
clase System, haciendo que la
aplicación termine inmediatamente.
clase System
todos sus
identificadores
estáticos
···· ·· ··· ····· ····
static … out
········ ··· ·· ·· ·
static … exit(...)
objeto de una clase
que representa la
salida estandar
varios elementos
miembro del objeto
··· ·· ·····
· ·· ····· ·· ······
… print(...)
… println(...)
···· · ··· ·····
nota.- De hecho System es una clase de la que no se instancian objetos (sólo hay un sistema en el que
se ejecuta el programa y la clase System lo describe) y todo su contenido es static, de modo que
todo se accede mediante expresiones del tipo... System.<identificador_de_algo> .
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 151
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Declaración y ámbito de identificadores
Hemos hablado de cómo se declara una clase, cómo una función o método y cómo un
atributo. Pero no se ha aclarado, dónde, en qué zona del código fuente se declaran.
Para las sentencias declarativas (para crear nuevos identificadores), hay dentro de la
clase que estemos definiendo, dos sitios posibles donde ponerlas y dependiendo del
lugar, estos identificadores tendrán un ámbito (o área) desde el cual podrán ser útiles:
Tengamos presente que el elemento para el que declaramos el identificador, podría
tratarse tanto de un atributo, como de un método, o incluso ser una clase declarada
dentro de la otra clase.
1. Dentro de una clase (pero fuera de sus métodos) como elemento de dicha clase...
... si es estático el identificador empieza a existir desde que se utiliza por primera
vez la clase que lo contiene.
Podrá ser usado desde cualquier lugar desde donde pueda usarse la clase que lo
contiene.
nota.- En ambos casos existen otras cuestiones que pueden afectar a la capacidad de que los
identificadores de una clase puedan ser utilizados, pero se estudiarán más adelante.
... si el identificador no es estático el identificador empieza a existir cada vez que
se crea un nuevo objeto de la clase y existe mientras el objeto del que forma
parte existe.
Puede ser usado desde cualquier sentencia que pueda hacer uso del objeto que lo
contiene, pero no desde los métodos estáticos de la propia clase que ya existen
antes de que se hubiese creado ningún objeto de su clase.
nota.- Recuerda que desde un método estático sólo pueden utilizarse elementos estáticos.
Los elementos de una clase (atributos, clases o métodos) que hayan sido declarados
estáticos, así como todos los elementos que estos contengan, sólo podrán hacer
uso de los identificadores de la clase que también sean estáticos (además, claro
está, de los identificadores de otras clases ajenas a la suya a las que tengan acceso).
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 152
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
public static class Punto {
static int diametro;
int x,y;
··· ···· ·····
Correcto
Incorrecto
·· ···· ··
static void duplicarTamaño() {
diametro*=2;
}
···· ···· ··· ·· ·
}
// o también CORRECTO
·· ···· ··
void duplicarTamaño() {
diametro*=2;
}
···· ···· ··· ·· ·
}
···· · ···· ······
static boolean avanzar(int potencia) {
····· ··· ···
// ERROR x es no es estática
if( x +diametro<430) {
x +=(int)(potencia*Math.random());
aunNoHaLlegado=true;
··· ···· ·······
}
······ ···· ···
}
- Este método no podría ser estático, porque
necesita usar un elemento no estático.
Evidentemente el caso contrario si que es posible; desde cualquier elemento que
no es estático (interno a un objeto de la clase) si pueden usarse los elementos
declarados estáticos que son por tanto comunes a todo objetos de la clase.
class A {
// CLASES ********
class AlojadaNoEstatica {
// cualquier identificador no estático
};
static class AnidadaEstatica {
// cualquier identificador estático o no estático
};
// ATRIBUTOS *****
<tipo> atributo;
static <tipo> atributoEstatico;
// METODOS *******
<tipo> metodo(<parametros>) {
// ident. de clases y objetos locales a esta ambito (o zona).
}
static <tipo> metodoEstatico(<parametros>) {
// ident. de clases y objetos locales a este ambito (o zona).
}
};
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 153
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
clase A
Declaración de identificadores no estáticos
objeto uno (de clase A)
clase AlojadaNoEstatica
·
clase AnidaEstatica
A.AnidaEstatica
identificadores
no estáticos
identificadores
estáticos y
no estáticos
A.atributoEstatico
uno.atributoEstatico
dos.atributoEstatico
atributo
atributoEstatico
<tipo> metodoEstatico (<parametros>)
A.metodoEstatico(…)
uno.metodoEstatico(…)
dos.metodoEstatico(…)
}
metodo (<parametros>) {
// identificadores locales
// no estáticos
<tipo>
{
}
// identificadores locales
// estáticos y no estáticos
uno.atributo
uno.metodo(...)
objeto dos (de clase A)
clase AlojadaNoEstatica
·
identificadores
no estáticos
dos.atributo
atributo
dos.metodo(...)
<tipo> metodo (<parametros>)
{
// identificadores locales
// no estáticos
}
Las clases declaradas como elementos de otra clase se conocen como:
o clases anidadas... si se declaran estáticas. Estas clases pueden contener tanto
elementos estáticos, como no estáticos. Y podrán ser usadas desde otros de la
propia clase, así comodesde otras clases.
ejemplo.- La clase Rectangle2d (de la libraría de geometría de java) dipone de
dos clases anidadas en dentro de ella, la clase Rectangle2d.Float y la
clase Rectangle2d.Double (no confundir con las clases Float y Double).
Con ellas podemos manejar datos que se comportan como rectángulos y con
los que podemos:
- calcular el área común a dos áreas rectángulares.
- consultar si un punto está contienido en el área del rectángulo.
- etc...
Los
cálculos
son
más
exactos
con
Rectangle2d.Double
que
con
Rectangle2d.Float, debido a que el primero realiza sus cálculos con
números double, mientras que el segundo lo hace usando números float.
o clases alojadas... cuando no se declaran estáticas. Sólo podrán contener
elementos no estáticos.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 154
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Las clases alojadas, no pueden ser utilizadas a través de un identificador de
objeto. Por lo que estas clases, al estar dentro de un objeto, no pueden ser
directamente referenciadas desde otro objeto distinto del que las aloja.
nota.- Para hacerlo es necesario hacerlo indirectamente usando la herencia de clases que
más adelante se estudiará.
·· ···· ······
class UnaClase {
······ ··· ····· ··
static class Anidada {
··· ·· ··
}
class Alojada {
··· ·· ··
}
· ······· ··· ·····
Correcto
·· ···· ······
Anidada objeto1=new Anidada();
Alojada objeto2=new Alojada();
}
·· ···· ······
class OtraClase {
·· ···· ······
· ···· ···
UnaClase.Anidada objeto3=
new UnaClase.Anidada();
······ ·· ··· ····
}
Incorrecto
· ···· · ··· ·····
}
·· ···· ······
class OtraClase {
·· ···· ······
· ···· ···
UnaClase obj=new UnaClase();
obj.Anidada objeto4=
new obj.Anidada();
UnaClase.Alojada objeto5=
new UnaClase.Alojada();
obj.Alojada objeto6=
new obj.Alojada();
·· ···· ······
}
2. Dentro del cuerpo de un método, como identificador local del método ...
El identificador empieza a existir cada vez que se ejecuta el método en que se declara y
desaparece cuando su ejecución finaliza.
Su utilidad es la de ser utilizado exclusivamente desde las sentencias del propio método
y siempre que sean posteriores a la propia declaración.
Si se declara dentro de un bloque de instrucciones su ámbito de existencia quedará
limitado, no sólo al método en el que se declara sino más estrictamente, al bloque de
instrucciones en el que se haya declarado.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 155
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
·· ···· ······
boolean avanzar(int potencia) {
// aquí aún no se pueden utilizar ninguno de los identificadores locales.
··· ··· ··· ·· ·
<tipo> <identificador_local_al_método>;
··· ··· ··· ·· ·
// aquí sólo se puede utilizar el local al método (ya declarado).
··· ··· ··· ·· ·
// bloque de sentencias
{
<tipo> <identificador_local_al_bloque>;
··· ··· ··· ·· ·
// aquí se pueden utilizar ambos identificadores.
}
··· ··· ··· ·· ·
// aquí sólo se pueden utilizar el local al método.
· ······· ··· ·····
return aunNoHaLlegado;
}
- El modificador static sólo tiene sentido para los identificadores de los elementos
miembro de las clases, nunca para los identificadores locales de los métodos.
- Dentro del cuerpo de un método no se puede declarar directamente otro método
(a no ser que vaya dentro de otra clase).
Correcto
Incorrecto
·· ···· ······
boolean avanzar(int potencia) {
boolean aunNoHaLlegado;
······ ··· ····· ··
class <ident_de_clase>
... <ident_de_función>(...) {
··· ·· ··
}
}
· ······· ··· ·····
return aunNoHaLlegado;
·· ···· ······
boolean avanzar(int potencia) {
boolean aunNoHaLlegado;
······ ··· ····· ··
... <ident_de_función>(...) {
··· ·· ··
}
· ······· ··· ·····
return aunNoHaLlegado;
}
}
- Como se puede observar, la única declaración que no puede ir directamente entre
las llaves de un método, es la declaración de otro método.
public class A { // identif. de clase
… dato; // identificador de dato
// atributo de la clase A
… acción(...) // identificador de
{
// método de la clase A
… dato_Local_al_método ;
class B {
........
................
};
}
··· · ···· ····
{
… dato_Local_al_bloque;
··· · ···· ····
}
class C {
........
................
};
};
Autor : FernandoToboso Lara
class D {
........
................
};
o
Ficher
en un
ente
u
F
o
Códig
)
(A.java
e-mail: [email protected]
Pag. 156
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Como nota aclaratoria, se habrá observado que los propios programas que hemos
estado escribiendo en los ejemplos son también clases (todos comienzan por la class),
lo único que realmente las hace especiales es que disponen de un método con este
prototipo:
static void main(String[] arg) ...
.... . ... ...
Por último, cualquiera que sea el ámbito en el que se declare un identificador, o en el
que se instancie un objeto, no debe resultar extraño que sus tipos sean... la propia clase
dentro de la que se declare el identificador, o se instancie el objeto.
El siguiente caso es perfectamente correcto, si se ajusta a lo que queremos implementar:
public class ModuloUnicoDeMiPrograma {
· · ·· ······ ··
ModuloUnicoDeMiPrograma atributo = new ModuloUnicoDeMiPrograma();
· ···· · ······ ··
static void main(String[] arg) {
ModuloUnicoDeMiPrograma variableLocal = new ModuloUnicoDeMiPrograma();
·· ··· ········· ··· ··
}
}
Inicialización
Cuando se declara un identificador de dato, se especifica el tipo de dato que éste va a
manejar, pero otra cuestión importante es saber qué valor va a tener inicialmente y en
qué momento se le asignará ese valor. También en este caso conviene diferenciar entre
los identificadores locales de los métodos y los identificadores para atributos de una
clase.
Los identificadores locales de variables (o constantes) no toman ningún valor inicial
por defecto y el compilador produce error si detecta que se intenta consultar su valor
mientras éste aún es indeterminado. Por ello es preciso que se les asigne explícitamente
algún valor (ya sea un dato fijo, o recogido de algún dispositivo de entrada como el
teclado) antes de usar su contenido.
En el caso de los identificadores para atributos, el tratamiento es diferente y un poco
más complicado.
Hay que tener en cuenta que desde el momento en que empiezan a existir poseen un
valor inicial por defecto por lo que no siempre es preciso que les asigne uno.
nota.- Los valores por defecto son los
mismos que vimos para los
elementos de las tablas.
Autor : FernandoToboso Lara
numéricos primitivos (short, int, float, ...) a ceros.
char, carácter cuyo código Unicode es cero.
boolean, a false.
de alguna clase (tipo referenciado), a null.
e-mail: [email protected]
Pag. 157
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Sin embargo, a menudo se necesita asignarles un valor inicial diferente al que tienen por
defecto y para ello existen varias opciones por las que podemos cambiarles el valor
inicial adecuado.
Para empezar, es posible asignarles un valor en el momento de su declaración (como a
los identificadores locales).
Pero además de esto, asociados a las clases y a los objetos que de ellas instanciamos
podemos declarar un conjunto de métodos especiales que iremos estudiando a
continuación y cuya peculiaridad consiste en que se ejecutan automáticamente cuando
comienza la existencia de las clases, con sus identificadores estáticos y los objetos que
de ellas instanciemos.
Son tres tipos de métodos que merecen una atención particular a los que llamaremos
respectivamente inicializador estático, inicializador de objetos y constructor.
Cumplen principalmente la función de contener aquellas instrucciones que el
programador estima que deben ejecutarse para inicializar adecuadamente los atributos
declarados.
class <nombre_de_clase> {
static {
//sentencias del inicializador estático
}
{
}
//sentencias del inicializador de objetos
<nombre_de_clase> (<parámetros>) {
//sentencias de un constructor de objetos
}
······ ··· ····· ···· ··· ····· ·····
// ··· declaración de elementos · ··
······ ··· ····· ··· ····· ··· ·····
}
Aunque los identificadores de atributo admitan que se les asigne en el momento de
declararlos un dato inicial, el uso de métodos inicializadores ofrece posibilidades de
utilizar sentencias más complejas, e incluso el tratamiento de las excepciones que se
puedan producir durante la inicialización.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 158
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Los pasos que se siguen en la creación de una clase así como en la instanciación de sus
objetos, son los siguientes:
• Cuando se instancia el primer objeto de una clase, o al utilizar por primera vez
alguno de sus identificadores estáticos... la clase va a ser creada.
1. Se localiza la clase y se carga en memoria.
2. Se les da su correspondiente valor por defecto a los atributos estáticos, o
bien en su caso el valor de la expresión que se les haya asignado en el
momento de declararlos.
3. Se ejecuta el método inicializador estático, si existe. Sus instrucciones
generalmente elaborarán los valores iniciales adecuados para los atributos
estáticos de la clase.
• Cada vez que se instancia un nuevo objeto de la clase:
4. Se reserva la memoria que necesitará para que sus atributos almacenen sus
datos.
5. Se da un valor por defecto a los atributos del objeto, o bien en su caso el
valor que de la expresión se les haya asignado en el momento de
declararlos.
6. Se ejecuta el método inicializador de objetos, si existe. Sus instrucciones
inicializarán los atributos cuyos valores iniciales sean iguales para todos
objetos de la clase. Se ejecutará cada vez que se cree un nuevo objeto de la
clase, sin embargo su uso es poco frecuente.
7. Se ejecuta un constructor, de entre los distintos constructores opcionales
entre los que se pueda elegir, para instanciar el objeto que se está creando.
Sólo se ejecuta el constructor elegido, al instanciar el nuevo objeto.
Inicializador estático (de clase)
Es un método cuyo aspecto es el de un bloque ( {…} ) de código sin nombre, sin
parámetros y sin valor de retorno (sin tipo), precedido por la palabra static que se
ejecuta automáticamente cuando se instancia el primer objeto de la clase, o se utiliza por
primera vez alguno de sus identificadores estáticos.
Se ejecuta una sola vez y en caso de existir varios, sencillamente se ejecutarían uno tras
otro en el orden en que aparezcan declarados.
Útiles principalmente para inicializar los atributos static de la clase.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 159
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
En nuestra última versión de la carrera de puntos, para que el atributo estático diametro
tome por sí mismo su valor inicial de 10 podemos declarar en la clase Punto este
inicializador:
·········
static {
diametro=10;
estáticos
}
·······
// Como tal método estático sólo puede
//
acceder
a
los
identificadores
Aunque en esta inicialización tan simple el uso del método inicializador no aportaría
ninguna mejora. Sería más sencillo asignarle el valor en el momento de la declaración:
·········
static int diametro=10;
·······
Código nativo
Una de las utilidades más interesantes de los inicializadores estáticos, es la de solicitar
(una sola vez para la clase) la carga de librerías de funciones de código nativo.
Se trata de funciones codificadas en otros lenguajes ya compiladas y agrupadas en
ficheros con formato...
<nombre_de_librería>.dll
...a los que se conoce como librerías dinámicas.
Estas funciones suelen realizar operaciones a bajo nivel (propias de dispositivos muy
concretos) por lo que son muy especificas de cada sistema para el cual se crean.
Para usarlas, tan sólo será necesario...
...solicitar la carga de la librería dinámica que contiene el código de la función deseada.
...y que alguna clase declare los métodos (anteponiéndoles el prefijo native), cuya
implementación estará contenida en una de las librerías dinámicas cargadas.
class MiClase {
static {
System.load(“<nombre_de_librería>”);
}
· ······· ····
// Sólo se codifica la cabecera de la función.
... native <tipo> <nombre_de_funcion> ( <parámetros> ) ;
···· ···· ··
}
La librería deberá estar cargada antes de que pida la ejecución del método nativo, por lo
que el inicializador estático resulta un sitio ideal para realizar esta tarea.
nota.- Para ampliar información respecto a cómo escribir en otros lenguajes librerías dinámicas de
funciones, consultar las especificaciones de la Java Native Interface de Sun y las instrucciones de
compilación propias del lenguaje elegido para implementarlas.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 160
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Inicializador no estático (de objeto)
Tiene la misma sintaxis que un inicializador estático, sólo que éste no va precedido de
la palabra static.
Se ejecuta cada vez que se crea un objeto de la clase y es útil para inicializar los
atributos no estáticos que deben tener el mismo valor inicial sea cual sea el objeto que
se instancie de la clase.
En nuestro ejemplo de la carrera de puntos, para que el atributo x tome por sí mismo el
valor inicial 20 menos lo que valga diametro (diametro ya habrá sido inicializado por el
inicializador estático) podemos declarar en la clase Punto este inicializador:
·········
{
x=20-diametro;
// Como se trata de un método no estático puede acceder tanto
// a identificadores tanto estáticos como no estáticos.
}
·······
En realidad su uso no es frecuente, ya que los constructores (que veremos a
continuación) cubren sobradamente su papel y normalmente se opta por usar estos en
lugar de un inicializador de objetos.
nota.- Se utilizan principalmente en un tipo de clases llamadas clases anónimas que aún no conocemos,
pero también pueden ir inicializando un clase normal.
Constructores (de objeto)
Su apariencia es la de un método, sólo que nunca retorna resultado, por lo que nunca se
le especifica tipo y su nombre es el mismo que el de la clase.
Puede haber más de un método constructor, todos con el mismo identificador (el
nombre de la clase) pero se diferenciarán entre sí por que reciben distinto tipo y/o
número de parámetros.
Cada vez que se instancia un nuevo objeto, tras la palabra new aparece el nombre de la
clase (o lo que es lo mismo... del constructor) con los datos iniciales entre paréntesis, de
modo que el número y tipo de éstos datos determinará cuál de los constructores se
ejecutará.
Al igual que el anterior, tienen la finalidad de inicializar los atributos de un objeto, pero
con la ventaja de que este ofrece más opciones, al poder elegir entre varias funciones
que inicialicen el objeto que en cada ocasión se instancie.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 161
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Si añadimos un par de constructores a la clase Punto, podemos simplificar la creación
de los objetos que se instancian de la clase.
Que de este modo se instanciarían e inicializaría
en una única sentencia:
puntos[i]=new Punto(30+i*15);
Ya que después de crearse el objeto, se ejecutaría
automáticamente el constructor correspondiente
que recibirá como parámetro el valor int
resultante de la expresión 30 + i * 15
··· ·········
Punto() {
y=30;
}
Punto(int py) {
y=py;
}
Punto(int px, int py) {
x=px;
y=py;
}
···· ·· ······
nota.- De entre los constructores ni el primero ni el último se ejecutarán en este caso, porque no encajan
con el formato del constructor expresado detrás del ‘new’:
... new Punto(<entero_x>,< entero_y>) ...
Y los otros atributos ya los habrá inicializado en los otros métodos inicializadores. Sin
embargo, lo más habitual es prescindir de los inicializadores de objeto (que inicializa el
atributo x) dejando que sean los constructores los que se hagan cargo de inicializar todos
los atributos.
Punto() {
x = 20;
y = 30;
}
Punto(int py) {
x = 20;
y = py;
}
nota.- Existen otras razones que justifican la necesidad de usar métodos de inicialización (no sólo la
comodidad de que el objeto se inicialice automáticamente cuando es creado) sin embargo este tema
se entenderá claramente cuando más adelante tratemos el tema de los permisos de acceso a los
atributos de una clase.
Si no se declara ningún constructor, la clase presupone que existe un constructor por
defecto sin parámetros y no ejecuta nada. Esto es algo muy importante a tener en
cuenta, porque los objetos de una clase que no dispone de ningún constructor se crean
usando new <NombreDeClase>(); sin datos incluir parámetros.
Pero sí existe algún constructor, los datos iniciales para instanciar objetos deben encajar
con los parámetros de alguno de los constructores definidos.
Si en el ejemplo de la clase Punto sólo se hubiera definido el último de los constructores
( Punto(int,int) ), sólo se hubieran podido instanciar objetos invocando a este
constructor usando por ejemplo ... new Punto(12,15); ... y nunca con new Punto();, ni
con new Punto(15);
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 162
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Todo programa Java se compone de clases
Si se revisan los códigos fuente de los ejemplos que hemos venido utilizando, se podrá
comprobar que TODO (salvo algún comentario y muy pocas líneas de código que ya
explicaremos) va entre las llaves que describen alguna clase. En efecto, en todo
programa java tendremos siempre al menos una clase y muy probablemente no será la
única.
En su esfuerzo por hacer más intuitiva la programación para los programadores, los
lenguajes intentan parecerse cada vez más a los lenguajes naturales humanos (al modo
normal de hablar de las personas) persiguiendo el ideal de que programar pudiese llegar
a consistir simplemente en explicar con nuestras propias palabras al ordenador lo qué
deseamos que haga.
Los objetos son un gran avance en esta línea, puesto que nos permiten describir de una
manera muy clara la representación de los elementos que que los programas deben
tratar.
Si la aplicación que se va a desarrollar es un procesador de textos, es fácil imaginar lo
útil que resultaría disponer de una clase Parrafo que permitiese almacenar un fragmento
de texto, junto a las propiedades características de los párrafos como alineación,
sangrado, interlineado, etc... al tiempo que dispusiese de métodos con los que poder ...
...consultar en cuantas líneas tiene colocadas sus palabras, ...modificar sus tabulaciones.
...obtener las palabras que contiene (en forma de objetos de alguna clase Palabra).
En el contexto de la programación orientada a objeto, el trabajo de los programadores
consiste en crear clases y objetos, mientras que lo que se llama tradicionalmente
aplicación, no pasa de ser un proceso en el que diferentes clases y objetos interactúan
entre sí unos con otros, ejecutando mutuamente sus métodos y modificado los valores
de sus respectivos atributos.
La ejecución de un programa, se iniciará en una de sus clases que contendrá el método
principal, cuyo prototipo es como ya hemos visto:
static void main(String[] arg) ...
Por esto, cuando se arranca una aplicación java, se le indica a la máquina virtual java
(JVM) el nombre de la clase que contiene dicha función y que hasta ahora hemos venido
identificando como “nuestro programa”.
C:\...\> java
_
nombre_de_clase_con_main()
nota.- Si se utiliza un enfoque abstracto, un programa podría ser entendido como un objeto más (de entre
los varios que ejecuta un ordenador) cuyas funciones se invocan desde otros objetos programa.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 163
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
En el código fuente de las versiones del ejemplo de la carrera, se puede observar como
el peso del programa va recayendo cada vez más en los métodos de los objetos de la
clase Punto y menos en las funciones ...avance(...) y ...preparadosListos(...) que se
hacen cada vez más cortas y sencillas.
En ese código, la clase Carrera, da nombre al programa (por contener la función
main(...)) es una clase como cualquier otra. Sólo que todos sus elementos se han
declarado estáticos para poder usarlos sin necesidad de instanciar objetos de la clase
(aunque si se deseara, podría hacerse).
En un programa pueden existir tantas clases como sean necesarias y cada cual podrá
estar declarada como una clase independiente de las demás. Lo que justifica el hecho de
declarar un elemento como miembro de una determinada clase, es normalmente el
hecho de que su significado esté de algún modo ligado al contexto de dicha clase.
En el caso de la clase Punto, se ha declarado anidada dentro de la clase Carrera para
hacer más simple el comienzo de manual, manteniendo la apariencia de que la clase que
contenía todo era “nuestro programa”.
Probablemente fuese más adecuado declarar la clase Punto como una clase
independiente y en un fichero separado.
//
Fichero: Punto.java
// Clase que describe como es cada punto
// *************************************
public class Punto {
static int diametro;
int x,y;
······ ··· ···
·· ······· ······
}
Generalmente cada una de las clases independientes (no anidadas) de un programa se
declaran en diferentes ficheros con idéntico nombre al de la clase que contienen.
En java todos estos ficheros contenedores de clases, se suelen ordenan formando grupos
de clases a modo de utilidades, de forma que es común disponer de gran número de
clases con las que poder desarrollar nuevas clases (y programas), tal como veremos
después.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 164
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Inicio de la ejecución de un programa
Cuando se ejecutan los programas de los ejemplos, lo hacemos ejecutando la JVM (Java
Virtual Machine), o máquina virtual Java (C:\> java ...) seguida del nombre de la clase
que debe contener el método estático main(...).
nota.- En caso de que hubiese más de un método principal, se ejecuta el de la clase especificada a la JVM.
Otro posible código para nuestro ejemplo hubiera podido declarar una nueva clase con
el único interés de contener la función principal y cuya única acción sería instanciar un
objeto de la clase Pista (que como ya se comentó, se encarga de controlar la ejecución
de la carrera).
class OtroPrincipal {
public static void main(String[] args) {
new Pista();
}
};
Con lo que una vez compilado, el programa podrá ejecutarse dándole a la máquina
virtual java indistintamente esta clase:
c:\> java OtroPrincipal ↵
c:\> _
...o ésta otra:
c:\> java Carrera ↵
c:\> _
Obsérvese que dado que la función main(...) es siempre estática, no precisa que exista
ningún objeto instanciado de su clase para ser ejecutada.
Por otra parte no todas las aplicaciones precisan de función principal.
Cuando iniciábamos el curso se mencionó un tipo de programación no imperativa sino
orientada a la ocurrencia de determinado eventos.
Es el caso de los programas insertados en paginas web (conocidos como applets) que
son ejecutados por mediación de un navegador de páginas web.
En la ejecución de estos programas applets, no se utiliza el método main(...) sino otros
distintos que se deben encargar de inicializar adecuadamente la ejecución del applet.
Pero dejemos para otro momento los applets y sigamos por el momento con la
programación de aplicaciones ajenas a la web.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 165
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Capitulo 11
Paquetes y
Permisos de
Acceso
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 166
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Utilización
Un paquete es una agrupación de clases, entre las que existe algo en común, al cual se
asigna un nombre que suele dar una idea del tipo de tarea que desempeñan.
Por convenio, los identificadores de los paquetes se escriben con letras minúsculas, para
poder distinguirlos fácilmente de las clases (que suelen empezar por mayúscula) y
puede constar de varios nombres unidos por puntos, por ejemplo java.awt.event es el
nombre de un paquete.
- Los nombre de los paquetes hacen referencia a los directorios que deben contener
los ficheros compilados (*.class) de cada una de las clases.
- Todas las clases que forman parte de un mismo paquete deben estar en el mismo
directorio.
- CLASSPATH es una variable de entorno del sistema, que establece la posición
absoluta de los directorios donde se encuentran todas la clases que usamos.
Cuando estas clases pertenecen a algún paquete, deberán estar guardadas (a partir
de uno de los directorios inidicado por CLASSPATH) en un subdirectorio cuyo
nombre (o pathname) coincida con el nombre de su paquete.
-
-
Jerarquia de Directorios
Paquetes
lib
java
awt
directorio
actual
java
java.awt
event
io
java.awt.event
java.io
lang
util
java.lang
java.util
jar
sun
jdbc
odbc
java.util.jar
otra_libreria
mios
util
videoclub
gestionprestamo
mios
mios.util
mios.videoclub
mios.videoclub.gestionprestamo
sun
sun.jdbc
contabilidad
mios.videoclub.contabilidad
entornografico
mios.videoclub.entornografico
sun.jdbc.odbc
stock
mios.videoclub.stock
En el gráfico la variable de entorno CLASSPATH contiene los nombres de los directorios
del árbol de directorios (···).
- ···/lib
- ···/otra_libreria
- .
(directorio actual)
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 167
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
En los que la JVM (Java Virtual Machine) buscará las clases que necesite, cada cual en
el directorio correspondiente al paquete al que pertenezca.
Los nombres unidos por puntos que se asignan a los paquetes se corresponden
directamente con la jerarquía de directorios en que se guardan las clases. Así pues las
clases del paquete ...
mios.videoclub.contabilidad
... deben estar almacenadas en el directorio:
<classpath>\mios\videoclub\contabilidad
nota.- Aquí se usa la barra ‘\’ separadora de directorios de MsDos.
Adaptar en cada caso a cada Sistema Operativo (Unix, Mac, etc...)
<classpath> debe ser una de las rutas de directorio que guarda la variable CLASSPATH.
Siguiendo el ejemplo del gráfico y suponiendo que se necesitase usar la clase
ClaseBuscada perteneciente a dicho paquete, la JVM la buscará en los sitios referidos
por CLASSPATH.
- primero
→ ···\lib\mios\videoclub\contabilidad\ClaseBuscada.class
- después
→ ···\otra_libreria\mios\videoclub\contabilidad\ClaseBuscada.class
- y finalmente → mios\videoclub\contabilidad\ClaseBuscada.class
Realmente esto no siempre es así, dado que a menudo (para optimizar su tamaño) la
estructura de directorios con los fichero *.class que contiene, se comprime en un sólo
fichero (*.jar) desde el cual los paquetes de clases se pueden usar directamente (la
máquina virtual java descomprime en el momento que oportuno, el código de las clases
que necesita).
Este suele ser el caso de los paquetes estándar de java, por lo que probablemente no
encontrarás en el ordenador los directorios correspondientes a esos paquetes.
La jerarquía de directorios (nombres unidos por puntos del paquete) permite que los
paquetes formen una jerarquía de paquetes que facilita su clasificación.
Los paquetes java.awt.image, java.awt.font, java.awt, ... son todos ellos diferentes y
cada uno de ellos agrupa varias clases, todas ellas relacionadas con el entorno gráfico
(awt son las iniciales de abstract window toolkit).
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 168
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
La interface de programación de aplicaciones (API) de Java versión 1.3 de Sun dispone
de más de 5.000 diferentes clases de objetos en una librería agrupada en un total de 76
paquetes, como los siguientes:
-
java.lang
javax.swing
java.awt
java.awt.event
java.applet
-
java.io
java.math
java.net
java.util
java.util.zip
java.util.jar
sun.jdbc.odbc
etc...
Clases básicas para el diseño del lenguaje Java (System, String, ...)
Agrupando clases relacionadas con los gráficos y componentes como
botones, ventanas, etc... (JFrame, Window, IOException, KeyAdapter, ...)
Clases para la realización de aplicaciones ejecutables (Applets) desde un
navegador web, tipo Netscape o Explorer
Clases para la gestión de entrada y salida de datos (File, InputStream, ...)
Clases para manejo de datos numéricos muy grandes
Clases útiles para conexiones vía red (Socket, URL, ... )
Clases con utilidades diversas (Time, Date, Vector, ...)
Clases para de gestión de ficheros comprimidos (*.zip)
Clases para gestión de ficheros de clases comprimidos (*.jar)
Clases para comunicación con gestores de bases de datos
....
Los identificadores completos para nombrar clases como System, Window, Integer, ... de
la librería de paquetes estándar de java, serían realmente java.lang.System,
java.awt.Window, java.swing.JFrame, java.lang.Integer, ...
nota.- Window y JFrame, son dos clases pertenecientes respectivamente a los paquetes java.awt y
java.swing y relacionadas ambas con la creación de ventanas del entorno gráfico.
Si esto no se hace generalmente es porque se utiliza la sentencia import para informar al
compilador de los paquetes en los que están las clases que se usarán. De modo que tras
escribir ...
import java.swing.JFrame;
... sólo será preciso nombrar la clase usando su propio identificador dentro del paquete.
La clase una vez importada del paquete java.swing puede ser utilizada simplemente
como JFrame (aunque también podríamos usar el identificador completo).
La sentencia import debe escribirse fuera de cualquier clase y su sintaxis nos permite
dos opciones:
- Importar una clase específica del un paquete (como hemos visto).
- O bien, importar todas las clases accesibles de un paquete, escribiendo un asterisco
en el lugar de la clase:
import java.swing.*;
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 169
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Atención al caso de que varias clases tengan el mismo identificador (cada cual en un
paquete distinto).
Al importar ambos paquetes será preciso nombrarlas usando sus identificadores
completos ( <nombre_paquete> . <nombre_clase> ) para evitar ambigüedades.
Si se importan dos clases con igual nombre y se las usa sin el identificador completo se
producirá un error de compilación puesto que el compilador no sabrá a cuál de las dos
se refiere.
Al importar un paquete no se importan los ‘subpaquetes’, las clases de cada cual se debe
importar explícitamente, puesto que en realidad se trata de paquetes distintos.
import java.awt.image.*;
//todas las clases del paquete java.awt.image
import java.awt.font.TextLine;
//la clase TextLine del paquete java.awt.font
import java.awt.*; //todas las clases del paquete java.awt
Desde cualquier clase, existen tres paquetes cuyas clases se consideran importadas por
defecto:
-
Las clases del paquete java.lang
-
Las clases del paquete sin nombre (formadas por aquellas que no se declaran
pertenecientes a ningún paquete y colocadas por tanto directamente en uno de
los subdirectorio indicados por CLASSPATH)
-
Las clases del paquete actual, al cual pertenece la clase que está siendo
utilizada (interpretada, o compilada). Si ésta, no ha declarado pertenecer a
ningún paquete, obviamente estará ya importada por pertenecer al paquete sin
nombre.
nota.- CLASSPATH debe contener a menos la ruta del subdirectorio donde está el paquete
java.lang, básico para poder compilar o ejecutar programas java.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 170
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Creación
También un programador de java puede crear sus propios paquetes de clases.
Para ello simplemente, deberá:
- Incluir como primera sentencia del código fuente (sin contar comentarios y líneas
en blanco), package seguido del nombre de paquete en el cual se quieran incluir
nuestras clases tras haber sido compiladas.
nota.- Recuerda que en ocasiones la compilación de un sólo fichero *.java puede generar varios
ficheros *.class (uno por cada clase definida en el código fuente).
En tal caso todas las clases pertenecerán al mismo paquete.
- Y después colocar los ficheros resultantes (*.class) en un subdirectorio que se
corresponda con el nombre dado al paquete (a partir de uno de los directorios
señalados por CLASSPATH), de modo que la JVM pueda encontrar la clase compilada
en alguno de los directorios convenidos.
nota.- Si la clase no forma parte de ningún paquete pertenecerá al paquete por defecto y se guardará
directamente en uno de los directorios referidos por CLASSPATH, que es donde la JVM la
buscará.
Se debe prestar especial atención para que el directorio en el que pongamos los
subdirectorios de nuestros paquetes cuelguen de alguno de los emplazamientos referidos
por CLASSPATH.
Una práctica muy recomendable para organizar los ficheros con las clases java que
escribimos consiste en colocarlos desde un principio en los diferentes subdirectorios
correspondientes a sus paquetes, pero duplicando la estructura de directorios para
colocar por separado los ficheros fuentes (*.java) y los ficheros ya compilados (*.class).
clasesCompiladas
empresaTururu
Autor : FernandoToboso Lara
codigosFuente
empresaTururu
utilidades
videoclub
gestionprestamo
utilidades
videoclub
gestionprestamo
contabilidad
contabilidad
entornografico
entornografico
stock
stock
e-mail: [email protected]
Pag. 171
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Tengase en cuenta que cuando el compilador traduce el código de una clase, necesita
localizar el código de las clases a las que ésta hace referencia, para comprobar que las
utiliza adecuadamente. Y para localizar el código de estas clases, actúa de la siguiente
manera:
busca
(en los subdirectorios indicados por CLASSPATH)
el fichero compilado de la clase que necesita chequear
(incluso en los paquetes comprimidos *.jar)
SI
lo
encuentra
NO
busca
(en los subdirectorios indicados por CLASSPATH)
el fichero con los fuentes de la clase que quiere chequear
NO
lo
encuentra
y ha sido modificado
mas recientemente
que el
compilado
SI
SI
lo
encuentra
lo compila
NO
ERROR
revisa la sintaxis de la clase para comprobar
que esta siendo correctamente utilizada
Puede resultar preferible que CLASSPATH sólo indique la estructura de directorios de las
clases compiladas.
nota.- Un error típico suele producirse cuando el compilador encuentra un fichero erróneo con el nombre
de alguna de las clases que busca (antes de dar con el auténtico) y al utilizarlo para comprobar su
corrección nos informa de errores inesperados.
De forma similar, el intérprete necesita localizar y cargar el código de aquellas clases
que participan en la aplicación que ejecuta. Sin embargo, éste sólo busca ficheros de
código ya compilado (*class y *.jar).
Otra opción que ofrece esta forma de organizar los ficheros, es la posibilidad de impedir
o limitar el acceso a los ficheros .java con el código abierto (no compilado), sin que ello
impida que se permita el acceso a las clases en los ficheros .class con el código
compilado.
Esto facilita que un programador pueda ocultar (si así lo desea) el código fuente que
desarrolle y evitar así que se conozcan los detalles del código fuente de sus clases.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 172
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Unicidad de identificadores.
Siempre que los identificadores dados a los paquetes sean únicos podremos usar sin
problemas clases hechas por programadores distintos sin temor de que una parte del
código interfiera en otra por usar nombres iguales, puesto que anteponiéndoles el
nombre del paquete al cual pertecen se resolvería cualquier problema relativo a nombres
ambiguos.
Técnicamente cada paquete tiene un contexto independiente para los identificadores de
sus clases, de forma similar a cómo en el contexto de cada clase los identificadores de
sus elementos (atributos, métodos, o clases) se distinguen de los que pertenecen a otras
clases, aunque elijamos para ellos el mismo nombre.
java.util.Date
fecha = new ... ;
java.sql.Date
fecha = new ... ;
java.awt.Point
puntoUno = new ... ;
java.awt.Point
puntoDos = new ... ;
java.awt.Rectangle
rectangulo = new ... ;
Float
numeroReal = new ... ;
// java.lang.Float
java.awt.geom.Rectangle2D.Float
rectanguloExacto = new ... ;
java.awt.geom.Arc2D.Float
arcoExacto = new ... ;
Double
numeroRealMuyExacto = new ... ;
// java.lang.Double
java.awt.geom.Rectangle2D.Double
rectanguloMuyExacto = new ... ;
java.awt.geom.Arc2D.Double
arcoMuyExacto = new ... ;
··· · ·····
·· ··· ····
puntoUno.x;
puntoDos.x;
// Los atributos
rectangulo.x;
//
sólo tienen en común el identificador
x
de estos objetos
rectanguloExacto.x;
//
elegido para ellos
rectanguloMuyExacto.x;
//
similitud en el concepto que representan.
arcoExacto.x;
// Estos atributos x,
arcoMuyExacto.x;
//
x
por existir gran
ni siquieran son del mismo tipo.
- Podemos ver cómo existen en la librería estándar de java dos clases llamadas Date,
pero en diferentes paquetes y con diferentes implementaciones (aunque ambas
representan una fecha).
- Del mismo modo hay varias clases llamadas Float y Double, de las cuales unas son
independientes (las del paquete java.lang), mientras que las otras se encuentran
anidadas en diferentes clases (del paquete java.awt.geom), por lo que nunca se las
confundirá.
- Tampoco es posible confundir los diferentes atributos x, porque pertenecen a
diferentes objetos o a clases diferentes.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 173
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Un modo de asegurar la unicidad mundial en los nombres de los identificadores,
consistiría en adherirse al estándar de nombres de dominio de internet, de modo que los
programadores respetasen la norma de prefijar los nombres de sus paquetes de un
nombre de dominio que hayan reservado en internet, pero invirtiendo el orden de los
dominios.
<nombre_de_dominio_invertido>.<nombre_uno_de_mis_paquetes>
Por ejemplo, si mi empresa tiene reservado el dominio www.soft.videodrome.es , sus
programadores deberían nombrar a su paquete videoclub.contabilidad (probablemente
clases java con las que programar la gestión contable de un videoclub) como
--- es.videodrome. soft.www.videoclub.contabilidad --cosa que nadie respetaría por engorroso, desproporcionado y poco atractivo
comercialmente. No obstante simplificando un poco el formato (eliminando el www e
incluso el distintivo del país)
--- videodrome. soft. videoclub.contabilidad --facilitaría que hubiese distintas empresas creando software en java para la contabilidad
de un videoclub...
--- videoclub.contabilidad --...sin necesidad de reñir por usar el mismo nombre en sus paquetes.
Resumiendo, algunas ventajas por las que debemos usar paquetes, son:
-
Facilidad en la localización de clases, puesto que cada paquete agrupa aquellas
clases que tienen algo en común. Por ejemplo en el paquete graficos podrían
estar agrupadas una serie de clases como Circulo, Ventana, Figura, Rectangulo,
Grafico, etc...
-
Evita que puedan existir conflictos entre los identificadores de clases de
diferentes aplicaciones, puesto que dentro de cada paquete los identificadores se
diferencian perfectamente de los identificadores de otros paquetes.
Además como se verá más adelante, para hacer uso de los elementos de una clase
existen una serie de restricciones de acceso. Los paquetes crean un ámbito en el que las
clases agrupadas tienen la posibilidad de cooperar más íntimamente entre sí, porque
existen permisos específicos de acceso a los elementos de una clase cuando se usan
desde otras clases agrupadas en un mismo paquete.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 174
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Permisos de acceso
Necesidad de controlar niveles de accesibilidad
Como hemos podido observar, las clases suelen definir métodos a través de los cuales
podemos modificar apropiadamente el valor de sus atributos. Supongamos la siguiente
clase PuntoAcotado en la que modificando las coordenadas con los métodos arr(), abj(),
... , se controla que el punto no salga de los límites indicados en el método constructor.
El constructor inicializa los valores tope de
las coordenadas para cada objeto que
construye. El valor de las coordenadas x e y,
son modificados a través de una serie de
métodos de desplazamiento, que
comprueban antes de modificar nada que la
nueva posición sea válida (esté entre 1 y el
límite impuesto).
// Fichero: PuntoAcotado.java
class PuntoAcotado {
int x=1,y=1;
int xLim,yLim;
PuntoAcotado (){
xLim=10;
yLim=10; }
PuntoAcotado (int xL, int yL) {
xLim=xL;
yLim=yL; }
boolean esvalido(int px, int py) {
return (
px>=1 && px <=xLim
&& py>=1 && py <=yLim ) }
void izq() {
if(esvalido(x-1,y))
x--; }
void der() {
if(esvalido(x+1,y))
x++; }
void arr() {
if(esvalido(x,y-1))
y--; }
void abj() {
if(esvalido(x,y+1))
y++; }
.. ...... ..
PuntoAcotado p=new PuntoAcotado(20,20);
p.arr(); // no modifica nada porque
// comprueba que se saldría
// del limite impuesto.
p.abj(); // desplaza la coordenada ‘y’
··· ···· // una posición hacia abajo
La sentencia p.arr() no modifica la posición
del objeto p, porque la función detecta que
el desplazamiento pondría el atributo y a
valor 0 (fuera del rango válido).
};
De este modo se puede definir el comportamiento de esta clase de objetos al
desplazarse, forzándolo a estar dentro de unos límites marcados (muy útil si
necesitamos coordenadas acotadas por los límites de la pantalla por ejemplo).
Pero de poco sirve esta argucia si cualquier programador puede acceder directamente a
los atributos y modificarlos libremente...
...... ..... ..............
p.x = -100; // desplaza la coordenada x (a la posición -100)
····· ··········
De este modo el objeto p se sale del comportamiento que debería tener.
Se impone por lo tanto la necesidad de restringir el acceso a los identificadores del
interior de los objetos (o clases), para impedir que se utilicen de manera ilícita.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 175
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Acceso a los paquetes
Primeramente es necesario que se disponga de acceso a los paquetes (subdirectorios) y a
los ficheros que contienen las clases que se quieran manejar, en los cuales al menos
debemos tener permiso de lectura.
nota.- Es incluso posible que estos paquetes no se encuentren en la propia máquina sino en algún
ordenador remoto desde el cual se descarguen las clases, siempre que exista una conexión adecuada
entre ellos.
Para localizar la ubicación de los paquetes se consulta la variable de entorno
CLASSPATH (a no ser que se indique un lugar distinto donde se deban buscar) tal
como se comentó anteriormente.
Una vez que se accede a la ubicación de un paquete cualquiera, existen diferente niveles
de acceso a sus clases, así como para los diversos elementos que éstas contienen, que se
indican precediendo con una de estas palabras la declaración del identificador que
corresponda:
-
public
protected
no escribiendo nada (que llamaremos por defecto)
private
Cada una de ellas impone un control que limita su manipulación, del modo que a
continuación analizamos.
Acceso a las clases (no anidadas) de un paquete
Ya sabemos que los únicos identificadores que pueden no ir declarados en el interior de
una clase, son los identificadores de clases. Cuando las clases se declaran así,
independientes, puede únicamente ser declaradas: o bien public, o bien por defecto.
- Las clases públicas pueden accederse desde cualquier clase de cualquier paquete.
- Las declaradas por defecto (sin precederlas de nivel de acceso) sólo podrán
utilizarse desde aquellas clases que pertenezcan a su mismo paquete.
En un mismo fichero fuente se pueden incluir la declaración de varias clases
(obviamente todas ellas pertenecerán a un mismo paquete), sin embargo de entre todas
ellas, sólo una de las clases no anidadas podrá ser declarada public y deberá además
tener el mismo nombre que el fichero *.java en que se encuentre (teniendo en cuenta
incluso las mayúsculas y minúsculas del nombre).
Obsérvese que nuestra clase PuntoAcotado, al haber sido declarada con acceso por
defecto sólo se podrá utilizar desde las clases de su mismo paquete.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 176
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Para hacerla accesible desde fuera del paquete habrá que declararla public.
public class PuntoAcotado {
int x=1,y=1;
int xLim,yLim;
··· ·· ·····
Por supuesto que un paquete puede exportar (ofrecer a clases de otros paquetes) más de
una clase pública, dado que podrán haber (y generalmente los habrá) muchos ficheros
fuente que declaren pertenecer al mismo paquete.
nota.- Puede darse también el caso de que un fichero fuente no contenga ninguna clase pública y contenga
exclusivamente clases para ser usadas desde las clases de su mismo paquete.
publicos
package matematica.geometria;
package matematica.geometria
public class Circulo {
· ····
·· ··
}
public class Triangulo {
· ····
·· ··
}
package matematica.geometria
···
····
···
public class Linea {
· ····
·· ··
}
por defecto
package matematica.geometria;
class UtilidadesInternas {
· ····
·· ··
}
package matematica.geometria
···
····
···
class OtrasUtilidadesInternas{
· ····
·· ··
}
De este modo las clases publicas serán las que un paquete aporta (las accesibles). Las
clases declaradas por defecto no será necesario ni siquiera darlas a conocer, puesto que
sólo se pueden usar internamente en la implementación de otras clases del paquete.
Sin embargo, de las clases a las que se tenga acceso, sólo se podrán usar los elementos
que contiene (atributos, métodos, o clases anidadas) si además se cumple con el nivel de
acceso con el que estos hayan sido declarados.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 177
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Acceso a los elementos de una clase (a la cual se tenga acceso)
Los atributos, métodos y clases anidadas, de una clase puede declararse con cualquiera
de los cuatro niveles de acceso:
- Publico (public) ... de este modo el elemento es accesible desde las instrucciones de
cualquier método (perteneciente a una clase de este u otro paquete).
- Por defecto ... los elementos se pueden usar desde las clases pertenecientes a su
mismo paquete.
nota.- Recuerda que si no declara pertenecer a ningún paquete, pertenecerá al “paquete sin nombre”.
- Protegido (protected) ... accesibles igual que por defecto, pero con un significado
especial desde un tipo de clases aún no vistas (que heredan elementos de otras, por lo
tanto hasta el capitulo de herencia no las usaremos).
- Privado (private) ... con este modo, única y exclusivamente las sentencias de la
propia clase tienen permiso de acceso.
Los niveles de accesibilidad son idénticos tanto para los elementos declarados static
(existentes en la clase) como para los no estáticos (existentes en cada objeto instanciado
de dicha clase).
Esta tabla resume los diferentes niveles de acceso:
accediendo
desde ...
especificadores
de acceso
...la misma
clase
...otra clase
del mismo
paquete
private
ACCEDE
por defecto
ACCEDE
ACCEDE
protected
ACCEDE
ACCEDE
public
ACCEDE
ACCEDE
...otra clase
de otro
paquete
...clase hija
de
otro paquete
(por herencia)
ACCEDE AL SUYO
ACCEDE
ACCEDE
Con lo que los niveles de accesibilidad (dejando al margen por ahora la última columna
de la tabla con la herencia, aún no estudiada), quedan como describe el gráfico
siguiente:
PAQUETE uno
class D {
… ......
… sentencias_de_D_de_uno...
....... .......
};
PAQUETE otro
… class C {
… ......
… sentencias_de_C_de_otro …
....... .......
};
public class P {
public…
elemento_publico_P_uno ... ;
(pordefecto) … elemento_pordefecto_P_uno
... ;
protected… elemento_protegido_P_uno ... ;
private…
···· ·········
elemento_privado_P_uno ... ;
…sentencias_de_P_de_uno ...
};
nota.- Recuerda que las clases pertenecientes a un paquete suelen estar distribuidas en varios ficheros
fuente. No obstante, la manera en que se distribuyan las clases en dichos ficheros *.java de un
mismo paquete no afecta a la accesibilidad.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 178
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
De modo que una mejora para asegurar
que nuestra clase PuntoAcotado
siempre funcione correctamente, sería
(como puede verse al lado) declarar
todos los atributos privados, accesibles
exclusivamente desde los métodos de
la propia clase.
De manera que será ilegal intentar
ejecutar desde cualquier otra clase la
sentencia...
...... ..... ..............
PuntoAcotado p=new PuntoAcotado();
.........
p.x=100; // ERROR
····· ··········
...ya que el atributo x no será accesible
más que desde sus propios elementos.
// fichero: PuntoAcotado.java
package el.que.sea;
public class PuntoAcotado {
private int x=1,y=1;
private int xLim,yLim;
PuntoAcotado (){
xLim=10;
yLim=10; }
PuntoAcotado (int xL, int yL) {
xLim=xL;
yLim=yL; }
private boolean esvalido(int px,int py){
return (
px>=1 && px <=xLim
&& py>=1 && py <=yLim ); }
int consulta_x() {
return x; }
int consulta_y() {
return y; }
void izq() {
if(esvalido(x-1,y))
x--; }
void der() {
if(esvalido(x+1,y))
x++; }
void arr() {
if(esvalido(x,y-1))
y--; }
void abj() {
if(esvalido(x,y+1))
y++; }
};
Claro que para poder consultar su valor (sin poder modificarlo), deberémos añadir
métodos accesibles, como consulta_x() y consulta_y(), para que retornen el valor de los
atributos restringidos.
El método esvalido(...) también se ha declarado private puesto que sólo tiene interés
para los métodos de la propia clase para comprobar fácilmente si la posición a la que se
pretende mover está dentro de los márgenes permitidos.
nota.- Cuidado con lo que dichas funciones retornan. En caso de que el atributo fuese de tipo
referenciado, lo que hubiéramos retornado hubiera sido la referencia al propio objeto que el atributo
referencia, con lo que se daría acceso libre para modificar (mediante la referencia retornada) el
objeto referenciado por el atributo privado.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 179
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Los métodos utilizables desde clases de
otros paquetes (en nuestro ejemplo
todos excepto esvalido()) deberán ser
declarados public.
Además el grado de protección excesivo
puede no resultar recomendable si ello
dificulta el uso de código.
Entre clases pertenecientes a un mismo
paquete se suele asumir que existe una
relación de confianza, por lo que a
menudo la protección por defecto se
considera suficiente y preferible. De
modo que finalmente la clase queda tal
como vemos aquí al lado.
Autor : FernandoToboso Lara
· ···· ··· ··
public class PuntoAcotado {
int x=1,y=1;
int xLim,yLim;
public PuntoAcotado (){
xLim=10;
yLim=10; }
public PuntoAcotado (int xL, int yL) {
xLim=xL;
yLim=yL; }
boolean esvalido(int px,int py){
return (
px>=1 && px <=xLim
&& py>=1 && py <=yLim ) }
public int consulta_x() {
return x; }
public int consulta_y() {
return y; }
public void izq() {
if(esvalido(x-1,y))
x--; }
public void der() {
if(esvalido(x+1,y))
x++; }
public void arr() {
if(esvalido(x,y-1))
y--; }
public void abj() {
if(esvalido(x,y+1))
y++; }
};
e-mail: [email protected]
Pag. 180
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Capitulo 12
Reutilización del
código
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 181
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Herencia
Los lenguajes de programación orientados a objetos utilizan esta técnica para facilitar la
reutilización del código ya escrito y poder así sacar partido mañana del trabajo de hoy.
Consiste en que al definir una nueva clase de objetos, nos apoyemos en otra clase ya
escrita, de la cual heredamos sus elementos, por lo que sólo será preciso definir aquellos
atributos, clases anidadas/alojadas, o métodos, que desemos añadir nuevos, o sobre los
que se desee realizar alguna adaptación, lo que llamaremos sobrecarga.
Para ello sólo necesitamos escribir detrás del encabezamiento class <clasenueva> la
palabra extends seguida del nombre de la clase de la que se parte <clase_base>
(obviamente, ésta deberá ser accesible desde el código de la nueva clase).
De éste modo la nueva clase que vamos a definir, dispone automáticamente de todos los
elementos definidos en la clase base.
Supongamos que disponemos de una clase PuntoAcotado (la última expuesta) y
necesitamos una clase que sea como ella pero que además...
- ... tenga un atributo estático para la clase (accesible sólo directamente desde el
mismo paquete) que indique el diámetro para los puntos. (atributo añadido)
- ... un método para modificar el diámetro (que acepte valores entre 1 y 30) y otro para
consultarlo, ambos estáticos y accesibles, desde cualquier parte. (métodos añadidos)
- ... y que los métodos de desplazamiento (arr(), abj(), ...) incrementen/decrementen
los atributos x e y en la cantidad indicada por el atributo diámetro en lugar de
incrementarlos en una unidad. (métodos sobrecargados)
Usando herencia, extendiendo
(es decir, heredando) de la
clase base PuntoAcotado sólo
tendríamos que codificar los
elementos nuevos y los
métodos cuyo comportamiento
sea diferente al que tenían en la
clase base, tal como se muestra
en el código adjunto...
Todos los elementos de la
clase base se hereda, con las
mismas restricciones de acceso
con que se declararon.
// fichero: PuntoHijo.java
package el.que.sea;
public class PuntoHijo extends PuntoAcotado {
static int diametro=5;
public void izq() {
if(esvalido(x-diametro,y))
x-=diametro; }
public void der() {
if(esvalido(x+diametro,y))
x+=diametro; }
public void arr() {
if(esvalido(x,y-diametro))
y-=diametro; }
public void abj() {
if(esvalido(x,y+diametro))
y+=diametro; }
public static int consultaDiametro() {
return diametro; }
public static void asignaDiametro(int diam) {
if(1<=diam && diam<=30)
diametro=diam; }
};
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 182
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
No obstante, los elementos de la clase base a los que la clase hija tenga restringido el
acceso (por ser declarados private por ejemplo) no se podrán usar directamente, a pesar
de que se heredan, por lo que para acceder a ellos, habrá que hacerlo a través de los
métodos que sí tengan acceso (normalmente heredados también de la clase base).
Existe un problema en la herencia de nuestro ejemplo, en el que quizás alguien ya haya
reparado. Para que funcione tal como está, sería necesario que ambas clases
pertenecieran al mismo paquete, ya que de otro modo los métodos de desplazamiento
sobrecargados en la clase hija no podrían acceder a los elementos que se declararon por
defecto en la clase base.
........ .... ...
public void abj() {
if(esvalido(x,y+diametro))
y+=diametro; }
... .......
En este método, por ejemplo, no se podría
acceder ni a los atributos x e y, ni al método
esvalido(...) que en la clase base fueron
declarados de acceso por defecto.
¿Quiere esto decir que para que una clase herede de otra debe pertenecer al mismo paquete?... ¡NO!
Aquí entra en juego el tipo de acceso protected que antes habíamos dejado pendiente.
Con este tipo de acceso se especifica que el elemento tendrá igual accesibilidad que los
declarados por defecto pero además será también accesible desde las clases de otros
paquetes que lo hereden.
Para
que
nuestra
clase
sea
correctamente
heredable
desde
cualquier paquete, será preciso que
rectifiquemos la clase base, declarando
protected los elementos que deban ser
accesibles desde la clase hija (y que no
lo sean ya de antemano por haberse
declarado public)
Autor : FernandoToboso Lara
public class PuntoAcotado {
protected int x=1,y=1;
int xLim,yLim;
······ ·····
·· ····· ······
protected boolean esvalido(int px,int py){
return (
px>=1 && px <=xLim
&& py>=1 && py <=yLim ) }
··· ···· ········
···· ·· ·······
e-mail: [email protected]
Pag. 183
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
clase PuntoAcotado
protected...x , y ;
pordefecto...xLim, yLim;
protected...esvalido() …
public...mis_constructores... instancia
public...consulta_x()...
public...consulta_y()...
public...izq()...
public...der()...
public...arr()...
public...abj()...
P1
objeto de PuntoAcotado
protected...x , y ;
pordefecto...xLim, yLim ;
protected...esvalido() …
public...mis_constructores...
public...consulta_x()...
public...consulta_y()...
public...izq()...
public...der()...
public...arr()...
public...abj()...
clase PuntoHijo
extends PuntoAcotado
protected...x , y ;
(pordefecto...xLim, yLim ;)
pordefecto static diametro;
protected...esvalido() …
public...mis_constructores
public...consulta_x()...
public...consulta_y()...
public...izq()...
public...der()...
public...arr()...
public...izq()...
public...abj()...
public...der()...
public...arr()...
public...abj()...
P2
instancia
objeto de PuntoHijo
protected...x , y ;
(pordefecto...xLim, yLim ;)
protected...esvalido() …
public...mis_constructores
public...consulta_x()...
public...consulta_y()...
public...izq()...
public...der()...
public...arr()...
public...abj()...
public static...consultaDiametro()...
public static...asignaDiametro(...) ...
Consideraciones
• Los elementos protected serán accesibles por herencia incluso desde clases que no
pertenezcan al mismo paquete... pero con una restricción importante, si la clase hija
pertenece a un paquete distinto al que pertenece la clase base, no podrá acceder a los
elementos protegidos que se encuentren en otros objetos instanciados de la clase base
(sólo los no estáticos).
ejemplo.- Suponiendo que las clases anteriores son de paquetes distintos, desde los métodos de la clase
PuntoHijo NO SE PUEDE ACCEDER a los elementos... esvalido(), x, e y, del objeto P1 (ni por
supuesto a xLim y yLim). Sin embargo si que podrá acceder a los elementos esvalido(), x, e y
que él mismo hereda o de otros objetos de su propia clase.
• Otra cosa importante a tener en cuenta es que en java, cada clase puede heredar de
una sola clase.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 184
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
• Y por último, saber que cuando se sobrecarga un determinado método:
Se le puede hacer más accesible, pero nunca aumentar su nivel de restricción
de acceso. Los casos más extremos serían: sobrecargar un método público, que
obligatoriamente deberá seguir siendolo, mientras que al sobrecargar uno
private, será posible darle cualquier nivel de accesibilidad.
No se pueden cambiar (si lo tiene) su modificador static.
nota.- Para identificar cuál es el método sobrecargado, tengase en cuenta que debe coincidir tanto en
su identificador, como en el número y tipo de parámetros que recibe (y en el mismo orden).
Inicializadores y Constructores de clases hija (derivadas por herencia)
Los inicializadores (incluidos los constructores) no se heredan propiamente sino que
cada clase se hace responsable de sus propias inicializaciones.
Cuando se instancian objetos de una clase que hereda de otra, la clase base inicializa los
atributos que de ella heredamos, por lo que normalmente la clase hija sólo se encarga de
la inicialización de los atributos que ella misma aporta.
Es lógico que la inicialización se realice desde los cimientos en los que se apoya la
nueva clase hija, por lo que ciertamente:
- Cuando se va a ejecutar el inicializador static de una clase que hereda de otra (sólo
la primera vez que se hace referencia a ella, o a una clase derivada de ella), se
ejecuta previamente el correspondiente inicializador estático de su clase base.
- Cada vez que se van a ejecutar los métodos inicializador y constructores (al crear
nuevos objetos) se ejecutan previamente los inicializadores definidos para los objetos
de la clase base.
En ambos casos, si la clase base hereda a su vez de otra se aplica de nuevo
recursivamente la misma norma para que siempre se comiencen ejecutando los
inicializadores de la clase base de todas (la que no hereda de ninguna).
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 185
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Supongamos una serie de clases definidas usando herencia, según muestra el siguiente
diagrama:
clase PuntoAcotado
.........
static {...}// inicializador de clase
{...} // inicializador de objeto
public PuntoAcotado () {...} // constructor de objeto
// sin parámetros
public PuntoAcotado (int x, int y){ // constructor de objeto
…}
// recibiendo (x,y)
......... ....
… ......
clase PuntoHijo
extends PuntoAcotado
clase PuntoOtroHijo
extends PuntoAcotado
.........
.........
static {...} // inicializador de clase
{...} // inicializador de objeto
public PuntoHijo () {...}
// constructor de objeto
// sin parámetros
public PuntoHijo (int x, int y){
// constructor de objeto
super(x,y);
// dando (x,y) al padre
…}
static {...} // inicializador de clase
{...} // inicializador de objeto
public PuntoOtroHijo () {...} // constructor de objeto
// sin parámetros
public PuntoOtroHijo (int x){ // constructor de objeto
super(x,x);
// dando (x,x) al padre
…}
......... ....
… ......
......... ....
… ......
clase PuntoNieto
extends PuntoHijo
.........
static {...} // inicializador de clase
{...} // inicializador de objeto
......... ....
… ......
La definición de estas clases en java sería la siguiente:
public class PuntoAcotado {
int x=1,y=1;
int xLim,yLim;
// Inicializador de clase.
static {
System.out.println(" Primera y unica Inicializacion de la clase PuntoAcotado");
}
// Inicializador de objeto.
{ System.out.println(" Inicializa cualquier objeto PuntoAcotado");
}
// Constructores
public PuntoAcotado (){
System.out.println("
Constructor por defecto de un objeto PuntoAcotado");
xLim=10;
yLim=10;
}
public PuntoAcotado (int xL, int yL) {
System.out.println("
Constructor dando la posicion (x,y) de un objeto PuntoAcotado");
xLim=xL;
yLim=yL;
}
··· ·····
·· ·· ···
};
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 186
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
public class PuntoHijo extends PuntoAcotado {
static int diametro=5;
// Inicializador de clase.
static {
System.out.println(" Primera y unica Inicializacion de la clase PuntoHijo");
}
// Inicializador de objeto.
{ System.out.println(" Inicializa cualquier objeto PuntoHijo");
}
// Constructores
public PuntoHijo() {
System.out.println("
Constructor por defecto de un objeto PuntoHijo");
}
public PuntoHijo(int x, int y) {
super(x,y);
System.out.println("
Constructor dando la posicion (x,y) de un objeto PuntoHijo");
}
··· ···· ····
··· ·· ····
};
public class PuntoNieto extends PuntoHijo {
// Inicializador de clase.
static {
System.out.println(" Primera y unica Inicializacion de la clase PuntoNieto");
}
// Inicializador de objeto.
{ System.out.println(" Inicializa cualquier objeto PuntoNieto");
}
··· ·· ····
··· ····
};
public class PuntoOtroHijo extends PuntoAcotado {
// Inicializador de clase.
static {
System.out.println(" Primera y unica Inicializacion de la clase PuntoOtroHijo");
}
// Inicializador de objeto.
{ System.out.println(" Inicializa cualquier objeto PuntoOtroHijo");
}
// Constructor
public PuntoOtroHijo(int x) {
super(x,x);
System.out.println("
Constructor dando la posicion (x,x) de un objeto PuntoOtroHijo");
}
· ···· ····
·· ·· ····
};
... supongamos que se ejecuta este fragmento de código:
System.out.println("nuevo objeto de tipo PuntoAcotado");
PuntoAcotado pA1= new PuntoAcotado();
System.out.println("PuntoAcotado Hecho.................... ");
System.out.println("nuevo objeto de tipo PuntoNieto");
PuntoNieto pN= new PuntoNieto();
System.out.println("PuntoNieto Hecho...................... ");
System.out.println("nuevo objeto de tipo PuntoHijo en (2,5)");
PuntoHijo pH= new PuntoHijo(2,5);
System.out.println("PuntoHijo en (2,5) Hecho........... ");
System.out.println("nuevo objeto de tipo PuntoOtroHijo");
PuntoOtroHijo pO= new PuntoOtroHijo(18);
System.out.println("PuntoOtroHijo Hecho....................... ");
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 187
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
El orden de ejecución de los distintos métodos inicializadores sería...
nuevo objeto de tipo PuntoAcotado
Primera y unica Inicializacion de la clase PuntoAcotado
Inicializa cualquier objeto PuntoAcotado
Constructor por defecto de un objeto PuntoAcotado
PuntoAcotado Hecho....................
nuevo objeto de tipo PuntoNieto
Primera y unica Inicializacion de la clase PuntoHijo
Primera y unica Inicializacion de la clase PuntoNieto
Inicializa cualquier objeto PuntoAcotado
Constructor por defecto de un objeto PuntoAcotado
Inicializa cualquier objeto PuntoHijo
Constructor por defecto de un objeto PuntoHijo
Inicializa cualquier objeto PuntoNieto
PuntoNieto Hecho......................
nuevo objeto de tipo PuntoHijo en (2,5)
Inicializa cualquier objeto PuntoAcotado
Constructor dando la posicion (x,y) de un objeto PuntoAcotado
Inicializa cualquier objeto PuntoHijo
Constructor dando la posicion (x,y) de un objeto PuntoHijo
PuntoHijo en (2,5) Hecho...........
nuevo objeto de tipo PuntoOtroHijo
Primera y unica Inicializacion de la clase PuntoOtroHijo
Inicializa cualquier objeto PuntoAcotado
Constructor dando la posicion (x,y) de un objeto PuntoAcotado
Inicializa cualquier objeto PuntoOtroHijo
Constructor dando la posicion (x,x) de un objeto PuntoOtroHijo
PuntoOtroHijo Hecho.......................
Primero se ejecutan los inicializadores estáticos de las clases en cuanto van siendo
usadas, si vuelven a usarse (directa o indirectamente por herencia) ya no se ejecutan
puesto que la clase sólo debe inicializarse una vez.
Por eso cuando se instancia el objeto PuntoNieto se ejecutan los inicializadores estáticos
de PuntoHijo y PuntoNieto (en ese orden) puesto que el de PuntoAcotado ya se ha
ejecutado antes.
Cada vez que se instancia un objeto se ejecutan el método inicializador y el constructor
de cada una de las clases de la jerarquía de herencia de la que depende la clase del
objeto y finalmente los suyos propios.
Incluso cuando el objeto no tiene inicializador alguno (el objeto PuntoNieto no tiene
ningún constructor definido) siempre se ejecutan los correspondientes a las clases de las
que hereda.
nota.- recuérdese que cuando una clase no define ningún constructor se le supone uno por defecto, sin
parámetros y que no ejecuta ninguna instrucción.
Cuando una clase hereda de otra que dispone de más de un constructor, cada
constructor de la clase hija decide cuál de los constructores de su clase base prefiere que
se ejecute (puesto que sólo se ejecutará uno):
- si no especifica nada al respecto, la clase padre ejecutará su constructor sin
parámetros el cual deberá obligatoriamente tener definido (a no ser que no defina
ninguno y se le suponga por defecto).
- si la primera línea del constructor es super(<lista de parámetros>); , la clase
padre ejecutará el constructor que encaje con la lista de parámetros especificados.
De este modo le facilita al constructor de la clase base los datos de inicialización
que precisa para los atributos que él mismo inicializa (después el constructor de la
clase hija tendrá ocasión de modificarlos nuevamente).
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 188
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Palabra reservada: super
Tiene dos utilidades:
• Usada como nombre de una función debe ser la primera sentencia de un
constructor y permite especificar que constructor de la clase base debe ejecutarse, al
tiempo que le facilita los datos para que realice la inicialización.
// Constructor de los
// objetos PuntoHijo
.........
public PuntoHijo(int x, int y) {
super(x,y);
System.out.pr··· ··
··· ···· ··
}
..... .. .........
Aquí super(...) pide que la clase
base PuntoAcotado ejecute el
constructor con dos parámetros int
y le facilita x, y como datos
iniciales.
// Constructor de los
// objetos PuntoOtroHijo
.........
public PuntoOtroHijo(int x) {
super(x,x);
System.out.pr··· ··
.....
}
..... .. .........
Aquí super(...) pide que la clase
base PuntoAcotado ejecute el
constructor con dos parámetros
enteros y le facilita x, x como
datos iniciales.
// Constructor de los
// objetos PuntoHijo
.........
public PuntoHijo() {
System.out.pr··· ··
}
..... .. .........
Aquí su ausencia hace que la clase
base PuntoAcotado ejecute su
constructor sin parámetros.
En la clase PuntoNieto, la ausencia de
constructor hace que cuando se
instancian objetos se ejecute antes el
constructor sin parámetros de la clase
PuntoHijo (lo que hará que antes aún
se ejecute el constructor sin parámetros
de la clase PuntoAcotado.
• Usada como identificador de objeto desde un método no estático de la clase permite
acceder a los elementos heredados (y accesibles) de la clase padre. Su utilidad es que
aunque se halla definido un elemento en la propia clase hija con igual nombre que el
heredado, siga siendo posible usarlo desde la clase hija. Por ejemplo, desde los
métodos de la clase PuntoHijo la sentencia:
<... izq(); ...>
... hace referencia al método definido en la propia clase PuntoHijo, pero aún podrían
acceder al método izq() de la clase padre PuntoAcotado usando super :
<... super.izq(); ...>
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 189
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
A menudo, cuando un método sobrecarga a otro heredado, con la finalidad de realizar
lo que hacía el método original y alguna otra cosa más, éste llama al heredado para
que se ejecute antes, después, o en medio de la ssentencias que él mismo ejecuta.
Imagina que creamos por herencia una clase de objetos, similar a PuntoAcotado, pero
en el que sus objetos cada vez que se desplazan emiten un breve pitido...
import java.awt.Toolkit;
public class PuntoAcotadoSonoro extends PuntoAcotado {
// objeto con utilidades genéricas del sistema.
static Toolkit utilidades = Toolkit.getDefaultToolkit();
public void izq() {
utilidades.beep();
super.izq();
}
public void der() {
utilidades.beep();
super.der();
}
public void arr() {
utilidades.beep();
super.arr();
}
public void abj() {
utilidades.beep();
super.abj();
}
};
nota.- beep() es un método de la clase Toolkit (en el paquete java.awt) cuya
finalidad es hacer que el sistema emita un pitido.
Palabra reservada: this
Al igual que la anterior, esta palabra reservada del lenguaje Java tiene un significado
especial y de igual manera puede tener dos utilidades:
• Usada como nombre de una función debe ser la primera sentencia de un constructor
y sirve para pedir, que se ejecute algún constructor de la propia clase ...
·· ···· ···· ·
// como primera sentencia de un
this(...);
··· ·· ······
constructor.
... ejecutándose como resultado el método constructor que corresponda, dependiendo
de los parámetros que se incluyan en this(<parámetros...>). De otro modo no sería
posible, puesto que los métodos constructores no pueden ser llamados por su nombre
(excepto para instanciar nuevos objetos con new).
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 190
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
En la definición de una clase es frecuente incluir en un sólo constructor, todos los
parámetros de inicialización del objeto, para poder ejecutarlo desde los demás
constructores usando this(...).
public class PuntoAcotado {
protected int x=1,y=1;
int xLim,yLim;
public PuntoAcotado () {
this(100,100);
}
public PuntoAcotado (int xL, int yL) {
xLim=xL;
yLim=yL;
}
·· ····· ··
}
• Usada como identificador de objeto desde un método no estático, su valor es siempre
la referencia al objeto desde el cual es utilizado. Por lo que evidentemente, sólo puede
usarse desde los objeto, nunca desde un elemento estático.
Por un lado, de manera similar a como ocurre con super, la palabra this permite
que nunca se pierda el acceso a los elementos del objeto, aún cuando exista otros
identificadores con igual nombre declarados en un ámbito más local.
Supongamos por ejemplo que al parámetro recibido por el método
asignaDiametro(...) de la clase PuntoHijo, le llamamos diametro en lugar de diam.
Como identificador es válido, puesto que el otro diametro (el atributo) está
declarado en un ámbito distinto, pero el hecho de llamarlo igual que uno de los
atributos de la clase, nos dificulta el distinguir ambos objetos, el atributo y el
parámetro.
Si existen dos identificadores con igual nombre, siempre se da prioridad de acceso
al más local de ellos (el más cercano al ámbito en el que estamos), en este caso al
parámetro.
Para utilizar el atributo en este caso tendríamos que hacerlo mediante la palabra
reservada this.
····· ···· ······ ···
public static void asignaDiametro(int diametro) {
if(1<=diametro && diametro<=30)
this.diametro = diametro ; // el subrayado es el atributo.
}
·· ··· ·· · ····· ···
En cualquier caso, la mejor opción es elegir identificadores distintos para hacer el
código más fácil de comprender.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 191
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Además, también nos permite que en el momento en que escribimos el código de
los elementos de una clase, podamos usar la referencia al propio objeto. Lo
veremos mejor con un ejemplo.
o Supóngase el siguiente ejemplo en el que creamos la clase ColorArcoIris:
public class ColorArcoIris {
private static final String[] NOMBRE={ "1Rojo","2Naranja","3Amarillo","4Verde",
"5Azul","6Añil","7Violeta"};
private int tono; // valores desde 0 a NOMBRE.length
public static final int MAXIMO =NOMBRE.length-1;
public static final int MINIMO =0;
// Sin retornar nada
// *****************
public void incrementar() {
tono=(tono+1)%NOMBRE.length; // incremento ciclico
}
public String toString() {
return NOMBRE[tono];
}
}
Con objetos de esta clase podremos manejar los colores del arcoiris pasando de un
color al siguiente con el método incrementar().
Si, por ejemplo, instanciamos un objeto de esta clase con el fin de listar los
nombres de los colores, pero saltando por ellos de dos en dos:
public class PruebaColor {
public static void main(String args[]) {
ColorArcoIris color=new ColorArcoIris();
int i;
for(i=ColorArcoIris.MINIMO ;i<=ColorArcoIris.MAXIMO; i++) {
Salida.println("Color "+(i+1)+": "+ color.toString());
color.incrementar();
color.incrementar();
}
}
}
Este será el resultado que se muestre por la salida:
C:\> java PruebaColor
Color
Color
Color
Color
Color
Color
Color
C:\>_
Autor : FernandoToboso Lara
1:
2:
3:
4:
5:
6:
7:
1Rojo
3Amarillo
5Azul
7Violeta
2Naranja
4Verde
6Añil
e-mail: [email protected]
Pag. 192
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
No resultará demasiado interesante, pero nos será muy util para ver como puede
mejorarse el método incrementar() con esta otra versión:
····· ···· ···
// Retornandose a si mismo: this
// *****************************
public ColorArcoIris incrementar () {
tono=(tono+1)%NOMBRE.length; // incremento ciclico
return this;
}
··· ·· ··· · ··
El uso de esta nueva versión de incrementar() podría ser más cómodo, puesto que
el propio objeto ya modificado es retornado por la función puede usarse in situ
para realizar una nueva llamada a cualquiera de sus métodos (incluido el propio
incrementar()).
····· ···· ···
for(i=ColorArcoIris.MINIMO ;i<=ColorArcoIris.MAXIMO; i++) {
Salida.println("Color "+(i+1)+": "+ color.toString());
color.incrementar ().incrementar ();
}
· ··· ·· ···
...y obtendría identico resultado.
o Otra posibilidad de su uso (en la que por el momento no profundizaremos)
consiste en facilitar la implementación de
relaciones entre distintos objetos:
un Objeto Elemento
Atributos:
relacion
class Elemento {
Elemento relacion = this;
static Elemento ultimoObjetoCreado;
· ··· ···· ··
Elemento() {
ultimoObjetoCreado = this;
}
··· ·· ··· · ··
}
clase Elemento
Atributos estaticos:
ultimoObjetoCreado
un Objeto Elemento
Atributos:
relacion
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 193
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Todas las clases derivan de la clase Object
Por defecto toda clase (que no herede de otra explícitamente) hereda de una clase
fundamental de java llamada Object que pertenece al paquete java.lang (importado
siempre por defecto).
De modo que de esta clase se heredan siempre (directa o indirectamente) algunos
elementos que son comunes a cualquier clase de objetos de java.
Merece la pena en este momento detenerse a reflexionar sobre lo que conceptualmente
significa que un objeto herede de otro.
Existen dos tipos fundamentales de relación posibles entre las clases:
• de pertenencia ...cuando una clase (mediante las variables locales de sus métodos y
especialmente a través de sus atributos) dispone de referencias a otros objetos. Esto
sirve para que los métodos de un objeto puedan:
... enviar mensajes a otros objetos (pedirles que ejecuten alguno de sus métodos) y
acceder a sus atributos.
... o para describir relaciones de dependencia entre distintos objetos.
Por ejemplo, cuando pedimos a un objeto tabla que almacene la referencia de otro
objeto en alguna de sus casillas, creamos una relación de pertenencia entre el
objeto tabla y el objeto que guarda.
La expresión que describe esto es...
“la clase/objeto A tiene un objeto B”.
nota.- digo clase/objeto para no dejar de
lado a los elementos estáticos.
ejemplo.- Una clase que describe el tipo de dato Triángulo posiblemente tenga tres objetos de clase
Punto para describir las coordenadas de sus tres vértices.
ejemplo.- Una clase Coche tendrá (como atributos): 4 objetos de clase Rueda, 1 objeto Motor, hasta 5
objetos Pasajero, ...
• de herencia ...cuando una clase hereda de otra describiendo así con más concreción
los objetos que ésta representa, añadiendo para ello nuevos elementos y sobrecargando
métodos heredados.
La expresión que describe esto es...
“el objeto A es más genéricamente un objeto B”.
ejemplo.- Los objetos PuntoHijo son más genéricamente objetos PuntoAcotado.
ejemplo.- Si tuviésemos una clase llamada Figura, de la que heredasen las clases Triangulo, Elipse y
Rectangulo. Diríamos que los objetos Triangulo (así como los objetos Rectangulo, o Elipse)
son más genéricamente objetos Figura.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 194
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
No olvidemos que todos lo objetos, sean de la clase que sean, son más genéricamente
(por heredar directa o indirectamente de la clase Object) objetos Object. De esta clase
heredan todas las demás algunos métodos con utilidades de tipo genérico, como por
ejemplo:
void finalize()
-
Relacionada con el momento en que un objeto deja
usarse y desaparece de la memoria.
String toString()
-
Que retorna un String con el que por defecto un
objeto es mostrado por funciones como print(...) de
System.out
Object clone()
-
Realiza una copia exacta de
correspondiente a un objeto.
boolean equals(Object ...)
-
Compara el propio objeto, con otro objeto recibido
como parámetro, entendiendo por defecto que son
iguales si sus atributos son idénticos.
Class getClass()
-
Retorna un objeto de la clase Class, una clase
capaz de analizar internamente como es un objeto.
la
zona
de
memoria
Por lo que estarán presentes en todos los objetos que se instancian, cualquiera que sea
su clase.
De todos ellos se hablará más adelante profundizando en su significado y utilidad.
Destructores de clases
En java existe un especie de módulo conocido como recolector de basura, que se
ejecuta paralelamente a cualquier programa.
Su trabajo consiste en eliminar cualquier objeto a partir del momento en que no es
referenciado por ningún identificador. Pero no necesariamente en el mismo instante en
que el objeto deje de ser referenciado sino en el momento que disponga de tiempo.
nota.- No obstante usando la sentencia System.gc(); se puede forzar a que el recolector de basura se active
y se encargue de eliminar los objeto pendientes ser eliminados.
Cuando un objeto muere (es eliminado por orden del recolector de basura) se ejecuta
automáticamente, su método finalize(), (el heredado de Object, u otro que lo
sobrecargue). Si una clase lo sobrecarga puede incluir en él, las últimas sentencias que
los objetos deban ejecutar para acabar adecuadamente su existencia (aunque no es
frecuente que esto sea necesario).
Por lo demás el método finalize() es un método totalmente normal.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 195
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Si una clase lo sobrecarga, al morir los objetos de esta clase ejecutarán sólo el finalize()
de la propia clase pero no el que se sobrecargó. Por esto, es habitual también que su
última sentencia sea...
·· ··· ····· ·· ·
super.finalize();
}
···· ··· ······
...para dar oportunidad a que también se ejecuten los
definidos en las clases base, después de ejecutar sus propias sentencias.
En estos fragmentos de código muestra como tras crear un objeto PuntoOtroHijo y
referenciarlo desde el identificador p0 se asigna null (referencia nula) al identificador
para que deje de referenciar al objeto creado y prepararlo para el basurero.
· · ······ ·· ··· ·······
PuntoOtroHijo pO= new PuntoOtroHijo(18);
System.out.println("PuntoOtroHijo Hecho....................... ");
.........
pO=null; // asignamos null para que deje al objeto libre de referencias
System.out.println();
System.out.println("objeto PuntoOtroHijo sin referencias...va a ser eliminado.");
System.gc(); // Forzando al recolector a eliminar basura
···· ······ ··· ······
public class PuntoAcotado {
.............
protected void finalize() {
System.out.println(" Destructor de un objeto PuntoAcotado");
super.finalize();
}
...........
};
public class PuntoOtroHijo extends PuntoAcotado {
..............
protected void finalize() {
System.out.println("
Destructor de un objeto PuntoOtroHijo");
super.finalize();
}
};
Su puesta en funcionamiento nos permite comprobar como el orden de ejecución de los
destructores (en el momento en que System.gc() fuerza la muerte del objeto) es el que a
continuación se muestra:
..... ... ........
objeto PuntoOtroHijo sin referencias...el recolector de basura lo elimina
Destructor de un objeto PuntoOtroHijo
Destructor de un objeto PuntoAcotado
Permitiendo que se concluyan los posibles trabajos creados por los respectivos
inicializadores de objeto, en orden inverso al que fueron creados.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 196
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
No obstante, habitualmente la mayor parte de los recursos que usa un objeto, se liberan
por sí mismos o bien por petición explícita de que se liberen, por lo que pocas veces las
clases necesitan sobrecargar el método finalize().
Modificadores
De los modificadores que se pueden anteponer a las declaraciones de identificadores
existen dos que tienen un sentido especial en el contexto de la herencia de clases, lo
modificadores final y abstract, que analizaremos a continuación.
final
Ya antes se mencionó este modificador (en el apartado de las sentencias declarativas).
Cuando precede al identificador de un objeto hace que este se mantenga invariable, es
decir, constante, pues sólo permite que se le asigne valor una única vez. En el caso de
constantes locales a un método, no tiene por qué asignársele en el mismo momento en
que se declara, a los atributos sí ya que inicialmente no tienen ningún valor por defecto.
nota.- recuerda que final delante de un identificador de tipo referenciado forzará a que el identificador
referencie siempre al mismo objeto, pero no impide que el objeto varíe.
Los identificadores de constantes, declarados con el modificador final, podrán
igualmente recibir su valor inicial en cualquier momento antes de ser consultados, sólo
que únicamente permitirán que se les asigne dato una única vez.
boolean avanzar(int potencia) {
boolean aunNoHaLlegado;
final char ID_CONSTANTE;
⌦
if(ID_CONSTANTE)
·· · ··· ····
·· · ··· ····
ID_CONSTANTE = ‘Z’;
·· · ··· ····
// ERROR DE COMPILACION. El identificador aún
// está indeterminado, no ha sido inicializado.
System.out.print(ID_CONSTANTE);
⌦
// CORRECTO, El identificador
// ya ha sido inicializado.
·· · ··· ····
ID_CONSTANTE = ‘E’;
// ERROR DE COMPILACION. Las constantes
// una vez inicializadas, no admiten
// que se les asigne un nuevo dato.
·· · ··· ····
}
nota.- Recuerda, que un identificador final que referencie un objeto lo referenciará siempre, no
obstante el objeto sí que podrá sufrir modificaciones.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 197
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Sin embargo cuando final precede a una clase o a un método, tiene un significado
distinto:
- precediendo a una clase ... impide que se puedan crear clases derivadas
(heredadas) de ella.
- precediendo a un método ... impide que éste pueda ser sobrecargado, es decir,
impide que las clases que lo heredan puedan redefinir otro método con igual
nombre (e idénticos parámetros, claro está).
Aquí la finalidad del modificador es la de hacer definitiva la clase o método a la que
precede.
abstract
Este modificador, puede preceder tanto a una clase como a un método. Una clase
abstracta es una clase de la cual no se pueden instanciar objetos y se declara
precediéndola de la palabra abstract.
A veces resulta conveniente declarar clases de las que no se pueden instanciar objetos
pero que representan claramente “algo” que un grupo de clases son de manera más
genérica, A menudo ese algo es tan abstracto que no tiene sentido instanciar objetos de
la propia clase, pero quizas sí de sus clases derivadas.
Es el caso de la clase Figura ya antes mencionada, de la cual no tendría sentido
instanciar objetos. Supongamos que su definición fuese algo así:
import java.awt.Point;
public class Figura {
public Point posicion;
public Figura(int a, int b) {
posicion= new Point(a,b);
}
public float area() {
·· ····
}
public float perimetro() {
·· ·····
}
·· ·· ····
}
};
- ¿cómo deberían sus métodos calcular el área o el perímetro de una figura que es
indeterminada?. ¿de qué tipo de figura se trata realmente?
Sin embargo la clase Figura puede resultar de una gran utilidad para definir en ella
elementos comunes para cualquier figura, como la posición, evitando tener que
repetirlos en cada clase de figura concreta.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 198
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Su definición debería utilizar mejor una clase abstracta.
public abstract class Figura {
public Punto posicion;
public Figura(int a, int b) {
posicion= new Punto(a,b);
}
abstract public float area();
abstract public float perimetro();
abstract public void dibujar(Graphics g);
};
...que utilice métodos abstractos (sólo declarados) para aquellos métodos cuya
implementación no pueda ser aún realizada.
Un método abstracto representa una acción genérica (perteneciente a una clase
necesariamente abstracta) cuya implementación se concretará más tarde, en las clases
derivadas y se declara igualmente anteponiéndosele la palabra abstract.
Las clases hija heredarán todos los métodos de la clase base abstracta, por lo que
deberán sobrecargar todos los métodos abstractos heredados si no desean ser igualmente
clases abstractas.
public class Rectangulo extends Figura {
int lado1, lado2;
public Rectangulo(int x, int y, int lado_1, int lado_2) {
super(x,y);
lado1=lado_1;
lado2=lado_2;
}
public float area() {
return lado1*lado2;
}
public float perimetro() {
return lado1*2+lado2*2;
}
···· ··· ···
// otros métodos propios.
}
En cualquier caso, no todos los métodos de una clase abstracta tienen por qué ser
abstractos, por lo que también podemos heredar de ellas métodos completamente
realizados.
Una clase que defina (o herede sin sobrecargar) uno o más métodos abstractos debe
declararse abstracta y no permitirá que se instancien objetos de ella.
De este modo se obliga a que las clases derivadas de Figura sobrecarguen los métodos
abstractos. Podremos así presuponer, que todos los objetos de clases heredadas de
Figura disponen de los métodos que deben estar por definición en cualquier tipo de
figura.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 199
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Lanzamiento de Excepciones
Ya anteriormente vimos cómo tratar las excepciones que una sentencia puede lanzar
cuando no es capaz de realizar la tarea que se le solicita, haciendo uso de bloques try y
catch().
Pero además existen ocasiones en las que son las funciones que nosotros mismos
desarrollamos las que pueden llegar a una situación sin solución, en la cual la única
alternativa coherente sea acabar bruscamente dejando que el problema trascienda a la
función que llamó a la nuestra.
Para ello simplemente debemos crear un objeto alguna clase heredada de la clase
Exception y lanzarla mediante la instrucción throw.
··· ···· ····
throw new UnaExcepcionDerivadaDeException(“breve explicación del error ocurrido”);
··· ·· ······ ····
Esto finaliza repentinamente la ejecución de la función actual, dando al módulo que la
llamó la posibilidad de que atrape el error lanzado, o que lo deje pasar y que trascienda
a otra función.
nota.- para que un objeto pueda ser lanzado (como excepción) con la palabra throw, debe
obligatoriamente heredar (directa o indirectamente) de la clase Throwable, aunque generalmente
heredan a partir de la clase Exception.
Existen en java una gran variedad de clases que derivan por herencia directa o
indirectamente de la clase Throwable con las que podemos crear objetos de error para
ser lanzados. Estas son algunas de ellas, pertenecientes al paquete java.lang, pero
existen muchas más. Además de las que uno mismo puede crear heredando de
cualquiera de ellas.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 200
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
class Throwable
class Error
class LinkageError
class ClassCircularityError
class ClassFormatError
class UnsupportedClassVersionError
class ExceptionInInitializerError
class IncompatibleClassChangeError
class AbstractMethodError
class IllegalAccessError
class InstantiationError
class NoSuchFieldError
class NoSuchMethodError
class NoClassDefFoundError
class UnsatisfiedLinkError
class VerifyError
class ThreadDeath
class VirtualMachineError
class InternalError
class OutOfMemoryError
class StackOverflowError
class UnknownError
class Exception
class ClassNotFoundException
class CloneNotSupportedException
class IllegalAccessException
class InstantiationException
class InterruptedException
class NoSuchFieldException
class NoSuchMethodException
class RuntimeException
class ArithmeticException
class ArrayStoreException
class ClassCastException
class IllegalArgumentException
class IllegalThreadStateException
class NumberFormatException
class IllegalMonitorStateException
class IllegalStateException
class IndexOutOfBoundsException
class ArrayIndexOutOfBoundsException
class StringIndexOutOfBoundsException
class NegativeArraySizeException
class NullPointerException
class SecurityException
class UnsupportedOperationException
La clasificación de estos objetos de error se ajusta a los siguientes criterios:
- Un objeto Error (o de alguna clase heredada de Error) normalmente sólo lo lanza la
máquina virtual java y en casos que normalmente resultan impredecibles para un
programador.
- Los programadores normalmente lanzan y atrapan, objetos Exception (o de sus clases
derivadas).
- Los objetos Exception (o de clases derivadas) corresponden a excepciones marcadas
que deben ser declaradas en la cabecera de toda función que pueda generarlas, excepto
como ya se dijo anteriormente los objetos de la clase RuntimeException (o de las
clases derivadas de ella).
Cuando una función decide que necesita lanzar una excepción pueden optar por elegir
uno de estos objetos Throwable, o bien declarar una nueva clase de error específico,
heredando de uno de ellos según le convenga.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 201
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
public class MiPropiaExcepcion extends Exception {
// Aquí normalmente sólo se ponen los constructores.
// Tipicamente uno sin parámetros y otro que recibe un String,
// que se limitan a pasar la llamada al constructor del padre.
MiPropiaExcepcion() {
// por defecto empieza ejecutando el constructor del padre
}
MiPropiaExcepcion(String mensaje) {
// pasa el mensaje explicativo al constructor del padre
super(mensaje);
}
}
En la nueva excepción se pueden sobrecargar las funciones que se deseen, pero esto no
es lo habitual puesto que lo más interesante radica del hecho de que esta nueva clase de
Exception se podría atrapar usando un catch(MiPropiaExcepcion e) especifico para
ella.
Supóngase que añadimos a la clase ColorArcoIris (anteriormente definida) un
constructor que permita inicializar los nuevos objetos con un color de entre los colores
válidos de la clase ColorArcoIris.
· ··· · ····· ··
public ColorArcoIris(int colorIni) {
tono=colorIni;
}
· ··· · ····· ··
Si se instancia un objeto pasado como parámetro un valor erróneo...,
¿qué debería hacer el método constructor?, ....no sabemos como actuar.
Esta puede ser una situación muy adecuada para lanzar una excepción...
· ··· · ····· ··
public ColorArcoIris(int colorIni) {
if(color < ColorArcoIris.MINIMO || ColorArcoIris.MAXIMO < color)
throw new IllegalArgumentException (“Color no valido para un Arcoiris.”);
// aquí no se llegará nunca, si se ejecuta la sentencia throw.
// por lo que es indiferente poner el else del if, que no ponerlo.
tono=colorIni;
}
· ··· · ····· ··
...y ofrecer la oportunidad al método que ha realizado mal la instanciación del nuevo
objeto, de que la atrape... o la deje pasar, si es que tampoco sabe como actuar.
...algunMetodo(...) {
· ··· · ····· ··
int cod_color;
· ······ ··
ColorArcoIris unColor;
try {
unColor= new ColorArcoIris(cod_color);
··· ······ ··
} catch(IllegalArgumentException e) {
// corregir el error.
}
· ··· · ····· ··
}
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 202
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Interfaces
Las interfaces son agrupaciones de métodos huecos (sin instrucciones) y de atributos
constantes. Se declaran de modo similar a las clases pero usando, la palabra interface,
en lugar de utilizar class.
interface Comunicable {
<tipo> enviar(...);
<tipo> recibir(...);
<tipo> acuseRecibo(...);
.... .. ..
<tipo> canalDeComunicacion = valor;
.... .. . ... .
};
Sirven para ser “heredadas” desde las clases y desde otras interfaces. Cuando una clase
hereda de una interface se dice que la implementa. La sintaxis para expresarlo utiliza la
palabra implements:
class CartaPostal implements Comunicable {
... ... . ......
....... . ... .....
}
Si una clase implementa una interface (hereda interface), se compromete a definir todos
los métodos declarados en ella, al tiempo que hereda las constantes que ésta tenga
definidas.
Su principal finalidad es la de establecer un grupo estándar de acciones que deben
existir en todas las clases que implementan una determinada interface. De este modo, si
un grupo de clases de objetos implementan una misma interface, dispondrán de un
mismo conjunto de métodos con los que ser manipulados... los que se declaran en la
interface. Además, claro está, de otros que cada clase defina particulares de ella.
La interface Comunicable, por ejemplo, serviría para definir un conjunto de métodos
que deban existir en las clases que la implementen como CartaPostal,
TransferenciaBancaria, SeñalTelefonicaDigital, ProgramaFTP, etc.. que deben poder
ser comunicadas.
nota.- A veces se les da a las interfaces nombres acabados en -able o -ible puesto que certifican que las
clases que las implementan, son... -ables o -ibles, (comunicables, accesibles, comparables, etc).
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 203
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
No se deben confundir la herencia de una clases abstractas con la implementación de
interfaces aunque ciertamente se parecen. En todo caso téngase en cuenta lo siguiente:
- A diferencia que con la herencia, una clase puede implementar varias interfaces.
En tal caso estará obligada a definir todos los métodos de todas las interfaces que
declare implementar.
- Por defecto, todos los métodos de una interface son implícitamente (sin que deba
especificarse) públicos y abstractos.
- Por defecto, todos los atributos de una interface son implícitamente públicos,
estáticos y constantes.
- Como consecuencia de esto último las interfaces no independientes deberán son
siempre estáticas y por ello...
o ... las interfaces anidadas dentro de alguna clase serán siempre estáticas, sus
atributos estáticos, no podrían estar dentro de una interface alojada dentro de
un objeto.
o ...las interfaces no podrán estar declaradas locales a ningún método (ni aún
cuando se trate de métodos estáticos.
nota.- recuérdese que las clases estáticas tampoco podían ser declaradas locales a un
método, ni siquiera en métodos estáticos.
En lo relativo a temas como el ámbito desde el que son utilizables, niveles de privacidad
de acceso, etc..., son aplicables las mismas normas que para las clases.
ejemplo: interface Comparable
Supóngase el siguiente caso de la interface Comparable, que declara los métodos que
deben tener definidos el grupo de clases de objetos que admiten entre ellos alguna
relación de equivalencia o igualdad.
// Fichero: tipos/Comparable.java
package tipos;
interface Comparable {
// RETORNARÁ :
-1... el objeto this es menor que el obj
//
0... el objeto this es igual que el obj
//
1... el objeto this es mayor que el obj
int comparadoCon(Object obj) throws ClassCastException;
}
nota.- Existe en el paquete java.lang una interface también llamada
Comparable, por lo que para evitar ambigüedades a veces será
necesario hacer uso de su nombre completo ...tipos.Comparable.
Es similar a esta nuestra, sólo que su método se llama compareTo(...).
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 204
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Considera la clase Racional , que representa a los números racionales, un tipo de datos
cuyos elementos mantienen una relación de orden entre sus miembros.
// Fichero: tipos/numeros/Racional.java
package tipos.numeros;
import tipos.Comparable;
public class Racional implements Comparable {
// NUMERADOR y DENOMINADOR
protected int numer, denom;
// CONSTRUCTORES
public Racional() {this (0,1);}
public Racional(int n) {this (n,1);}
public Racional(int n, int d) {
if(d==0)
throw new RacionalException("No existe el racional (X/0)");
numer=n;
denom=d;
}
// ALGUNAS OPERACIONES
public Racional multiplica (Racional r) {
return new Racional(numer*r.numer, denom*r.denom);
}
public Racional suma(Racional r) {
return new Racional( numer *r.denom+r.numer*denom, denom*r.denom);
}
public Racional inversa() {
if (numer==0)
throw new RacionalException("No tiene inversa.");
return new Racional (denom,numer);
}
// IMPLEMENTACION interface Comparable
public int comparadoCon(Object obj) {
Racional elOtro= (Racional) obj; // Puede lanzar ClassCastException.
double oper1,oper2;
oper1=numer/(double) denom;
oper2=elOtro.numer/(double)elOtro.denom;
if (oper1 < oper2)
return -1;
else if (oper1==oper2)
return 0;
else
return 1;
}
// CONSULTA
public int numerador() { //permite consultar el atributo protegido
return numer;
}
public int denominador() { //permite consultar el atributo protegido
return denom;
}
public double doubleValor() { // dato correspondiente de tipo double
return numer/(double)denom;
}
public int intValor() {// dato correspondiente de tipo int.
return numer/denom;
// evidentemente perdiendo precisión.
}
public String toString(){
return new String ("(" + numer +"/"+ denom +")");
}
}
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 205
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
// Fichero: tipos/numeros/RacionalException.java
package tipos.numeros;
public class RacionalException extends ArithmeticException {
RacionalException(String mensaje) { super(mensaje);}
RacionalException(){}
}
- Sus constructores impiden que el denominador se pueda inicializar erroneamente a
cero, lanzando una excepción RacionalException si se intenta.
- Los metodos numerador(), denominador(), intValor() y doubleValor(), permiten
conocer el valor de los atributos declarados protegidos, sin que puedan ser directamente
accedidos y evitando así que puedan tomar valores inaceptables (como 0/0).
- A modo de ejemplo se han incluido algunos metodos correspondientes a operaciones
de los números racionales multiplica(), suma(), inversa(). En todos ellos se retorna el
resultado en un nuevo objeto Racional con el que pueden encadenarse varias
operaciones.
··· · ··· ···· ·····
Racional rA = new Racional(2,5) , rB = new Racional(1,2);
Racional resul;
····· ····· ········ ····· ·· ·······
resul = rA.suma(rB).inversa();
···· · ·· ··· ······· ··
- Se sobrecarga el método toString(), heredado de Object, retornando cómo mostrará
print() los datos racionales... entre parentesis y separando numerador y denominador
con una barra inclinada.... (23/7).
- No obstante, lo más importante para nuestro ejemplo es la definición de los métodos
que hace que nuestra clase efectivamente implemente la interface Comparable.
El método comparadoCon(Object ...) aceptará como parámetro un objeto de cualquier
clase, pero en esta implementación sólo admite la comparación de un objeto Racional
con otro de la misma clase Racional.
nota.- Si se le pasa un objeto que no sea Racional, lanzará una excepción ClassCastException, al aplicar
(en la primera línea) el moldeador de tipo.
interface Comparable
class Object
class Racional
Herencia
Implementación
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 206
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Capitulo 13
Varios
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 207
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Análisis de un objeto desconocido
Ahora que conocemos el fenómeno de la herencia, no debería extrañarnos la situación
en la que se defina una función que se ejecute como respuesta a más de un prototipo de
llamada. Por ejemplo la función:
static void almacena(Figura f) {
··· ·· ····
}
...se ejecutaría ante cualquiera de las siguientes llamadas:
·· ···· · ··
Triangulo tria; // de clase hija de Figura
Elipse elip;
// de clase hija de Figura
Rectangulo rect; // de clase hija de Figura
··· ····· ··· ·
almacena(tria);
almacena(elip);
almacena(rect);
····· · ·· ····
...puesto que en todas las llamadas los parámetros (además de ser de clase Triangulo,
Elipse y Rectangulo) son más genéricamente de clase Figura y se ajustan por lo tanto
al prototipo de la función almacena(Figura ...).
A no ser que exista otro prototipo que les encaje mejor, como:
static void almacena(Triangulo t) {
··· ·· ····
}
Si existe este método, será este el que responda a la llamada con el parámetro
Triangulo, en lugar de la anterior.
Puesto que como hemos visto todas las clases heredan por defecto de la clase Object,
común a todas las clases, una función con el prototipo:
... void funcion(Object obj) {
··· ·· ····
}
...aceptará que se le pase como parámetro cualquier clase de objeto. Y de hecho así
ocurre en ocasiones y en esto casos resulta muy útil que la función pueda hacer
averiguaciones respecto a cuál es la clase a la que realmente pertenece un objeto.
Para ello disponemos de dos herramientas:
-
Por un lado, el operador instanceof .
-
Y por otro del uso de clases como Class, Method, etc... de la librería
estándar de java, que disponen de métodos con los que es posible analizar el
interior de los objeto: qué métodos tiene, como hay que invocarlos, etc...
nota.- Puede buscar en la documentación de la librería de java la especificación de cómo
usar estas clases. A continuación veremos algún ejemplo de su utilización.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 208
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Operador instanceof
El operador instanceof permite saber si un objeto es (o hereda, o implementa) de una
determinada clase (o interface). Se trata de un operador binario (de dos operandos) cuya
sintaxis es:
<objeto> instanceof <Clase o Interface>
... y su resultado es true o false según el objeto sea de dicha clase (o implemente dicha
interface).
public class Graficos {
static float area(Objetc obj) {
if(obj instanceof Figura)
return ((Figura)obj).area();
else if(obj instanceof PlanoVivienda)
return ((PlanoVivienda)obj).metrosUtiles();
else if(... .. .
··· ···
else {
String nombreClase = obj.getClass().getName();
throw new IllegalArgumentException ("Imposible hallar area del objeto tipo "
+ nombreClase + " recibido.");
}
}
·· ··· ····· ·· ····
}
Con instanceof se intenta averiguar la clase del objeto obj.
Suponiendo que los datos Figura disponen del método area() y los datos PlanoVivienda
disponen del metrosUtiles() para informar del espacio que ocupan respectivamente, se
invoca a un método u otro dependiendo del resultado de la operación instanceof.
Si no se consigue encontrar una clase o interface que encaje con el objeto, se lanza una
excepción, con un mensaje en el que se incluye el nombre real de la clase de la que se
instanció el objeto recibido obj.
Para obtenerlo, se usa el método getClass() (heredado de la clase Object) que retorna un
objeto de clase Class con información de la clase auténtica del objeto. Con este objeto
Class resultante se llama a la función getName() que nos da el nombre de la clase en
cuestión.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 209
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Clase Racional mejorada
Con esta ampliación de la implementación del método comparadoCon() será posible
comparar los objetos Racional, no sólo con objetos de su misma clase sino también, con
otros objetos Integer, Long, Float, etc...
······ ·· ····
public int comparadoCon(Object obj) {
double oper1=doubleValor();
double oper2;
if(obj instanceof Racional)
oper2=((Racional)obj).doubleValor();
else if(obj instanceof Integer)
oper2=((Integer)obj).doubleValue();
else if(obj instanceof Long)
oper2=((Long)obj).doubleValue();
else if(obj instanceof Float)
oper2=((Float)obj).doubleValue();
else if(obj instanceof Double)
oper2=((Double)obj).doubleValue();
else if(obj instanceof Byte)
oper2=((Byte)obj).doubleValue();
else if(obj instanceof Short)
oper2=((Short)obj).doubleValue();
else {
String claseVerdadera=obj.getClass().getName();
throw new ClassCastException("Los objetos Racional no se pueden comparar "
+ "con objetos de clase "+claseVerdadera);
}
if (oper1 < oper2)
return -1;
else if (oper1==oper2)
return 0;
else
return 1;
}
··· ··· ·· ··
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 210
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Clonación
La interface Cloneable es un interface con una utilidad muy peculiar, el proceso de
duplicación de objetos, o clonación. Así se denomina a la acción por la cual podemos
instanciar nuevos objetos, como copias exactas de objetos previamente existentes.
El proceso de la copia se debe ajustar a la siguiente especificación:
- el objeto copia y el objeto original deben ser instancias distintas.
objetoCopia != objetoOriginal
- generalmente la igualdad entre ellos resulta cierta según la comparación aplicable a
los datos de su clase o clases.
objetoCopia.equals(objetoOriginal)
nota.- téngase en cuenta que ciertas clases de datos pueden entender que sus datos sean iguales, aun
no siendo idéntico. Una supuesta clase Nombres, por ejemplo podría considerar que los
nombre “Pepe” y “José” son iguales, aunque ni siquiera se parezcan.
- ambos objetos serán normalmente de la misma clase, (aunque esto no siempre tiene
por qué cumplirse).
objetoCopia.getClass() == objetoOriginal.getClass()
Cuando se crea un objeto por clonación de otro no se ejecuta ninguno de sus
constructores ni inicializadores sino un método llamado clone(). Todos los objetos en
java disponen de este método, que por defecto heredan de clase Object.
Cuando se ejecuta el método, éste retorna la referencia a un objeto nuevo, que es una
copia exacta de la zona de memoria del objeto que lo contiene.
Autor : FernandoToboso Lara
objetoOriginal
objetoCopia
Atributos:
dato1 20
dato2 ‘A’
Atributos:
dato1 20
dato2 ‘A’
Métodos:
··· ····· ···
...Object clone()...
Métodos:
··· ····· ···
...Object clone()...
e-mail: [email protected]
Pag. 211
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
La definición del método tiene aproximadamente este aspecto:
public class Object {
·· ···· ··
protected Object clone() throws CloneNotSupportedException {
// si la clase NO implementa la interface Cloneable
// lanza una excepcion.
if( !(this instanceof Cloneable))
throw new CloneNotSupportedException();
// si la clase SI implementa la interface Cloneable
// se crea la copia de memoria del objeto this
// y se retorna.
}
}
...y donde Cloneable es una interface vacía, sin ningún elemento declarado.
interface Cloneable {}
Su única finalidad es permitir que se pueda distinguir, que clases permite la clonación
de sus objetos y cuales no.
Lo único que debe hacer una clase, para permitir la duplicación de sus objetos es
declarar en su cabecera que implementa esta interface, ya que si no lo hace... el método
clone() lanzará la excepción marcada CloneNotSupportedException al ser ejecutada.
··· · ···· ··
public class UnaClase implements Cloneable {
int dato1=20;
char dato=’A’;
· ·· ······ ··
public class ejemploDeDuplicacion {
}
UnaClase objetoOriginal;
· ··· ··
UnaClase objetoCopia;
objetoOriginal = new UnaClase();
· ·· ···· ······ ·
try {
objetoCopia=(UnaClase)objetoOriginal.clone();
··· ···· · ··· ·····
} catch(CloneNotSupportedException e) {
System.out.println("Objeto no clonable");
}
}
- Como el método clone() de Object es protected, deberá ser invocado desde una clase
u objeto que tenga permiso para ejecutarlo, ...a no ser que la clase UnaClase
sobrecargue el método haciéndolo más accesible.
··· · ···· ··
public class UnaClase implements Cloneable {
·· ···· ··
public Object clone() {
return super.clone();
}
}
· ·····
- Aunque clone() crea un duplicado del objeto de clase UnaClase, como por su
definición retorna tipo Object, se necesita aplicar un moldeador de tipo (o casting).
objetoCopia=(UnaClase)objetoOriginal.clone();
A este tipo de clonación se le suele llamar copia simple (shalow copy), pero con ella no
siempre se consigue una clonación adecuada para los objetos.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 212
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Suponiendo que algunos atributos fuesen de tipo referenciado el objeto duplicaría las
referencias no los objetos que los atributos referencian y por lo tanto compartirían los
objetos, de manera que las modificaciones en uno afectarían al otro.
objetoCopia
objetoOriginal
Métodos:
··· ····· ···
...Object clone()...
…
…
‘B’
‘E’
‘T’
‘*’
Atributos:
dato1 20
tabla
frase
Atributos:
dato1 20
tabla
frase
Métodos:
··· ····· ···
...Object clone()...
‘S’
“Hola Mundo”
Esto probablemente no será lo deseado sino que cada uno de los objetos, original y
copia, sea instancias completamente independientes.
objetoCopia
Métodos:
··· ····· ···
...Object clone()...
‘B’
‘E’
‘T’
‘*’
Métodos:
··· ····· ···
...Object clone()...
…
…
Atributos:
dato1 20
tabla
‘B’
‘E’
‘T’
‘*’
…
…
Atributos:
dato1 20
tabla
objetoOriginal
‘S’
‘S’
“Hola Mundo”
“Hola Mundo”
Para lograrlo no bastará con implementar la interface Cloneable sino que además será
preciso sobrecargar el método clone(), para que realice una duplicación correcta de los
atributos de tipo referenciado.
··· · ···· ··
public class UnaClase implements Cloneable {
int dato1=20;
char[] tabla={‘B’,’E’,’T’,’*’, ... ,’S’};
String frase = “Hola Mundo”;
· ·· ······ ··
protected Object clone() {
UnaClase copia = (UnaClase)super.clone();
// correccion de atributos mal duplicados.
copia.tabla = (char[])tabla.clone();
copia.frase = new String(frase);
return copia;
}
}
·· ··· ·· ···
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 213
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
A este tipo de copia se le llama copia detallada (deep copy), puesto que debe ser
definida específicamente para cada clase de datos.
- El método sobrecargado deberá ser protected, o más accesible (recuerda que nunca se
puede aumentar el grado de privacidad de un método heredado)...
- ... y deberá retornar Object (para coincidir con el prototipo heredado).
- Normalmente se empieza por ejecutar el método sobrecargado super.clone() para que
éste se ocupe de crear la copia simple y por si acaso su clase padre también hubiese
sobrecargado este método. Con esto obtenemos una copia simple del propio objeto de
clase UnaClase (aunque al retornarlo como Object debe serle aplicado un moldeador
de tipo)
nota.- un objeto de clase UnaClase siempre será al mismo tiempo de clase Object, pero lo inverso no
tiene por que ser siempre así (un Object no siempre será un objeto de clase UnaClase), por eso es
preciso aplicarle explícitamente el moldeador de tipo.
- Antes de retornar la copia conseguida, se corrigen aquellos atributos cuyo valor no
haya sido adecuadamente duplicado.
o Si la clase del atributo implementa Cloneable, será conveniente ejecutar
su propio método clone() (suele ser más rápido).
o Si no, habrá que crear el duplicado instanciándolo con new e
inicializándolo con el valor del atributo original.
- Finalmente el objeto correctamente clonado se retorna como resultado (aparentemente
como Object, pero realmente de clase UnaClase).
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 214
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Herencia de interfaces
La herencia estudiada para las clases, también se puede dar entre interfaces,
considerando evidentemente que una interface sólo puede heredar de otras interfaces y
que a diferencia de las clases, una interface si puede heredar simultáneamente de más
de una interface (herencia múltiple). De esta forma a menudo se establecen relaciones
muy convenientes.
Hemos visto cómo la clase de datos Racional implementaba la interface Comparable y
del mismo modo otras clases con relaciones de orden entre sus datos (enteros, reales,
caracteres alfabéticos, etc...) podrían implementar este misma interface.
Supongamos el caso de los números enteros. Sus datos tienen orden entre sí, pero
además (a diferencia de los racionales) cada dato tiene un sólo dato inmediatamente
mayor que él y sólo uno inmediatemente menor.
Números racionales
iguales entre sí
· · ··
<
− 5 ó − 15 ó 45
2
6
18
iguales entre sí
iguales entre sí
+3
1 ó 10 ó 4
2
20
8
6 ó 3 ó 18
4
2 12
-67
+67
+1
iguales entre sí
1
+
4
− 7 ó 7 ó 21
−4
4 12
<
· · ··
Números enteros
· · ··
<
-68
+1
0
+8
8
+1
9
+1
10
+96
106
<
· · ··
Podría interesarnos atribuir a la clase Entero la característica de ser incrementable.
Es decir que disponga de un método con el que poder modificar los objetos Entero
incrementándolos/decrementándolos en la cantidad solicitada (positiva o negativa).
Puesto que si un dato se puede incrementar necesariamente debe tener orden, podemos
definir la interface Incrementable heredando a partir de la interface Comparable.
interface Incrementable extends Comparable {
// Aumenta el valor del objeto que
// lo implementa en 'incremento' unidades.
Incrementable incrementar(int incremento);
}
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 215
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
class Object
interface Comparable
interface Incrementable
class Racional
class Entero
Herencia
Implementación
Los elementos que implementen esta interface deben definir (o heredar) tanto el método
de Comparable, como el especificado en Incrementable:
public int comparadoCon(Object obj);
public Incrementable incrementar(int incremento);
La implementación de la clase Entero es buen ejemplo de clase Incrementable (y
consecuentemente también Comparable) que además podemos definir apoyándonos en
la clase Racional anteriormente definida.
package tipos.numeros;
import tipos.Incrementable;
public class Entero extends Racional implements Incrementable {
// CONSTRUCTORES
public Entero() {
this(0);
}
public Entero(int n) {
super(n,1); }
// INCREMENTABLE
public Incrementable incrementar(int incremento) {
numer+=incremento;
return this;
}
// CONSULTA
public String toString() { // cadena representativa del dato.
return (Integer.toString(numer));
}
}
Puesto que los enteros son un subconjunto de los números racionales, heredamos de la
clase Racional para reutilizar su código. De este modo los constructores se limitan a
pasar al constructor más apropiado de la clase base, los valores del entero que
inicializan.
nota.- Un entero es un número racional cuyo denominador vale uno (1).
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 216
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
La implementación de los métodos de la interface Comparable, se heredan de la clase
Racional. Si no los heredase tendría que definirlos, puesto que al implementar
Incrementable (que hereda de la interface Comparable) indirectamente se compromete a
implementar también la interface Comparable. En este caso, sin embargo, solamente
será necesario definir el método que no hereda ...incrementar(int incrementar).
ejemplo: herencia múltiple de interfaces
Cuando en la herencia es de interfaces, pueden haber múltiples interfaces base. Ya sea...
... porque una clase implemente (implements) varias interfaces.
... o bien, porque una interface herede (extends) de varias interfaces.
Si deseamos que la clase Racional también implemente la interface Cloneable (o
cualquier otra), no habrá ningún problema en codificarlo así:
public class Entero extends Racional implements Incrementable, Cloneable {
..... . .........
// implementación de los métodos de cada interface implementada.
....... ... ..... .
}
O bien, en caso de que un grupo de interfaces sean implementadas conjuntamente
(desde la misma clase) con frecuencia, quizá sea preferible disponer de una interface
que las agrupe en una sola, heredando de ellas.
interface GrupoInterfaceDeInterfaces extends Interface1, Interface2, ... {
// declaración de otros metodos especificos (no heredados)
}
De modo que implementarla será equivalente a implementar todas las interfaces de las
que GrupoInterfaceDeInterfaces hereda (ademas de los métodos específicos que esta
interface pueda declarar).
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 217
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Capitulo 14
Polimorfismo
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 218
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Polimorfismo significa “muchas formas”, pero el concepto en el contexto de la
programación orientada a objeto, es algo más concreto. Se refiere a todas aquellas
acciones que siendo conceptualmente iguales reciben un mismo nombre y sin embargo
se llevan a cabo de diferentes maneras según sea el objeto sobre el que actúen o según el
contexto en que se realicen.
Por ejemplo aunque usen el mismo verbo y tengan similitudes conceptuales... no es lo
mismo tocar una guitarra que tocar una mesa, ni que tocar un timbre, no es lo mismo
sumar dos números enteros que sumar dos vectores, ni que sumar dos colores,...no es
lo mismo cortar una madera que cortar un papel, ni que cortar un cristal, el
procedimiento para realizar cada una de estas acciones será muy diferente en cada uno
de los casos.
En java, dos métodos con el mismo identificador son distintos ...
... si se diferencian en el número o tipo de sus parámetros (teniendo en cuenta incluso
el orden de los mismos).
... o bien, si pertenecen a clases diferentes (o a objetos diferentes).
Resulta ventajoso el hecho de poder dar nombres iguales a métodos que realicen
acciones similares.
De este modo, aunque hayamos usado ya el identificador “dibujar” para el nombre de
una función (encargada de dibujar una circunferencia) es posible volver a utilizarlo
como nombre de otra función (que dibuje otra cosa, como por ejemplo un cuadrado).
Normalmente cuando se escribe una llamada a una de estas funciones, el programador
sabe de antemano cuál de ellas se ejecutará, por el tipo de parámetros que le pasa en la
llamada y por la clase a la que pertenece el método.
No obstante, en combinación con la herencia aparecen casos en los NO es posible saber
de antemano la clase de la que realmente se ha instanciado un objeto.
Según hemos visto, cualquier objeto podría ser pasado como parámetro al método
comparadoCon(Object...) del anterior ejemplo y sus sentencias intentarán averiguar la
clase real del objeto, dando respuestas adecuadas al tipo concreto de dato facilitado.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 219
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Observese en este ejemplo. Sólo en el momento en se va a ejecutar la función (no en el
momento de compilar), se podrá saber la clase real del objeto.
..... . .........
System.out.print(“Introduzca numerador y denominador... ”);
Racional dato1=new Racional(Entrada.readInt(),Entrada.readInt());
Object dato2=null;
System.out.println(“Que clase quiere usar para compararlo con 25”);
System.out.println(“ ·Float (F)”);
System.out.println(“ ·Integer (I)”);
System.out.println(“ ·Byte (B)”);
System.out.println(“ ·Racional (otra letra)”);
System.out.print(“opcion: ”);
switch(Entrada.readChar()) {
case ‘F’:
case ‘f’: dato2 = new Float(25.0F);
break;
case ‘I’:
case ‘i’: dato2 = new Integer(25);
break;
case ‘B’:
case ‘b’: dato2 = new Byte((byte)25);
break;
default: dato2 = new Racional(100,4);
}
System.out.print(dato1+“ y ”+dato2);
if(dato1.comparadoCon(dato2)==0)
System.out.println(“ son iguales.”);
else
System.out.println(“ son distintos.”);
....... ... ..... .
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 220
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Enlace Tardío
Si se ojea la última implementación del método ComparadoCon(...) de la clase Racional
se podrá observar cómo hace uso del operador instanceof, para intentar descubrir la
auténtica clase a la que pertenece el objeto que recibe y así hacer que se ejecute el
método doubleValue() que realmente le corresponde.
Existe la posibilidad de invocar a una función sin tener que preocuparse de cuál de entre
todas las que comparten un mismo identificador será la que realmente se ejecute. Es
decir, que en java podemos dejar que sea el ordenador el que elija el método que se debe
ejecutar.
El método ComparadoCon(...) de la clase Racional, podríamos haberlo implementado
simplemente así.
public int comparadoCon(Object obj) {
double oper1=doubleValor();
double oper2=0.0;
if(obj instanceof Racional)
oper2=((Racional)obj).doubleValor();
else
try {
oper2=((Number)obj).doubleValue();
} catch(ClassCastException e) {
String claseVerdadera=obj.getClass().getName();
throw new ClassCastException("Racionales no se comparan con "+claseVerdadera);
}
if (oper1 < oper2)
return -1;
else if (oper1==oper2)
return 0;
else
return 1;
}
Esto es posible gracias a que java (como otros muchos lenguajes hoy en día) dispone de
un mecanismo para escribir funciones polimórficas llamado enlace tardío con el que el
compilador gestiona automáticamente la elección del método que debe ejecutarse.
Todas las clases con las que comparábamos el objeto Racional (Float, Integer, Double,
...) derivan de la clase Number.
nota.- Esta es una clase abstracta definida en el paquete básico java.lang y todos sus métodos son
abstractos... pero podemos tener la certeza de que todas la clases no abstractas que derivan de ella
habrán definido (sobrecargado) todos sus métodos.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 221
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Desconocemos la clase real del parámetro Object que recibe en el método
comparadoCon(...), pero sus instrucciones presuponen que si no son de clase Racional,
debe ser de alguna clase que heredade de Number. Si no es así, al intentar moldear obj a
tipo Number se lanzará la excepción ClassCastException (como en la anterior versión).
Lo peculiar es que llegado el momento de ejecutar el método doubleValue() la llamada
se redirigirá por sí misma al método definido en la clase de la que realmente sea el
objeto Number. Por eso se le llama...enlace tardío a esta forma ejecutar un método.
class Integer {
public double doubleValue() {
·· · ··
}
}
class Long {
public double doubleValue() {
·· · ··
}
}
class Float {
public double doubleValue() {
·· · ··
}
}
·· ·· ···· ···
..((Number)obj).doubleValue();
······ ····· ···
class Double {
public double doubleValue() {
·· · ··
}
}
class Byte {
public double doubleValue() {
·· · ··
}
}
class Short {
public double doubleValue() {
·· · ··
}
}
La estrategia es siempre la misma...
• Se parte de una clase o interface de la que heredan (o que implementan) todas las
posibles clases con las que se instanciarán los objetos que vamos a manejar.
• Se declara un identificador cuyo tipo sea esta clase o interface común a todos estas
clases de objetos.
• Este identificador referenciará un objeto instanciado con una de estas clases hijas.
• Usando el identificador, se invoca uno de los métodos declarados en la clase o
interface base (que existirá necesariamente en el objeto, sea cual sea su clase)...
...y el compilador se encargará de prepararlo todo para que llegado el momento se
ejecute el código del metodo implementado en la clase de la que realmente sea el objeto
referenciado por el identificador.
Estos casos son los casos que generalmente se conocen como ejemplos de
polimorfismo. Cuando se solicita la ejecución de un método esperando algo genérico de
él, pero sin saber de antemano cuál será el método que realmente responderá ni cómo
llevará a cabo la petición que se solicita.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 222
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Existen otros muchos ejemplos de funciones polimórficas entre la clases estándar de
java, con las que a menudo se usa enlace tardío.
1.- Las existentes en todos los objetos de clases derivadas de una misma clase...
Como en el ejemplo visto, todos los objetos de cualquier
clase derivada de la clase abstracta Number tendrán
necesariamente implementados todos sus métodos.
De manera que podrían usarse de manera polimórfica
con enlace tardío.
Otro ejemplo podemos encontrarlo en el
paquete java.awt.geom. La clase abstracta
RectangularShape que declara una gran
cantidad de métodos con los que realizar
complicados cálculos geométricos ajustados
a cada tipo concreto de forma.
Desde
un
identificador
de
tipo
RectangularShape que referencie un objeto
de alguna de sus clases derivadas, como
Rectangle,
Elipse2D.Double
(anidada),
Elipse2D.Float
(anidada),
Arc2D.Float
(anidada), etc... podemos ejecutar métodos
que se implementan en cada clase de objeto
específico.
Autor : FernandoToboso Lara
int intValue()
long longValue()
float floatValue()
double doubleValue()
byte byteValue()
short shortValue()
boolean contains(Point2D ...)
boolean contains(Rectangle2D ...)
Rectangle getBounds()
double getMaxX()
double getMaxY()
double getMinX()
double getMinY()
double getWidth()
double getX()
double getY()
boolean intersects(Rectangle2D ...)
e-mail: [email protected]
etc, etc, etc...
Pag. 223
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
2.- Las de todos los objetos de clases que implementan una misma interface...
Definido (o heredado) por todos aquellos objetos que
implementan la interface Comparable.
nota.- La del paquete java.lang , no la del paquete tipos
de nuestros anteriores ejemplos. Funciona igual
que la nuestra, sólo cambia el nombre del método.
int compareTo(Object ...)
Podremos ordenar objetos de clase desconocida, si se
comprueba (mediante ) que implementa Comparable.
Todos los objetos cuyas clases implementan la
interface DataInput, implementan estas funciones
con las que permitirán leer de algún sitio, que cada
clase deberá determinar (un fichero, el teclado,
etc...).
Cada uno de los métodos retorna un dato con el
formato propio de las tipos primitivos de java.
Autor : FernandoToboso Lara
e-mail: [email protected]
boolean readBoolean()
char readChar()
double readDouble()
float readFloat()
byte readByte()
int readInt()
etc, etc, etc...
Pag. 224
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
3.- Las que todos los objetos heredan de la clase Object...
Todas las clases heredan de la clase Object, directa o
indirectamente, el método toString().
String toString()
Este retorna un String representando la forma escrita del objeto (por defecto el nombre
de su clase seguido de un código de identidad interno).
Este método puede ser sobrecargado (reescrito) en cualquier clase, de manera que
retorne un String con el que los objetos de la clase deban ser mostrados textualmente
(en forma de texto). Si se hace así, éste será el que indique a funciones como print(...), o
println(...), o a la concatenación (+), el texto con el que dicho objeto deberá escribirse,
en lugar del heredado de Object.
Como en lo scasos anteriores, las sentencias de llamada al método toString() de un
objeto, desconocen de antemano cuál será la implementación real del método toString()
que ejecutarán. Esta es por tanto una función polimórfica para los objetos de java de
aquellas clases que definen su propia implementación.
En el ejemplo que imprimía los colores del arco iris, la sentencia que imprime el valor
de la variable color, realmente no necesitaba usar la exprsión color.toString() puesto
que la concatenación lo llamará automáticamente, por la que sería suficiente
···· ··· ···· ···
Salida.println(“Color... “ + color );
color.incrementar().incrementar();
}
····· ·· ·
En lugar de como se escribió antes...
···· ··· ···· ··
Salida.println(“Color... “ + color.toString());
color.incrementar().incrementar();
}
····· ·· ·
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 225
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
El método finalize() que se hereda por defecto no hace nada,
sólo se asegura de estar presente en todo objeto, puesto que
será invocado por el recolector de basura para cada objeto que
vaya a eliminar cuando ya ningún identificador lo referencia.
void finalize()
Si los objetos de una clase tienen necesidad de realizar una última acción antes de ser
eliminados, sólo tienen que sobrecargar este método, incluyendo en él la sentencias
(póstumas) que deban ser ejecutadas.
nota.- Recuerda que no queda determinado el momento exacto de la eliminación de los objetos y por tanto
la ejecución automática de este método. El recolector de basura la hará cuando tenga tiempo
disponible.
El recolector sólo sabe que ejecutará el método finalize() de un objeto heredado de
Object pero realmente ignora si la implementación que ejecutará es la heredada de
Object, o la definida en alguna clase que la haya sobrecargado.
Para que se ejecute el clone() propio de su clase no es
tampoco necesario que el objeto esté referenciado como
objeto de su auténtica clase y sin embargo si ésta implementa
su propio método clone(), será éste (y no el de Object) el que
se ejecute.
Object clone()
Como ya vimos, los objetos que implementan Cloneable indican que pueden ser
duplicados. Si su copia simple (duplicado de memoria) no es adecuada su clase deberá
sobrecargar este método para que sus instrucciones se ocupen de realizar una correcta
duplicación, o copia detallada, como ya vimos anteriormente.
El siguiente ejemplo intenta crear una copia del objeto que recibe, para evitar hacer
modificaciones sobre el original (no obstante, si no lo consigue manipula el original).
Object accion(Object original) {
Object copia;
if(original instanceof Cloneable)
copia = original.clone();
else
copia = original;
// se realiza alguna acción sobre copia.
return copia;
}
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 226
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Es común que este método sea sobrecargado por cada
clase para comparar adecuadamente sus objetos y
como en los demás casos, no se precisa conocer la
clase real del objeto para poder compararlo con otro.
boolean equals(Object ...)
Por defecto este método retorna true cuando el objeto recibido como parámetro es el
propio objeto. Es decir que por defecto equals(...) sólo es cierto cuando compara dos
referencias al mismo objeto.
nota.- Como el operador = = , cuando compara objetos (datos de tipo no primitivo).
“Hola Carola” == “Hola Carola” ............. es false por ser objetos distintos.
“Hola Carola”.equals(“Hola Carola”) ..... es true porque la clase String
sobrecarga el método equals(...) .
Si la clase de objetos tiene orden (implementa Comparable), su resultado suele ser
consistente con el orden natural impuesto por compareTo(Object ...). Si este método
retorna cero (0), equals(...) suele retornar cierto (true).
Pero esto no siempre tiene por que cumplirse.
Los objetos Racional son buen ejemplo de ello,
orden se refiere y sin embargo no son iguales.
2
4
es tiene equivalente
1
2
en lo que a su
ejem.- No es igual 1/2 de euro.... una moneda de 50 centimos.
que 2/4 de euro... dos monedas de 25 centimos...(particularmente porque la moneda
de 25 centimos no existe).
public boolean equals(Object obj) {
Racional elOtro= (Racional)obj;
return numerador==elOtro.numerador && denominador==elOtro.denominador;
}
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 227
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
ejemplo: La Pizarra
Analizaremos todo esto desarrollando un ejemplo más completo.
Supongamos que necesitamos crear clases como Elipse y Rectangulo para un programa
de dibujo y geometría.
Los atributos que describen sus posiciones y los métodos que las modifican ciertamente
serán comunes a cualquier “figura” por lo que podríamos incluirlos en una clase base
que llamaremos Figura de la que las clases hereden, evitando así duplicar dichas partes
en cada una de nuestras clases.
Al margen de esto, lo más significativo en este caso es el hecho de que estas clases
comparten unos conceptos (como área, perímetro, altura, anchura, o la acción de ser
dibujadas) que precisan en cada figura de procedimientos diferentes para ser calculados
(o para llevarse a cabo) pero que suelen denominarse igual puesto que se refieren
conceptualmente a lo mismo.
De hecho puesto que tiene mucho sentido el definir estos conceptos para cualquier tipo
de figura, sería deseable que en la clase Figura (de la que heredarán todas la clases de
figura) existan estos elementos, para que todas sus hijas los hereden.
Concretamente estos elementos miembros de cualquier
objeto figura se corresponderían con cinco métodos,
cuyos prototipos deberían ser algo así y que después se
sobrecargarán en las clases Elipse y Rectangulo.
Después, desde un identificador declarado de clase
Figura que referencie objetos Elipse y Rectangulo
podrán ser usardas con enlace tardío
float area()
float perimetro()
void dibujar()
int alto()
int ancho()
La clase Figura no puede pasar de ser nada más que un concepto abstracto. No sabría
cómo implementar sus propios métodos.
De modo que lo mejor será declararla abstracta y también sus cinco métodos area(),
perimetro(), alto(), ancho() y dibujar().
// Fichero: Figura.java
public abstract class Figura {
public Point posicion;
public Figura(int a, int b) {
posicion= new Point(a,b);
}
public abstract float area();
public abstract float perimetro();
public abstract int alto();
public abstract ancho();
public abstract void dibujar(Graphics g);
};
nota.- Los objetos de clase Graphics disponen de métodos con
los cuales dibujar fácilmente en un entorno gráfico.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 228
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
// Fichero: Rectangulo.java
public class Rectangulo extends Figura {
protected int lado1, lado2;
public Rectangulo(int a,int b,int c,int d) {
super(a,b);
lado1=c;
lado2=d; }
public float area() {return lado1*lado2;}
public float perimetro() {return lado1*2+lado2*2;}
public int alto() {return lado2;}
public int ancho() {return lado1;}
public void dibujar(Graphics g) {
Color cActual=g.getColor();
g.fillRect(posicion.x,posicion.y,lado1,lado2);
g.setColor(Color.white);
g.drawRect(posicion.x,posicion.y,lado1,lado2);
g.setColor(cActual);
}
};
// Fichero: Elipse.java
public class Elipse extends Figura {
public final static float PI=3.1415F;
protected int dMenor,dMayor;
public Elipse(int a, int b, int c, int d) {
super(a,b);
dMenor=c;
dMayor=d; }
public float area() {return PI * dMenor * dMayor;}
public float perimetro() {return PI*(dMenor+dMayor);}
public int alto() {return dMayor;}
public int ancho() {return dMenor;}
public void dibujar(Graphics g) {
Color cActual=g.getColor();
g.fillOval(posicion.x,posicion.y,dMenor,dMayor);
g.setColor(Color.white);
g.drawOval(posicion.x,posicion.y,dMenor,dMayor);
g.setColor(cActual);
}
};
Es fundamental que estos métodos existan en la clase Figura (aún siendo abstractos),
para poder usar enlace tardío...
·· · · ······ ·····
Figura algunaFigura;
if (<condicion>)
algunaFigura = new Rectangulo(50,50,20,40);
else
algunaFigura = new Elipse(50,50,20,40);
// Aquí se realiza la llamada usando enlace tardio, sin que importe el
// hecho de que ya no sabemos cuál de los métodos dibujar() se ejecutará.
algunaFigura.dibujar();
···· · ······ ·
...porque de otro modo el compilador hallaría errónea la llamada a las funciones desde
el identificador de clase Figura.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 229
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Efectivamente, puesto que las clases Rectangulo y Elipse heredan de la clase Figura...
los objetos de las clase Rectangulo y Elipse son más genéricamente objetos de clase
Figura, por lo que pueden ser referenciarlos con un identificador declarado de clase
Figura.
Dependiendo de cuál de las alternativas del if(...) se ejecute (en cada ocasión puede ser
indistintamente una u otra) la figura referenciada será de una clase u otra, por lo que en
el momento de compilar este código no es posible saber con certeza la clase concreta de
objeto que el identificador algunaFigura va a referenciar.
La última sentencia ejecutará el método concreto definido en la clase (Rectangulo o
Elipse) de la que realmente se creó el objeto asignado al identificador algunaFigura.
Pero de antemano es imposible anticipar cuál de ellas se ejecutará y sólo cuando el
programa se esté ejecutando se optará por un método o por otro según se halla elegido
en la condición de la sentencia if().
Sea cuál sea la figura instanciada, se dibujará correctamente con el método que le
corresponda. Por lo que se están otorgando a este concepto varias formas de proceder
según sea el caso concreto. Es decir se le da un significado polimórfico al método
dibujar(), que por herencia y sobrecargado estará disponible en toda figura.
A continuación podemos ver un ejemplo completo en el programa Pizarra (cuyo código
completo aparece al final de este apartado) del que resaltaremos aquí algunos
fragmentos. En él encontraremos el uso de varias clases de Figura que son dibujadas
con el mismo método dibujar() sin necesidad de precisar qué tipo concreto de figura se
ha seleccionado y los otros métodos también sobrecargados para cada clase concreta de
figura.
clase Figura
declaración del
atributo posicion
de clase Point
métodos genericos
Objeto de clase Pizarra
atributos:
p
constructor()
y otros metodos...
clase Rectangulo
clase Elipse
clase Triangulo
declaración de los
atributos lado1 y lado2
PI
declaración de los
atributos lado1, lado2
lado3, lx, y altura
métodos específicos
clase Pincel
INCREMENTO 5
CIRCULO
1
ELIPSE
2
CUADRADO
3
RECTANGULO 4
TRIANGULO
5
TOP_ABJ
345
TOP_ARR
10
TOP_DER
445
TOP_IZQ
5
pincelActivo
true
Constructor
y otros métodos
para acciones
de los pinceles.
Autor : FernandoToboso Lara
y declaración de los
atributos dMenor y dMayor
métodos específicos
métodos específicos
Objeto clase Pincel
Atributos:
punta
3.1415
clase Cuadrado
clase Circulo
constructor propio
constructor propio
Objeto de clase ¿¿¿????
heredada de Figura
Objeto de
clase Point
atributos:
Atributos:
x 350
y 30
posicion
métodos:
...area()
...perimetro()
...alto()
...ancho()
...dibujar(...)
e-mail: [email protected]
métodos
Pag. 230
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
•
(en el paquete por defecto... directorio base)
Pizarra.java: Es la menos interesante para lo que ahora estamos tratando. La clase Pizarra contiene la
función principal main() en la que comienza la ejecución del programa y se hace cargo de la
interface gráfica (ventana, teclado, ...).
Dependiendo de las pulsaciones que reciba desde el teclado... moverá el pincel, lo activará y
desactivará, o lo substituirá por otro que pinte con un objeto Figura distinto.
Con cada pulsación, se solicita a un objeto figura que se dibuje (en su posición actual). Para esto le
pasa un objeto de la clase Graphics (del paquete java.awt) que dispone de métodos gráficos, a su
método dibujar(Graphics ...).
Además se encarga de mostrar en la ventana del interprete de comandos una pequeña lista de
las opciones posibles.
Pincel.java: Los objetos Pincel pintan cada vez que se llama a su método dibujar(...), sólo si su atributo
pincelActivo vale true. Dependiendo del valor que se dé al parámetro forma de su constructor (al
ser instanciado) su atributo punta referenciará una clase u otra de objeto Figura, por lo que con
una misma sentencia punta.dibujar(g), pintará con una forma geométrica diferente en cada
caso.
... ... ..... .
//atributos
private Figura punta;
public boolean pincelActivo;
... .....
//constructor
public Pincel(int forma,int px,int py, boolean activo) {
pincelActivo=activo;
switch(forma) {
case 1: punta=new Circulo(px,py,20);
break;
case 2: punta=new Elipse(px,py,20,40);
break;
case 3: punta=new Cuadrado(px,py,20);
break;
case 4: punta=new Rectangulo(px,py,20,40);
break;
case 5: punta=new Triangulo(px,py,20,40,42);
}
}
... .....
public void dibujar(Graphics g) {
if(pincelActivo)
punta.dibujar(g); // no se sabe qué método se ejecutará. Sólo que
// será el de una de las clase hijas de Figura
dibujarPaneles(g,10,40);
}
... ........ ..
Si el atributo pincelActivo del pincel está a true se pedirá, a la figura referenciada por punta que se
dibuje.
Cada vez que se instancia un nuevo objeto Pincel se puede especificar (mediante el constructor) si
se desea que inicialmente este activo, o no.
El método privado dibujarPaneles(...) se ocupará de mostrar unos paneles que informan del área,
perímetro, etc... de la figura que hace de “punta de pincel” en cada momento.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 231
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
... .. ...
public void arr() {
if(punta.posicion.y > TOP_ARR)
punta.posicion.y -= INCREMENTO;
else
punta.posicion.y = TOP_ABJ;
}
... ... ..
... .....
public void izq() {
if(punta.posicion.x > TOP_IZQ)
punta.posicion.x -= INCREMENTO;
else
punta.posicion.x = TOP_DER;
}
····· ··· ·
.. . ... ..
public void abj() {
if(punta.posicion.y < TOP_ABJ)
punta.posicion.y += INCREMENTO;
else
punta.posicion.y = TOP_ARR;
}
... .....
. . . ... ..
public void der() {
if(punta.posicion.x < TOP_DER)
punta.posicion.x += INCREMENTO;
else
punta.posicion.x = TOP_IZQ;
}
.. . ... ..
nota.- Los atributos TOP_ARR, TOP_ etc.. pertenecen también a la clase Pincel aunque los
inicializa la clase Pizarra con el tamaño de la ventana gráfica.
La posición del pincel será la posición que tenga la figura, pero los desplazamientos de la figura
punta del pincel, los controlan los métodos arr(), abj(), der(), izq() del objeto Pincel, que se
ocuparán de modificar los atributos x e y, del atributo posicion de la Figura punta.
Estas funciones modifican la coordenada de manera controlada para evitar que rebasen los límites
de la ventana.
(en el paquete figuras... subdirectorio figuras)
Figura.java: Es la clase base de todas las figuras heredan. Sólo se
ocupa de gestionar el atributo posición. Y aunque define los
métodos comunes a todas las figuras no ejecuta en ellos ninguna
instrucción, (porque no sabría cómo actuar).
nota.- El código correspondiente fué mostrado ya antes.
Elipse.java: Hereda de la clase Figura la posición (su constructor se
limita a pasar mediante super(...) sus coordenada al constructor
de la clase base). Añade los atributos de los diámetros, dMayor y
dMenor, que su constructor inicializa.
Lo más interesante es la sobrecarga de los métodos de la figura
genérica con instrucciones especificas para calcular el área y
el perímetro de la elipse y para dibujarla adecuadamente.
Estos serán los métodos que realmente se ejecuten cuando se
invoquen desde el atributo punta declarado de clase Figura,
cuando lo que realmente referencie sea un objeto Elipse.
nota.- Código correspondiente ya expuesto.
Autor : FernandoToboso Lara
e-mail: [email protected]
(posicion.x,
posicion.y)
x
(posicion.x,
posicion.y)
x
dMenor
•
dMayor
Pag. 232
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
public class Circulo extends Elipse {
public Circulo(int a, int b, int d) {
super(a,b,d,d); }
};
(posicion.x,
posicion.y)
x
diametro
Circulo.java: Hereda de la clase Elipse (e indirectamente de Figura)
la posición y los dos radios. Puesto que un círculo es una elipse
con los diámetros (mayor y menor) iguales, los métodos de la
clase Elipse siguen siendo válidos. Simplemente se le definirá un
constructor para recibir sus coordenadas y su único diámetro,
datos que se pasará mediante super(...) al constructor de la clase
base Elipse, dándole el mismo único valor en ambos diámetros,
el diámetro del círculo.
diametro
Los métodos de la elipse (que sobrecargan a los de la figura genérica), serán igualmente los
métodos que realmente se ejecuten cuando se soliciten desde el atributo punta cuando este
referencie un objeto Circulo.
Cuadrado.java: Similar a la clase Circulo, pero heredando de
Rectángulo.
public class Cuadrado extends Rectangulo {
public Cuadrado(int a,int b,int c) {
super(a,b,c,c);
}
};
lado2
(posicion.x,
posicion.y)
x
lado1
(posicion.x,
posicion.y)
x
lado
Rectangulo.java: Hereda de la clase Figura la posición (su
constructor se limita a pasar mediante super(...) sus coordenada
al constructor de la clase base). Añade los atributos que
describen sus lados, lado1 y lado2, que su constructor inicializa.
Sobrecarga los métodos de la figura genérica con las
instrucciones especificas para calcular el área y el perímetro
del rectángulo y para dibujarlo adecuadamente. Estos se
ejecutarán cuando se soliciten desde punta y éste referencie un
objeto Rectangulo.
nota.- Código correspondiente ya mostrado.
lado
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 233
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Triangulo.java: Similar a las clase vistas, pero concretando la descripción de un triángulo.
El constructor comprueba además que no se construyen triángulos imposibles (con un lado mayor
o igual que la suma de los otros lados).
(posicion.x,
posicion.y)
lado1
la
do
3
2
altura
x
lado
public class Triangulo extends Figura {
protected int lado1,lado2,lado3;
private
int lx,altura;
public Triangulo(int a, int b, int c, int d, int e) {
super(a,b);
// Toma el valor del lado mayor
int elMayor=(c>d & c>e)? c : ((d>e)? d : e );
//Si elMayor es >= que la suma de los demás lados
if( elMayor>=(c+d+e-elMayor) )
throw new IllegalArgumentException("Lados imposibles");
lado1=c; lado2=d; lado3=e;
lx=(lado2*lado2-lado3*lado3-lado1*lado1)/(2*lado1);
altura=(int)Math.sqrt(lado3*lado3-lx*lx);
}
public float area() {return (lado1*altura)/2;}
public float perimetro() {return lado1+lado2+lado3;}
public int alto() {return altura;}
// Segun se dibuja
public int ancho() {return lado1;} // esta figura
public void dibujar(Graphics g) {
Color cActual = g.getColor();
int[] xPuntos ={ posicion.x,
posicion.x+lado1,
posicion.x+lado1+lx };
int[] yPuntos ={ posicion.y,
posicion.y,
posicion.y+altura };
g.fillPolygon(xPuntos, yPuntos, 3);
g.setColor(Color.white);
g.drawPolygon(xPuntos, yPuntos, 3);
g.setColor(cActual);
}
};
lx
Este es aproximadamente el aspecto que puede tomar el programa (puede variar un
poco dependiendo del sistema en que se ejecute) después de jugar un poco los distintos
pinceles geométricos.
nota.- Se ejecuta desde la clase
Pizarra, que creará un objeto
Pincel
inicialmente
(y
nuevamente cada vez que
solicite cambio de pincel).
Será cada objeto Pincel
instanciado quien creará el
objeto (de alguna clase
heredada de Figura) con el que
dibujará.
nota.- El dibujo sólo permanece
mientras la ventana no se
redibuje.
nota.- Mientras el pincel esté
inactivo podremos moverlo
pero no veremos por donde va.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 234
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Código Completo de la Aplicación PIZARRA
// Pincel.java
// *********** ************************************************************
import
import
import
import
java.awt.Graphics;
java.awt.Point;
java.awt.Color;
figuras.*;
public class Pincel {
private static final int INCREMENTO=5;
public static final int CIRCULO=1;
public static final int ELIPSE=2;
public static final int CUADRADO=3;
public static final int RECTANGULO=4;
public static final int TRIANGULO=5;
public static int TOP_ARR;
public static int TOP_ABJ;
public static int TOP_IZQ;
public static int TOP_DER;
private Figura punta;
public boolean pincelActivo;
public Pincel(int forma,int px,int py) {
this(forma, px, py, false);
}
public Pincel(int forma,int px,int py, boolean activo) {
pincelActivo=activo;
switch(forma) {
case 1: punta=new Circulo(px,py,20);
break;
case 2: punta=new Elipse(px,py,20,40);
break;
case 3: punta=new Cuadrado(px,py,20);
break;
case 4: punta=new Rectangulo(px,py,20,40);
break;
case 5: punta=new Triangulo(px,py,20,40,42);
}
}
public void dibujar(Graphics g) {
if(pincelActivo)
punta.dibujar(g); // no se sabe que método concreto se ejecutará.
dibujarPaneles(g,10,40);
}
private void dibujarPaneles(Graphics g, int xCoord, int yCoord) {
int xPunta, yPunta;
Color cActual;
xPunta=punta.posicion.x;
yPunta=punta.posicion.y;
cActual=g.getColor();
g.setColor(Color.white);
// Cajas de informacion
g.fillRect(xCoord,yCoord
,60,15);
g.fillRect(xCoord,yCoord+20,250,15);
g.fillRect(xCoord,yCoord+40,260,15);
g.fillRect(xCoord,yCoord+60,100,15);
// Canal de cursores
g.fillRect(1,1,5,Pincel.TOP_ABJ);
g.fillRect(1, Pincel.TOP_ABJ-1, Pincel.TOP_DER, 3);
g.setColor(cActual);
// Cursores
g.fillRect(1,yPunta,5,punta.alto());
g.fillRect(xPunta, Pincel.TOP_ABJ-1, punta.ancho(), 3);
// Informacion del pincel
g.drawString("(" + xPunta + "," + yPunta +")",
xCoord+1,yCoord+11);
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 235
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
g.drawString("Area de la figura (punta de pincel): "+punta.area(),
xCoord+1,yCoord+31);
g.drawString("Perimetro de la figura (punta de pincel): "+punta.perimetro(),
xCoord+1,yCoord+51);
if(pincelActivo)
g.drawString("Pincel Apoyado", xCoord+1,yCoord+71);
else
g.drawString("Pincel Levantado", xCoord+1,yCoord+71);
}
public void arr() {
if(punta.posicion.y > TOP_ARR)
punta.posicion.y -= INCREMENTO;
else
punta.posicion.y = TOP_ABJ;
}
public void abj() {
if(punta.posicion.y < TOP_ABJ)
punta.posicion.y += INCREMENTO;
else
punta.posicion.y = TOP_ARR;
}
public void izq() {
if(punta.posicion.x > TOP_IZQ)
punta.posicion.x -= INCREMENTO;
else
punta.posicion.x = TOP_DER;
}
public void der() {
if(punta.posicion.x < TOP_DER)
punta.posicion.x += INCREMENTO;
else
punta.posicion.x = TOP_IZQ;
}
public Point leePosicion() {
return new Point(punta.posicion);
}
}
// Pizarra.java
// ************ ************************************************************
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Pizarra extends JFrame{
public static void main(String [] arg) {
new Pizarra();
}
private
private
private
private
private
private
final
final
final
final
final
final
int
int
int
int
int
int
ESP=32;
ESC=27;
ARR=38;
ABJ=40;
IZQ=37;
DER=39;
Pincel p;
Pizarra() {
System.out.println("");
System.out.println("_____________________________________________________________");
System.out.println("Dibuja con flechas de teclado arrastrando figuras geométricas");
System.out.println();
System.out.println("Para cambiar de figura puedes pulsar en cualquier momento");
System.out.println();
System.out.println("___________________________");
System.out.println(" 1 .....Circulo");
System.out.println(" 2 .....Elipse");
System.out.println(" 3 .....Cuadrado");
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 236
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
System.out.println(" 4 .....Rectangulo");
System.out.println(" 5 .....Triangulo");
System.out.println("___________________________");
System.out.println("la barra espaciadora levanta o apoya el pincel en la pizarra");
System.out.println("____________________________________________________________");
Pincel.TOP_ARR=10;
Pincel.TOP_IZQ=5;
Pincel.TOP_ABJ=345;
Pincel.TOP_DER=445;
p=new Pincel(Pincel.CIRCULO,50,150,true);
//valores de inicializacion de la ventana del programa.
//*****************************************************
setSize(Pincel.TOP_DER+5,Pincel.TOP_ABJ+5);
setResizable(false);
setLocation(1,1);
// hace visible la ventana.
setVisible(true);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
dispose();
System.exit(0);
}
}); // add window listener
addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
switch(e.getKeyCode()) {
case IZQ:p.izq();
break;
case DER:p.der();
break;
case ARR:p.arr();
break;
case ABJ:p.abj();
break;
case ESP:p.pincelActivo=!p.pincelActivo;
break;
case ESC:System.exit(0);
break;
case Pincel.CIRCULO+48: p=new Pincel(Pincel.CIRCULO,
p.leePosicion().x,
p.leePosicion().y);
break;
case Pincel.ELIPSE+48: p=new Pincel(Pincel.ELIPSE,
p.leePosicion().x,
p.leePosicion().y);
break;
case Pincel.CUADRADO+48: p=new Pincel(Pincel.CUADRADO,
p.leePosicion().x,
p.leePosicion().y);
break;
case Pincel.RECTANGULO+48:
p=new Pincel(Pincel.RECTANGULO,
p.leePosicion().x,
p.leePosicion().y);
break;
case Pincel.TRIANGULO+48:
p=new Pincel(Pincel.TRIANGULO,
p.leePosicion().x,
p.leePosicion().y);
};
p.dibujar(getGraphics());
}
}); // add key listener
}
public void paint(Graphics g) {
super.paint(g);
p.dibujar(g);
}
};
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 237
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
// Figura.java
// *********** ************************************************************
package figuras;
import java.awt.Point;
import java.awt.Graphics;
public abstract class Figura {
public Point posicion;
public Figura(int a, int b) {
posicion= new Point(a,b);}
abstract public float area();
abstract public float perimetro();
abstract public int alto();
abstract public int ancho();
abstract public void dibujar(Graphics g);
};
// Elipse.java
// *********** ************************************************************
package figuras;
import java.awt.Graphics;
import java.awt.Color;
public class Elipse extends Figura {
public final static float PI=3.1415F;
protected int dMenor,dMayor;
public Elipse(int a, int b, int c, int d) {
super(a,b);
dMenor=c;
dMayor=d; }
public float area() {return PI * dMenor * dMayor;}
public float perimetro() {return PI*(dMenor+dMayor);}
public int alto() {return dMayor;}
// Segun se dibuja
public int ancho() {return dMenor;} // esta figura
public void dibujar(Graphics g) {
Color cActual=g.getColor();
g.fillOval(posicion.x,posicion.y,dMenor,dMayor);
g.setColor(Color.white);
g.drawOval(posicion.x,posicion.y,dMenor,dMayor);
g.setColor(cActual);
}
};
// Circulo.java
// ************ ************************************************************
package figuras;
public class Circulo extends Elipse {
public Circulo(int a, int b, int d) {
super(a,b,d,d); }
};
// Rectangulo.java
// *************** ************************************************************
package figuras;
import java.awt.Graphics;
import java.awt.Color;
public class Rectangulo extends Figura {
protected int lado1, lado2;
public Rectangulo(int a,int b,int c,int d) {
super(a,b);
lado1=c;
lado2=d; }
public float area() {return lado1*lado2;}
public float perimetro() {return lado1*2+lado2*2;}
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 238
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
public int alto() {return lado2;}
// Segun se dibuja
public int ancho() {return lado1;} // esta figura
public void dibujar(Graphics g) {
Color cActual=g.getColor();
g.fillRect(posicion.x,posicion.y,lado1,lado2);
g.setColor(Color.white);
g.drawRect(posicion.x,posicion.y,lado1,lado2);
g.setColor(cActual);
}
};
// Cuadrado.java
// ************* ************************************************************
package figuras;
public class Cuadrado extends Rectangulo {
public Cuadrado(int a,int b,int c) {
super(a,b,c,c); }
};
// Triangulo.java
// ************** ************************************************************
package figuras;
import java.awt.Graphics;
import java.awt.Color;
public class Triangulo extends Figura {
protected int lado1,lado2,lado3;
private
int lx,altura;
public Triangulo(int a, int b, int c, int d, int e) {
super(a,b);
int elMayor=(c>d & c>e)? c : ((d>e)? d : e ); //Toma el valor del lado mayor
if(elMayor>=(c+d+e-elMayor)) //Si elMayor es >= que la suma de los demás lados
throw new IllegalArgumentException("Los lados del triangulo son imposibles");
lado1=c; lado2=d; lado3=e;
lx=(lado2*lado2-lado3*lado3-lado1*lado1)/(2*lado1);
altura=(int)Math.sqrt(lado3*lado3-lx*lx); }
public float area() {return (lado1*altura)/2;}
public float perimetro() {return lado1+lado2+lado3;}
public int alto() {return altura;}
// Segun se dibuja
public int ancho() {return lado1;} // esta figura
public void dibujar(Graphics g) {
Color cActual = g.getColor();
int[] xPuntos = {posicion.x,
posicion.x+lado1,
posicion.x+lado1+lx };
int[] yPuntos = {posicion.y,
posicion.y,
posicion.y+altura};
g.fillPolygon(xPuntos, yPuntos, 3);
g.setColor(Color.white);
g.drawPolygon(xPuntos, yPuntos, 3);
g.setColor(cActual);
}
};
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 239
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Clases alojada y herencia (uso polimórfico)
Tal como se comentó anteriormente, las clases alojadas (no estáticas) no pueden ser
directamente utilizadas desde fuera de los objetos que las alojan. Pero a pesar de esto, sí
es posible utilizar objetos instanciados de ellas, siempre y cuando se usen haciéndolos
pasar por objetos de una clase más genérica de la cual dicha clase alojada herede (en
último extremo de la clase Object).
Para que esto sea posible, debe existir siempre un método miembro de la clase que aloja
para que se ocupe de crear el objeto de la clase Alojada, ya que desde fuera no puede
hacerse referencia directa a ella absolutamente para nada.
// Fichero: UnaClase.java
public class UnaClase {
······ ··· ····· ··
class Alojada extends ClaseBase{
··· ·· ··
public String toString() {
return “soy un objeto instanciado de la clase Alojada.”;
}
}
ClaseBase crearObjetoDeLaClase_Alojada() {
return new Alojada();
}
}
// Fichero: PruebaAlojada.java
public class PruebaAlojada {
... main(String[] arg) {
UnaClase objetoAnfitrion = new UnaClase();
· ···· ···
// obtener un objeto instanciado de la clase Alojada, en el objeto objetoAnfitrion.
// aparentando ser de clase ClaseBase.
ClaseBase objetoDeClaseAlojada = objetoAnfitrion.crearObjetoDeLaClase_Alojada();
System.out.println(“Aparentemente tipo ClaseBase pero ” + objetoDeClaseAlojada);
· ···· ···
}
}
Su ejecución mostraría el siguiente mensaje...
C:\> java PruebaAlojada
Aparentemente de tipo ClaseBase pero soy un objeto instanciado de clase Alojada.
nota.- Recuerda que println(...) muestra el String que le retorna el método toString()
sobrecargado en la clase con la que realmente se ha instanciado el objeto que le pasan.
Usando ese método podrán crearse objetos de la clase Alojada y aunque desde fuera se
les tenga que usar como si fuesen de alguna de sus clases base (Object o ClaseBase) el
enlace tardío hará que cuando se invoquen sus métodos, indirectamente se ejecuten los
métodos implementados en la propia clase Alojada.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 240
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
ejemplo: Enumeracion de líneas
En el paquete java.util junto a la clase Vector, existe una interface llamada
Enumeration, que declara el prototipo de dos métodos con los que pretende estandarizar
un modo sencillo de recorrer los elementos de almacenados en un objeto.
interface Enumeration {
boolean hasMoreElements();
Object nextElement();
}
El primer método nos permitiría saber si quedan elementos por recorrer, el otro nos
daría en cada llamada el siguiente el siguiente secuencialmente.
La clase Vector, implementa un método llamado elements() que retorna un objeto
Enumeration con el que podemos recorrer secuencialmente los objetos que almacena.
Realmente se trata de un objeto de alguna clase Alojada que implementa la interface
Enumeration y como tal podemos usarlo polimorficamente.
nota.- Piensa que realmente no es posible que existan objetos instanciados de interfaces.
ejem.- Este código, implementan una
función que cuenta las veces que un
dato aparece repetido en un Vector.
Para ello utiliza un objeto
Enumerator obenido a través del
método elements() del propio objeto
Vector.
· ···· ·· ·· · ·
...static int vecesRepetido(Vector v, Object dato) {
Enumerator i = v.elements();
int veces=0;
while( i.hasMoreElements() )
if(i.nextElement().equals(dato))
veces++;
return veces;
}
····· · · · ····
Las implementaciones de los métodos hasMoreElements() y nextElement() que
realmente se ejecutarán, son la definidas en la clase alojada del objeto Vector.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 241
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Clases anónimas
Las clases que hasta ahora hemos visto se pueden clasificar en ...
• clases declaradas globales (no declaradas dentro de otra clase)
• clases internas (declaradas dentro de otra clase)
• clases anidadas (declaradas static).
• clases alojadas (no estáticas).
• clases locales (declaradas dentro de un método estático o no).
Existe un tipo de clases internas un tanto particular a las que se llama clases anónimas,
que no disponen de identificador con que nombrarlas. Suele tratarse de clases locales,
aunque también pueden ser clases miembro de objetos (anidadas o alojadas).
Son útiles cuando se necesita instanciar un único objeto, permitiendo definir una clase
(generalmente pequeña) y crear simultáneamente el objeto, en el mismo fragmento de
código.
Aquí suele ser particularmente útil el uso de métodos inicializadores de objeto, que no
precisan nombre para ser definidos para asegurar que el objeto se crea con un estado
inicial adecuado. Como la clase no tiene nombre no se le pueden definir constructores
que inicialicen cada objeto instanciado (ni falta que hace puesto que sólo se instancia
un único objeto).
La sintaxis con la que se especifica e instancia un objeto de clase anónima es, ...
...cuando la clase anónima no declara heredar ni implementar nada:
...... .... . .....
...new {
// definición de los elementos de la clase anónima
// (atributos, métodos y clases)
} ... .. .
.... . ... .... ..
...cuando la clase anónima hereda alguna clase, o implementa alguna interface:
.. .. .. .... . .. ...
...new clase o interface base ( parámetros del constructor de la clase base ) {
// definición de los elementos de la clase anónima
// (atributos, métodos y clases)
} . ... . ...
.... ... . ... ..
nota.- Recuerda que si implementa una interface (o si no declara heredar de ninguna clase), al heredar
por defecto de Object cuyo único constructor no recibe parámetros, el objeto (de clase anónima)
se instanciará sin parámetros iniciales.
Como cualquier otro, el objeto instanciado resultante de la ejecución de new suele, ...
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 242
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
- ... ser asignado a un identificador que haya sido declarado de una clase (o interface)
de la cuál herede (o a la cuál implemente).
- ... o bien ser pasado como parámetro en la llamada a un método que espere recibir un
objeto de una clase (o interface) de la cuál la clase anónima herede (o a la cuál
implemente).
Cuando el fichero fuente se compila, se genera un fichero NombreDeLaClase.class para
cada clase.
La compilación de cada clase anónima genera igualmente un fichero .class , cuyo
nombre resulta de la concatenación del nombre de la clase que la contiene seguido de un
número correlativo distinto para cada clase anónima... ClaseContenedora$número.class.
Las clases anónimas se utilizan muy a menudo cuando se trabaja en la gestión de
eventos en entornos gráficos, para la atención simultanea de dispositivos como el ratón,
el teclado, botones gráficos pulsados, módem telefónico, etc...
Ejemplos de clases anónimas los podemos encontrar en los ejemplos del curso en los
que se utilicen elementos gráficos y multimedia, como el fichero pista.java (de la
carrera de objetos Punto) donde se gestionan los eventos de la ventana gráfica y las
pulsaciones del teclado.
Los objetos de la clase Window (del paquete java.awt) y de las clase derivadas de ella,
disponen del método:
public void addWindowListener(WindowAdapter ...)
Una llamada a este método hará que algunos eventos relacionados con el objeto Window
(cerrar, maximizar, etc...) se asocien a métodos con instrucciones que se ejecutarán cada
vez que ocurra el evento, para darle respuesta.
Este método debe recibir un objeto de una clase derivada de WindowAdapter (del
paquete java.awt.event) ...
... abstract class WindowAdapter {
·· ·· ···
abstract void windowClosing(WindowEvent e) {
...
}
abstract void
...
}
windowDeactivated(WindowEvent e) {
abstract void
...
}
· ·· ···· ··
windowDeiconified(WindowEvent e) {
}
... que deberá sobrecargar los métodos correspondientes a los eventos a los que quiera
dar respuesta. Normalmente de la clase derivada de WindowAdapter sólo se necesitará
un objeto para pasarlo a addWindowListener(...) para lo cuál, una clase anónima es
ideal.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 243
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
nota.- En el paquete java.awt.event hay otras clases útiles para asociar acciones a otros tipos de eventos,
como por ejemplo las clases KeyAdapter, MouseAdapter, FocusAdapter. Cada una de ellas con
prototipos de métodos pensados para ejecutarse como respuesta a cada posible evento del entorno
gráfico.
En el ejemplo de la carrera de Puntos, el constructor de la clase Pista ejecuta el método
addWindowListener(...) (que hereda de JFrame) para pasarle la instancia de un objeto
de clase anónima que hereda de la clase WindowAdapter. Esta clase anónima sobrecarga
sólo el método asociado al evento que le interesa windowClosing(...) , método que se
ejecutará automáticamente cuando se pulsa el botón
de la esquina superior derecha
de la ventana para terminar el programa.
public class Pista extends JFrame{
.... . . ....
Pista() {
......
addWindowListener( new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
···· ··· ··· ·
}
}); // añade escuchador de eventos de ventana.
...... .. . ... ......
}
.. .. . . .. . .
}
De modo similar, lo siguiente pide que se asocien las pulsaciones del teclado al método
sobrecargado por el objeto de clase anónima que se pasa al método addKeyListener(...).
......
addKeyListener
(
new KeyAdapter() {
public void keyPressed(KeyEvent e) {
if(e.getKeyCode()==27)
System.exit(0);
··· ·· ···
}
}); // añade escuchador de eventos de teclado.
...... .. . ... ......
}
.. .. . . .. . .
}
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 244
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Capitulo 15
Estructuras de
Datos
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 245
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Colecciones de datos
Es habitual que un programa tenga necesidad de gestionar grupos de datos para
manipularlos, o simplemente mantenerlos almacenados con el fin de describir el estado
de algo.
Estos agrupamientos pueden ser clasificados en función de varias cuestiones, por
ejemplo:
- Que entre los datos contenidos exista alguna relación de orden
(sin orden propio: alimentos, coordenadas, montañas, etc... )
(con orden propio: distancias, alturas, nombres, porcentajes, etc...).
- Que la posición relativa entre los datos sea, o no, importante.
Lista de la Compra
Bacalao
Perejil
Menu
Pimientos
Cebollas
Caldo de Cilantro Primero
Bacalao a la vizcaina Segundo
Manzana Tercero
Clasificacion de Alimentos
Altamente Calóricos
Proteínas
Hidratos
de Carbono
Vitaminas y
Minerales
Bajos en Calorías
Tocino Cordero Salmón Almendras Pavo Lentejas Soja
Arroz
Pasta
Pan Patatas Boniato
Guisantes Habas
Aguacate Aceitunas Plátano Puerro Melón Champiñon Lechuga
- Según sea el procedimiento por el cuál se insertan, extraen, modifican, etc... los
elementos del grupo.
- etc...
En el paquete java.util podemos encontrar una buena cantidad de clases e interfaces que
son de gran utilidad para facilitar:
a ) La estandarización en el uso de los agrupamientos más comunes en programación
(mediante interfaces y clases abstractas).
b ) El uso directo de agrupaciones de datos completamente implementados (clases
no abstractas).
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 246
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
interface Collection
class AbstractCollection
class AbstractList
interface List
class ArrayList
class Vector
class Stack
class AbstractSequentialList
class LinkedList
interface Set
class AbstractSet
class HashSet
interface SortedSet
class TreeSet
Herencia
Implementación
CLASE
CLASE
ABSTRACTA
INTERFACE
a ) Algunas entidades como las interfaces y las clases abstractas, potenciarán la
integración del código que a partir de ellas desarrollemos. Todas las clases que
implementen una misma interface (o que hereden de una clase común) se podrán
manejar exactamente del mismo modo.
b ) Las clases (las no abstractas) nos permiten además instanciar directamente
objetos capaces de gestionar el almacenamiento en memoria de datos
referenciados cualquiera que sea su clase (Object).
a,b) Todos las clases en general nos permiten reutilizar su código, heredando de
ellas. Incluso las clases abstractas disponen de algunos métodos implementados
(no abstractos).
Todos los objetos (que implementen) Collection se comprometen a implementar los
métodos de la interface, que clasificaremos en tres apartados:
1.- Operaciones básicas.
- añadir un nuevo objeto. (add(Object o) )
- comprobar si esta vacía. (isEmpty() )
- comprobar si contiene un determinado objeto. (contains(Object o) )
- eliminar un objeto de la colección. (remove (Object o) )
- etc...
2.- Operaciones aplicadas sobre un grupo de objetos contenidos en la colección.
-
vaciar la colección. ( clear() )
añadir todos los elementos contenidos en otra colección. ( addAll(Collection c) )
comprobar si una colección contiene todos los elementos de otra. (containsAll(Collection c) )
etc...
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 247
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
3.- Operación de conversión, para obtener en forma de tabla de objetos, los
elementos contenidos en la colección (toArray() y toArray(Object[] t) ). Esto
resulta muy útil para poder seguir utilizando las funciones antiguas que manejan
grupos de datos en forma de tabla y no como Collection.
4.- Recorrido.
La finalidad de cualquier recorrido, será acceder directamente a cada uno de los
objetos de una colección para realizar sobre ellos individualmente alguna consulta
o modificación, e incluso en ocasiones eliminarlos de la colección que recorremos.
Los objetos Collection, se comprometen a implementar un método llamado
iterator() que retornará un objeto de alguna clase que implemente la interface
Iterator. Este objeto (del cuál trataremos después) nos permitirá acceder
secuencialmente a todos los objetos de cualquier colección de modo similar a como
ya hicimos (en el apartado de las clases alojadas) para recorrer un Vector con un
objeto que implementaba Enumeration.
Cuando una clase deriva de una clase o interface, se compromete también a ajustar la
implementación de sus métodos al comportamiento que de ella se espera. Éste debe
estar suficientemente documentado junto a cada clase base o interface implementada.
Las colecciones derivadas de AbstracList (o que implementen List), mantienen sus
objetos colocados en secuencias lineales.
• Unas veces formando bloques compactos en la memoria del sistema...
(preferentemente cuando derivan de AbstractList)
AGRUPACION DE DATOS FORMANDO UN ARRAY (O TABLA, O VECTOR)
DATO_1
D_2
DATO_3
DATO_4
D_5
D_6
D_n
• Otras en fragmentos que se encuentran dispersos por la memoria del sistema de
modo que cada dato conozca la ubicación de otro (u otros) en forma de
secuencia de datos.
(preferentemente cuando derivan a partir de AbstractSequentialList)
AGRUPACION DE DATOS FORMANDO UNA LISTA ENLAZADA
D_2
D_5
DATO_3
DATO_1
D_n
D_6
DATO_4
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 248
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Las clases derivadas de AbstractSet o que implementen Set, contienen objetos
agrupados que:
• o bien no dan importancia a la posición relativa en la que son almacenados,
• o bien forman estructuras no lineales como árboles y redes de datos.
AGRUPACION DE DATOS FORMANDO UN ARBOL
DATO
AGRUPACION DE DATOS FORMANDO UNA RED
DATO
DATO
DATO
DATO
DATO
DATO
DATO
DATO
DATO
DATO
DATO
DATO
DATO
DATO
DATO
Esto afecta evidentemente a la implementación que cada clase realizará para cada una
de las operaciones: añadir, eliminar, consultar, recorrido, etc... y en especial a
operaciones que son particulares de cada tipo de agrupamiento.
Iteradores
Es una interface muy parecida a la interface Enumeration y pertenece igualmente al
paquete java.util. Su sintaxis es:
interface Iterator {
boolean hasNext();
Object next();
void remove();
// cierto se quedan elementos.
// retorna cada vez que se la llama, el siguiente
// objeto de la Collection.
// generalmente, elimina de la Collection el último
// objeto retornado por next() pero su implementación
// es opcional.
}
nota.- Sus dos métodos primeros métodos tienen identica finalidad que lo correspondientes de la
interface Enumeration.
Lo objetos que lo implementan, nos permiten recorrer secuencialmente los elementos de
una Collection.
El ejemplo que vimos anteriormente con la interface Enumeration, sería casí igual
usando Iterator...
- usando un objeto Enumerator.
- y con un objeto Iterator.
..int vecesRepetido(Vector v, Object dato)
{
Enumeration i = v.elements();
int veces=0;
while( i.hasMoreElements() )
if(i.nextElement().equals(dato))
veces++;
return veces;
}
..int vecesRepetido(Vector v, Object dato)
{
Iterator i = v.iterator();
int veces=0;
while( i.hasNext() )
if(i.next().equals(dato))
veces++;
return veces;
}
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 249
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
La verdadera clase del objeto iterador no importa, de hecho será distinta para cada clase
de Collection. Sin embargo podremos hacer uso polimórficamente de sus métodos para
recorrer secuencialmente los objetos de la colección a la que pertenece.
El siguiente ejemplo imprime en la salida estándar un listado de los objetos contenidos
en el objeto Collection que se le pasa por parámetro.
... imprimir( Collection grupo ) {
Iterator i = grupo.iterator();
while( i.hasNext() )
System.out.println( i.next() );
}
nota.- Para que println(..) muestre cada objeto de la
colección correctamente, suponemos que estos
tienen sobrecargado el método toString().
Los iteradores que las colecciones implementan, suelen disponer de un método llamado
remove() con el que se puede eliminar objeto actual, pero su implementación es
opcional.
En los iteradores se entienden por dato actual, el último objeto que se les ha solicitado.
Si dicho método está adecuadamente implementado, el siguiente código por ejemplo
eliminará las repeticiones del objeto dato de la colección grupo (ambos recibidos como
parámetro):
... void eliminarRepetidos( Collection grupo, Object dato ) {
Iterator i = grupo.iterator();
boolean repetido=false;
while( i.hasNext() )
if(i.next().equals(dato))
if(repetido)
i.remove();
else
repetido=true;
}
nota.- Respeta sólo la primera copia que encuentra, en el recorrido que
hace el iterador de la Collection grupo.
Si no se desea implementar el método remove() y para evitar que la clase sea abstracta,
será preciso implementarlo de este modo:
class IteradorDeMiClaseCollection implements {
public boolean hasNext() {....}
public Object next() {...}
public void remove() {
throw new UnsupportedOperationException(“El iterador no puede eliminar.”);
}
···· ··· ····
}
Pero en tal caso la función eliminaRepetidos(...) lanzará una excepción de tipo
UnsupportedOperationException y terminará repentinamente sin conseguir su objetivo.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 250
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Ejemplo: Lista Enlazada
Es una estructura de datos dispersos, en la que sólo cada dato conoce la ubicación del
siguiente dato. De modo que sólo se necesita disponer de la referencia al primero de los
datos, para poder (a partir de él) acceder secuencialmente a todos los demás.
La lista está realmente formada por una secuencia de objetos Nodo que almacenan las
referencias a los dato Object.
Además utiliza una estrategia
llamada de Nodo Fictíceo que
consiste en colocar al principio
un nodo inicial sin dato válido.
Esto simplifica un poco la
implementación de los métodos
de la clase, pero deberemos
recordar que la lista ya estará
vacía cuando le quede este nodo
(que núnca debe ser eliminado).
objeto de clase
ListaEnlazada
objeto
Nodo Ficticeo
falsoNodoInicial
dato
sig
null
Métodos:
··· ·· ······· ··
objeto de clase
ListaEnlazada
objeto
Nodo Ficticeo
falsoNodoInicial
dato
sig
Métodos:
··· ·· ······· ··
objeto Nodo
dato
sig
objeto Nodo
objeto Nodo
dato
sig
dato
sig
null
import java.util.*;
public class ListaEnlazada {
protected static class Nodo {
Object dato;
Nodo sig;
}
protected Nodo falsoNodoInicial = new Nodo();
public void vaciar () {
falsoNodoInicial.sig=null;
}
public Iterator iterator() {
return new Desplazarse();
}
protected class Desplazarse implements Iterator {
private Nodo actual=falsoNodoInicial;
public boolean hasNext() {
return actual.sig != null;
}
public Object next() {
if(!hasNext())
throw new NoSuchElementException ("No hay mas elementos en la lista.");
actual= actual.sig;
return actual.dato;
}
public void remove() {
// no se ha implementado.
throw new UnsupportedOperationException("Funcion remove() no implementada.");
}
}
}
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 251
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Recorrer los elementos de una colección usando objetos iteradores (en vez hacerlo con
métodos del propio objeto Collection), tiene la ventaja de permitir que se realicen sobre
una misma colección varios recorridos simultaneamente. Incluso sería posible que una
colección ofreciese diferentes recorridos alternativos que podrían realizarse
simultaneamente.
objeto Iterator
.
actual
Recorrido: DATO_1 - D_2 - DATO_3 - D_4 - D_5 - DATO_6 - … - D_n
metodos
DATO_1
D_2
DATO_3
D_4 D_5
DATO_6
D_n
otro objeto de otra
clase que también
implementa Iterator
.
actual
Recorrido: D_2 - DATO_1 - DATO_3 - D_n - D_5
metodos
Las colecciones tipo lista que implementan la interface List, deben además manejar otro
tipo de iterador llamado ListIterator que ofrece, entre otras, la posibilidad de recorrer
sus objetos en ambos sentidos, pasando al siguiente elemento, o volviendo al anterior.
interface ListIterator extends Iterator {
// Consulta
boolean hasPrevious();
Object previous();
int nextIndex();
int previousIndex();
// Modificacion
void add(Object obj);
void set(Object obj);
}
En este ejemplo, la función esCapicua(...) nos permitiría comprobar si los objetos
contenidos en una lista se encuentran, o no, situados de manera simétrica.
public static boolean esCapicua( List lista ) {
if(lista.size()==0)
return false;
// Si no se ha ejecutado el return...
ListIterator ini,fin;
boolean simetrica=true;
// Se crear un ListIterador desde el inicio de la lista.
ini = lista.listIterator();
// Se crear un ListIterador desde el final de la lista.
fin = lista.listIterator(lista.size());
while(simetrica && ini.nextIndex() < fin.previousIndex())
simetrica = ini.next().equals(fin.previous());
return simetrica;
}
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 252
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Además disponen de otros métodos con los que:
- Consultar la posición relativa de los elementos siguiente o anterior.
- Sustituir el dato almacenado en la posición actual por otro dato.
- Insertar nuevos datos en la posición actual (o tras ella).
nota.- Téngase en cuenta que durante el proceso de recorrido con un iterador, cualquier modificación de
la Collection que no se realice directamente utilizando dicho objeto iterador, puede hacer (por
motivos de implementación) que éste se comporte de manera inesperada.
No siempre es así, pero ante una situación así suele ser recomendable desechar el iterador y crear
uno nuevo.
class Collections
nota.- No confundir con la interface Collection.
Aunque no implementa ni hereda de ninguna colección, la clase Collections, tiene una
gran relación con las colecciones de datos.
Sus elementos son todos estáticos y son en su mayoría métodos que implementan
algoritmos útiles para manipular objetos Collection. Utilidades como:
-
buscar el objetos menor (o el mayor) de los objetos de una colección.
invertir el orden de los objetos de una lista.
ordenar una lista.
desordenar aleatoriamente el contenido de una lista.
etc...
Vease aquí una muestra, para que sirva de ejemplo:
public static void pruebaCollections(Vector original) {
Vector delReves=(Vector)original.clone();
Vector revuelta=(Vector)original.clone();
Vector ordenada=(Vector)original.clone();
Collections.reverse(delReves);
Collections.shuffle(revuelta);
Collections.sort(ordenada);
System.out.println("Lista originalmente recibida..."+original);
System.out.println("Lista invertida..."+delReves);
System.out.println("Lista desordenada al azar..."+revuelta);
System.out.println("Lista ordenada..."+ordenada);
}
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 253
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Pila de datos y Cola de datos
Son estructuras de agrupamiento de datos muy interesantes. Se trata en ambos casos de
estructuras lineales (tipo lista), en las que los datos se añaden y extraen solamente por
los extremos.
• En una pila los datos se añaden y se extraen desde el mismo extremo.
DATO
DATO
· ···· ·· ····
Añadir DATO
····· ···· ······
· ···· ·· ····
Extraer DATO
····· ···· ······
DATO
DATO
DATO
DATO
...es decir que los datos se extraen en orden inverso a como fueron introducidos.
El último que llega es el primero que sale. En inglés: “Last Input, First Output”,
por lo que también se conocen como estructuras L.I.F.O.
• En una cola los datos se añaden por un extremo y se extraen desde el otro.
· ···· ·· ····
Añadir DATO
····· ···· ······
DATO
DATO
DATO
DATO
DATO
· ···· ·· ····
Extraer DATO
····· ···· ······
...los datos se extraen en el mismo orden en que fueron introducidos.
Aquí el primero que llega es el primero que sale. En inglés: “First Input, First
Output”, por lo que también se conocen como estructuras F.I.F.O.
Vamos a implementar estas estructuras apoyándonos en la clase Vector, heredando de
él, para lo que aprovecharemos algunos métodos de la clase Vector como:
public void add(Object obj) ... // añade un objeto a final del vector.
public boolean isEmpty()...
// cierto se el vector no contiene ningún objeto
public Object remove(int index) ... // elimina el elemento de la posicion index,
// los elementos posteriores decrementan
// por lo tanto sus posiciones una unidad.
public int size() ...
// número de elementos contenidos en el vector.
Una posible implementación sería así de sencilla:
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 254
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
public class Pila extends Vector {
public void apilar(Object obj) {
add(obj);
}
public Object desapilar() {
return remove(size()-1);
}
public boolean vacia() {
return isEmpty();
}
}
public class Cola extends Vector {
public void acolar(Object obj) {
add(obj);
}
public Object desacolar() {
return remove(0);
}
public boolean vacia() {
return isEmpty();
}
}
De este modo nuestras pilas y colas al heredar de Vector podrá ser también utilizadas
como si de objectos Vector se tratase.
Si no deseamos que los objetos de Pila y Cola puedan tratarse como vectores, podemos
optar por definirles un atributo Vector en lugar de heredar de él.
public class Pila {
Vector datos = new Vector();
public void apilar(Object obj) {
datos.add(obj);
}
public Object desapilar() {
return datos.remove(size()-1);
}
public boolean vacia() {
return datos.isEmpty();
}
}
public class Cola {
Vector datos = new Vector();
public void acolar(Object obj) {
datos.add(obj);
}
public Object desacolar() {
return datos.remove(0);
}
public boolean vacia() {
return datos.isEmpty();
}
}
nota.- La clase Pila está ya implementada en el paquete java.util con el nombre Stack.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 255
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Capitulo 16
Entrada/Salida
de Datos
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 256
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Dispositivos de almacenamiento masivo.
Se llama de esta manera a los dispositivos capaces de almacenar información de manera
duradera (sin necesidad de electricidad ni otras fuentes de alimentación energética) en
los cuáles un ordenador puede leer y/o escribir para intercambiar información.
Dispositivos como, discos, cintas, tarjetas electrónicas, tarjetas perforadas, CD-ROMs,
CDs regrabables, memorias flash, minidiscs, etc..., que almacenan la información
principalmente usando materiales magnéticos y ópticos.
En general son características deseables el que sean:
- económicos: bajo coste de fabricación.
- duraderos:
puesto que todos los soportes (hasta la piedra) termina
antes o después, por perder lo que sobre ellos se graba.
- seguros:
que resistan inmutables ante posibles malos tratos o
accidentes.
- fiables:
que aseguren la correcta lectura de la información que en
ellos se ha almacenado.
- pequeños:
para facilitar su transporte y almacenamiento.
- rápidos:
realizando las operaciones de lectura y escritura.
Pero cada uno de estos dispositivos funciona de manera muy diferente, como diferentes
son físicamente entre sí. Sin embargo, los programas rara vez necesitan manipularlos
directamente.
Los sistema operativo de cada ordenador se encargan normalmente de hacer de
intermediarios entre las aplicaciones y estos dispositivos y suelen organizan la
información que se almacena en ellos agrupándola en entidades llamadas ficheros
(clasificándola a su vez jerarquicamente en directorios, o carpetas).
Aún así las acciones que se pueden realizar en un fichero dependerán de las
características del dispositivo sobre el que se encuentra fisicamente almacenado.
nota.- En ocasiones para hablar de un dispositivo nos referiremos indistintamente al aparato o al material
que éste lee o escribe, según resulte más conveniente puesto que una cosa y la otra están
íntimamente relacionadas.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 257
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Estas características, nos permitirían clasificar los dispositivos según:
• Su modificabilidad los habrá.o que sólo permitirán ser grabados una sola vez ( CD-rom, papel impreso,
microfichas, tarjetas perforadas, etc... ).
o que permitan que se les escriba, reescriba y modifique tantas veces como
sea preciso (discos duros, cintas, monitores de cristal líquido o de rayos
catódicos, tarjetas de banda magnética, tarjetas inteligentes, etc...).
• Su capacidad de ser leídos por la máquina:
o existen dispositivos sólo de salida, como impresoras, plotters, altavoces,
pantallas, telares, etc..., que producen un material cómodo de ser leído
por el ojo humano.
o dispositivos de entrada como teclados, reconocedores de voz, sensores
de temperatura, sensores de presión, lector para códigos de barras,
camaras de vídeo, etc...
o y dispositivos de entrada/salida, pensados para actuar en ambos sentidos.
modems, magnetoscopios (o casettes), disquetes y cintas magnéticas,
CD-Regrabables.
• El modo en que sus datos pueden ser accedidos:
o dispositivos secuenciales: son aquellos en los que para acceder a la
posición del dispositivo en la que se encuentra el dato deseado, es
preciso acceder a la secuencia de datos almacenados en posiciones
anteriores, hasta llegar a la que se desea (cintas, lotes de tarjetas
perforadas, etc...). Son económicos e ideales por lo tanto para el
almacenamiento de cantidades muy grandes de datos.
o dispositivos de acceso directo: aquellos capaces de permitir el acceso
directo e inmediato a la posición en la que se almacenan los datos
que se precisen sin necesidad de acceder al resto de datos. (discos,
CDs, memorias flash, tarjetas inteligentes, etc...). Las operaciones
de acceso a sus datos son muy rapidas y son idoneos para alojar
datos que deban ser modificados.
nota.- No confundir la capacidad de los dispositivos de permitir un tipo de
acceso, con el tipo de acceso real que se realiza. Los CDs de audio por
ejemplo, permiten el acceso directo a la canción que se desea escuchar,
pero también permiten el acceso secuencial (para reproducir en orden
todas las canciones del disco).
Los sistemas operativos asocian a algunos disposivos un nombre con el que poder
identificarlos, tratandolos en ocasiones como si de un fichero más se tratase (La
impresora a manudo se maneja como un fichero en el cuál sólo podemos escribir
caracteres, al igual que los dispositivos de salida como la pantalla, etc...).
Otros dispositivos necesitan ser manejados de una manera más específica, mediante
instrucciones característica propias de ellos por lo que necesitaríamos dedicarles un
tratamiento especial a cada uno de ellos.
Nosotros nos centraremos a continuación en el modo de operar con ficheros, separando
la cuestión del dispositivo físico que los almacena (excepto cuando ello afecte al modo
de operar con el fichero).
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 258
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Operando con ficheros
Un fichero consiste en una secuencia de datos (generalmente del mismo tipo) cuyo
tamaño es teóricamente ilimitado y almacenado en un soporte perdurable.
Cualquiera que sea el tipo de dispositivo o el lenguajes de programación que se esté
utilizando, para acceder al contenido de un fichero (tanto para leer como para escribir)
es preciso contar con dos operaciones fundamentales:
• Abrir el fichero: Su ejecución es necesaria antes de proceder a acceder al contenido
del fichero, pues advierte al sistema para que prepare las zonas de memoria a
través de las cuales se intercambia información con el fichero y realice las
comprobaciones precisas sobre si el fichero existe, si está disponible, si se
poseen los permisos necesarios para manejarlo, etc...
En este momento, además de identificar el fichero con el que se va a operar se
suelen especificar detalles relativos al modo de acceso con el que se pretende
usar (si sólo se va a consultar o también se va a modificar, si contiene
carácteres o bytes binarios, etc..)
• Cerrar el fichero: Su ejecución debería realizarse en el mismo momento en que la
aplicación deje de operar con él, para que el sistema libere lo antes posible los
recursos con los que nos facilita acceder a su contenido.
Normalmente, mientras que un fichero no se cierra, no existe certeza de que las
modificaciones que se han realizado sobre él, se hayan efectuado realmente. A
menudo estas modificaciones se aplazan con la finalidad de realizar varias a un
mismo tiempo y minimizar así el número de operaciones necesarias.
nota.- Las operaciones con dispositivos mecánicos (cosas como hacer que un disco empiece a
girar) son de las que más tiempo necesitan, en comparación con la gran veloccidad con la
que se realizan las operaciones electrónicas.
La operación de apertura de un fichero en java se realiza indirectamente asociada al
momento de la creación de los objetos mediante los cuáles se realiza la lectura/escritura
de ficheros.
La operación de cierre sí que está presente mediante un método llamado close().
En algunos lenguajes es típico asociar también el cierre de ficheros al método destructor
de los objeto (el método finalize() correspondiente). En java esto no es buena idea ya
que no se controla el momento exacto en que el recolector de basura eliminará el objeto
y ejecutará consecuentemente su método finalize().
En el momento en que se abre un fichero, se especifica el modo en que se va a acceder a
su contenido, básicamente tres posibles estrategias de acceso :
- Secuencial.
Todos, excepto el acceso secuencial, requieren que el dispositivo - Directo.
- Indexado.
físico permita el acceso directo y que los datos en el fichero se
encuentren organizados según alguna disposición especial, que a continuación
pasaremos a analizar.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 259
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Tengase en cuenta, no obstante, que los dato almacenados en un fichero sólo son
secuencias de ceros y unos, que no suelen disponen de ninguna explicación respecto a
cómo deben ser leídos.
El programador de una aplicación deberá interpretar correctamente la información
contenida en cada secuencia de bytes leída, ajustandose a las especificaciones de que
disponga respecto al formato del fichero.
nota.- Aunque no es definitiva, la extensión (.java, .exe, .xxx, ...) de un fichero puede dar una idea de su
contenido.
Además algunos ficheros, contienen en su comienzo alguna secuencia que permite confirmar el tipo
de dato que contienen, así como indicaciones de como debe interpretarse. Hay formatos estándar que
especifican cómo es el contenido de ficheros de imagen (.bmp, .gif, .jpg, .avi, ...), de sonido (.wav,
.mp3, .midi, ...), de texto (.txt, .rtf, .html), etc...
Si por ejemplo dos datos de tipo short se leen del fichero como un sólo dato de tipo int,
no se producirá ninguna excepción que nos permita detectar el error, sino que la
secuencia de bytes se interpretará como un dato entero... dando lugar probablemente a
un error en el resultado esperado (error de lógica).
27 de tipo short
-19 de tipo short
... 0001 1011 1001 0010 ...
7058 de tipo int
Acceso Secuencial
Este tipo de acceso funciona sobre cualquier tipo de dispositivo (directo o secuencial).
Una vez que el fichero se abre (y hasta el momento en que se solicite su cierre), están
disponibles dos operaciones básicas de acceso: la de lectura y la de escritura.
• Al leer, con cada cada operación de lectura simplemente se obtiene el siguiente dato
que haya en el fichero (a no ser que ya no quede ningún dato por leer).
Cuando un fichero se abre para leer secuencialmente, éste debe existir y
quedará preparado para ser leído desde el principio.
• Al escribir con cada escritura se añade un nuevo dato al fichero a continuación del
último escrito. Al abrir un fichero para escritura secuencial se pueden dar dos
casos:
que el fichero quede vacío y preparado para que se escriba en
él desde su primer dato (si el fichero no existía se creará
nuevo y si existe se eliminará su contenido).
que se respete el contenido del fichero y se deje preparado para
escribir datos a continuación del último dato (si el fichero no
existía se creará nuevo y si existe se respeta su contenido).
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 260
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
En java existen una gran variedad de
clases con las que leer o escribir usando
acceso secuencial que iremos viendo en
los próximos apartados.
Por ahora empezaremos por ver un
ejemplo usando las clases:
- FileOutputStream
- FileInputStream
Secuencia
de datos
dato1 dato2 dato3 dato4
con las que implementaremos los métodos:
... delTecladoAlFichero(...) que lee una serie de números enteros de la entrada
estándar para escribirlos secuencialmente en un fichero, en el mismo orden en que
los va recibiendo.
y ... delFicheroAlMonitor(...) que lee secuencialmente los datos de un fichero y los va
mostrando en la salida estándar del sistema.
Los constructores de las clases FileInputStream y FileOutputStream abren los ficheros
(cuyo nombre se les indica en el parámetro) preparándolos para leer y para escribir,
respectivamente.
Escribiendo en fichero
...... ... ..
static void delTecladoAlFichero (String nombre, boolean añade) throws IOException {
int numero;
FileOutputStream fichSecuencial= new FileOutputStream(nombre,añade);
Salida.println("Introduce números enteros entre 0 y 255. (999 para acabar)");
numero=Entrada.readInt();
// Lectura anticipada para no
while(numero!=999) {
// escribir la marca final 999
fichSecuencial.write(numero);
// Aunque numero es un entero,
numero=Entrada.readInt();
// write() sólo escribe bytes 0..255
}
fichSecuencial.close();
} // ** fin_de_delTecladoAlFichero(...) **
.... .. ....................
Cuando el constructor del objeto FileOutputStream abre el fichero, según
el segundo parámetro del
constructor sea ... ... false: éste queda vació y preparado para que se le
escriban sus primeros datos.
o
o
si el fichero no existía se crea nuevo.
si ya existe, se borra todo su contenido.
... true: queda preparado para que se le añadan nuevos
datos a continuación del último que contenga.
o
o
si el fichero no existía se crea nuevo.
si ya existía, su contenido permanece.
nota.- Si la operación de apertura del fichero encuentra algún problema para llevarse a cabo, se lanzará
una excepción de clase IOException (o derivada de ella) que al ser marcada será preciso atrapar o,
como en este ejemplo, que el encabezamiento del método la notifique.
Una vez abierto el fichero, cada instrucción write(...) escribe cada vez un nuevo dato.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 261
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Sin embargo estos datos no iran directamente al dispositivo físico sino a una zona de
memoria intermedia (buffer o tampón de memoria) donde permanecerán temporalmente
hasta que sean suficientes datos como para que vaga la pena escribirlos al dispositivo de
almacenamiento.
Esto mejorar el rendimiento de los dispositivos cuyas operaciones resultan
generalmente mucho más lentas que las operaciones electrónicas de la memoria.
nota.- Cualquier dispositivo compuesto de partes mecánicas (el motor que hace girar el disco, desplaza el
cabezal de una impresora, etc...) es muy lento en comparación con dispositivos electrónicos.
Sólo cuando el fichero se cierra, los datos que aún pudieran estar en el buffer de
memoria, quedarán con seguridad escritos en el dispositivo de destino.
A partir del momento en que sea cerrado, no se podrá operar con él. A no ser que vuelva
a abrirse, para lo cual sería necesario instanciar un nuevo objeto FileOutputStream.
Leyendo de fichero
···· · ····
·· ···
static void delFicheroAlMonitor (String nombre) throws IOException {
int numero;
FileInputStream fichSecuencial= new FileInputStream(nombre);
Salida.println("Estos son los números almacenados en el fichero "+nombre);
numero=fichSecuencial.read();
while(numero!=-1) { // read() devuelve -1 cuando el fichero se acaba.
System.out.println(numero);
numero=fichSecuencial.read();
}
fichSecuencial.close();
} // ** fin_delFicheroAlMonitor **
······ ··· · · ······· ···· · ·······
Igual que antes, tanto al abrir el fichero como al intentar leer de él, puede producirse una
IOException y como no se atrapa es necesario advertirlo en la cabecera del método.
Al crear el objeto FileInputStream, el fichero queda abierto y preparado para leer su
primer dato.
Cuando se lee de un fichero la cuestión no es consultar si existe el siguiente dato. Sólo
tras realizar la lectura sabremos si quedaban o no datos por leer. Cuando se alcanza el
final del fichero, read() devuelve -1 indicando que no quedan más datos en el fichero.
nota.- Existe la posibilidad de que el fichero esté totalmente vacío (sin un sólo dato) por lo que los
programas deben contemplar la posibilidad de que la primera operación de lectura indique que no
quedan datos.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 262
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Para probar estos métodos puedes usar un programa como éste:
public class Secuencial {
static final boolean AÑADIENDO =true;
public static void main(String args[]) {
try {
// Primeramente el fichero se crea nuevo. Si existe se subtituye
delTecladoAlFichero("secuencia_enteros.dat ", !AÑADIENDO);
// Despues se añade una 2ª tanda de datos al fichero ya existente
delTecladoAlFichero("secuencia_enteros.dat", AÑADIENDO );
// Y finalmente se muestra lo que se escribió en él
delFicheroAlMonitor("secuencia_enteros.dat");
} catch(IOException e) {System.out.println("Error de entrada/salida");}
} // ** fin_main **
// metodos delTecladoAlFichero() y delFicheroAlMonitor(...)
........... .. ... ...
} // ** fin **
• La primera secuencia de números enteros se sobre-escribe (en caso de que ya exista)
el fichero que se indica... “secuencia_enteros.dat” , puesto que !AÑADIENDO se evalúa
como false ( ! true → false).
• La segunda tanda de números se añaden en el fichero, a continuación de los
anteriores.
• Finalmente toda la secuencia de números del fichero se muestra por la salida estándar.
Acceso Directo
Sólo es posible realizar este tipo de acceso con ficheros almacenados sobre dispositivos
que permitan el acceso directo a sus datos.
• Para leer como para escribir) se pasa directamente a la posición elegida para el dato
y allí se lee (o escribe).
• Junto al dato que se va a leer (o escribir) se indica la posición, o bien (como es el
caso de java) mediante una operación previa se posiciona el fichero en el punto
en que se desea realizar la lectura (o escritura).
Es posible encontrarse básicamente con dos casos:
- Que los datos del fichero estén organizados secuencialmente uno tras otro sin una
disposición especial y simplemente se pretenda leer el dato de una determinada
posición sin para ello tener que leer los anteriores.
- Que los datos estén dispuestos siguiendo algún tipo de colocación intencionada, que
permita que se localice rápidamente la posición de cada uno.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 263
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Si este último es el caso, se dice que
los datos del fichero tienen
organización directa.
Datos
con ordenación para
acceso directo
No se debe olvidar que un fichero se
corresponde con un bloque de espacio
físico sobre un dispositivo y si cada
dato tiene su propio sitio, en el fichero
aparecerán posiciones vacías (libres)
que deben ser marcados de algún
modo para poderlas distinguir de las
posiciones que sí estén ocupadas.
Este sistema consigue un acceso inmediato al dato deseado, aunque para ello tenga que
desperdiciar el espacio que queda entre las posiciones ocupadas.
Será necesario reservar anticipadamente el espacio necesario para la cantidad estimada
de datos que se guardarán.
La posición asignada a cada dato, suele venir dada por alguna parte del propio dato, o
bien se obtiene transformando el dato (usando técnicas llamadas de hashing o
aleatorización) en una posición válida dentro del espacio inicialmente reservado en el
fichero.
De está forma, después se podrá localizar rápidamente la posición que corresponda con
el dato buscado.
Son algo complicados de gestionar, pero son muy útiles para ficheros que se modifican
poco o nada y especialmente adecuados para consultas rápidas.
nota.- Si se desea profundizar en este terreno, se recomienda estudiar la clase Hashtable que implementa
esta misma estrategia de acceso, para almacenar datos en la memoria central en lugar de hacerlo en
dispositivos externos.
En java los accesos directos se realizan a través de la clase RandomAccessFile, cuyo
constructor permite dos modos de apertura del fichero asociado:
- Abrir sólo para leer de él.
...new RandomAccessFile(nombreDelFichero, “r” )
- Abrir para leer y escribir datos.
...new RandomAccessFile(nombreDelFichero, “rw” )
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 264
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Esta clase de objetos mantiene un único indicador con el que controla el punto del
fichero en el que se realizará la siguiente lectura o escritura. De modo similar a como
ocurre en los accesos secuenciales este punto avanza automáticamente con cada
operación de lectura o escritura, sin embargo los objetos RandomAccessFile disponen
de métodos con los que variar la posición de este indicador para elegir la posición de la
que a continuación se leerá o escribirá.
Para la operación de escritura:
- Si dicho indicador apunta al final del fichero los datos se añaden a
continuación del último dato, aumentando el tamaño del fichero.
- Y si apunta a una posición en la que ya hay datos, la acción de escribir se
produce sobre ellos, por lo que cualquier dato que hubiera en las posiciones
ocupadas por los nuevos datos, se pierde. Es decir se modifica el contenido
de las posiciones en que se escribe.
Suponiendo que el fichero del anterior ejemplo (acceso secuencial) está almacenado en
un dispositivo que permita acceso directo (un disco por ejemplo), el siguiente programa
accede al último byte que en él se haya escrito.
........... . .....
public static void main(String args[]) {
String nombreFich="secuencia_enteros.dat";
try {
// abre el fichero sólo para leer de él..."r"
RandomAccessFile accesoDirecto= new RandomAccessFile(nombreFich,"r");
long tamaño=accesoDirecto.length();
if(tamaño!=0) {
accesoDirecto.seek(tamaño-1); // posiciona para leer el ultimo byte
Salida.println("Último numero del fichero " +
nombreFich + " es "+accesoDirecto.read());
}
else
Salida.println("el fichero "+nombreFich+" está vacío.");
} catch(IOException e) {System.out.println("Error acceso directo a fichero.");}
}
....... .... ..
Puesto que no se va a modificar se abre en modo de sólo lectura, se consulta su tamaño
(en bytes) con el método length() y mediante el método seek(...) se reposiciona el
indicador (que inicialmente estará al principio del fichero) para que se coloque en
disposición de leer el último dato.
nota.- El primer dato está en la posición cero, por lo que si el tamaño del fichero es tamaño, la posición
del último dato debe ser tamaño-1.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 265
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Esta clase implementa las interfaces DataInput y DataOutput, por lo que dispone de
diferentes métodos con los que se pueden leer y escribir datos de los tipos primitivos y
de la clase String.
interface DataInput {
boolean readBoolean();
char readChar()
double readDouble()
int readInt()
String readLine()
.. .. ....
}
interface DataOutput {
void writeBoolean(boolean dato);
void writeChar(char dato);
void writeDouble(double dato);
void writeInt(int dato);
void writeLine(String dato);
.. .. ....
}
Otros métodos destacables, son:
- long getFilePointer() que permite consultar la posición del indicador que determina la
posición de la próxima lectura o escritura.
Si retorna:
o
o
o
o
cero... el próximo dato leído/escrito será el primero (el de la posición cero),
pos * <bytes_del_dato>... el próximo dato leído/escrito será el de la posición pos,
< accesoDirecto>.length() - 1... el próximo dato (byte) leído/escrito será el último.
< accesoDirecto >.length() - <bytes_del_dato>... el próximo dato leído/escrito será el
último de fichero.
- void setLength(<nueva_longitud>) permite modificar el tamaño del fichero.
nota.- Util por ejemplo para reservar anticipadamente el espacio de un fichero con organización directa.
o Si el nuevo tamaño es menor que el que el fichero tenía, los datos del final desaparecen.
Si el puntero de lectura/escritura estaba más allá del nuevo fin del fichero, ahora ubicará su
posición al final del fichero.
o Si el nuevo tamaño es mayor que el que el fichero tenía, éste queda ampliado con un
espacio extra cuyo contenido será indeterminado.
- int skipBytes(<desplazamiento>) hace avanzar el puntero de lectura/escritura el
número de bytes que se le indica.
El desplazamiento sólo puede ser positivo.
Es posible que no se pueda avanzar tanto como se solicita (si alcanza el fin de fichero),
por eso el método retorna el número de bytes que realmente ha conseguido desplazado
el apuntador.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 266
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Acceso Indexado
Como el anterior, sólo se puede utilizar para ficheros almacenados sobre dispositivos
directos, pero en este caso resultará indispensable que su contenido tenga una
organización indexada.
Deben existir una serie de índices que hagan posible acceder convenientemente a los
datos almacenados. Estos índices nos señalan la zona del fichero en la que debemos
buscar cada dato del fichero, de manera similar a como buscaríamos en un libro, un
determinado capítulo usado su índice.
Nombre
Edad
Datos con ordenación para
acceso indexado
Apellidos
Altura
Apuesta
Jugada
Compañero
D.N.I.
Campo clave (único)
TABLA DE INDICES
Datos hasta el tal clave
bloque 1
desde tal clave a tal otra
.. .....
.. .... ........
.... ..
bloque 2
de tal clave hasta el final
bloque final
Los datos que se almacenan suelen ser datos compuestos de datos más elementales a los
que se suele llamar campos (que en java serían los atributos de cada objeto).
De entre todos los campos (atributos) al menos uno debe contener una información que
sea única entre todos los datos que se escriben, de modo que pueda servir para
identificar unívocamente al dato (objeto) al que pertenece.
La tabla de índices permite acceder de manera rápida a los datos, como el índice de un
libro nos permitiría saltar a la página donde comienza un determinado capítulo.
Un vez localizada la posición del dato, este puede ser fácilmente leído, escrito,
modificado, o eliminado del fichero.
En aquellas operaciones que modifiquen el contenido del fichero, será necesario
corregir además tabla de índices para que se mantenga en todo momento actualizada.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 267
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Algunos lenguajes de programación incluyen instrucciones que automatizan las
operaciones de acceso indexado, ofreciendo instrucciones similares a estas:
- escribir DATO usando clave (para añadir nuevo dato)
- modificar DATO usando clave (o bien, reescribir ...)
- leer usando clave
- borrar usando clave
(o bien, eliminar ...)
nota.- Obsérvese que es necesario distinguir cuando se escribe un nuevo dato (su clave no debe coincidir
con la de ningún otro dato del fichero) y cuando se modifica el contenido de un dato ya existente
en el fichero (su clave debe existir en ya en el fichero). Si no es así, se producirá un error.
De igual forma la operaciones de intentar leer o borrar un dato cuya clave no exista fallarían.
Esta estrategia combina la rapidez de los accesos directos, con una mayor eficacia en las
modificaciones de los datos que se almacenan.
Son muy prácticos para aquellos ficheros en los que se producen frecuentemente altas,
bajas y modificaciones de datos.
En java no se incluyen este tipo de sentencias debido a que hoy en día no resultan muy
eficaces en comparación con la posibilidad de almacenar los datos usando una base de
datos.
Las bases de datos son algo así como estructuras para el almacenamiento de datos que
permiten establecer entre ellos relaciones complejas y de cuya gestión se encarga un
programa especializado llamado gestor de bases de datos.
Este programa hace de intermediario cada vez que una aplicación necesita acceder a los
contenidos de la base de datos procurando para sus datos fiabilidad, rapidez, consultas
complejas y otras características muy interesantes.
Más adelante se comentará, el modo en que una aplicación java puede interactuar con
un programa gestor de una base de datos.
Unicode
Java codifica sus dato de tipo caracter con el estándar unicode UCS-2 que recibe su
nombre del inglés Universal Character Set. de 2 bytes (16 bits) lo cuál le permite
codificar los caracteres de la gran mayoría de las lenguas del mundo, signos
matemáticos, musicales, etc... (216 = 65536 caracteres).
Como se trata de un estándar desarrollado por países occidentales, como era de esperar
primero contempla el estándar ASCII (American Standard Code for Information Interchange) de
manera que los 7 bits de menor peso siguen este convenio.
El siguiente código unicode, representa al caracter ‘A’ y como puede observarse utiliza
el mismo valor numérico (el 65) que para su representación ascii, pero utilizando más
bits.
ASCII
... 00000000 0 1000001 ...
Unicode (UCS-2)
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 268
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
El tamaño de los caracteres unicode, en ocasiones (cuando se trabaja usando un único
alfabeto) desperdicia demasiado espacio.
El problema es más notable cuando dichos caracteres deben ser utilizados por otros
programas. Si trabajasemos con ficheros de caracteres codificados directamente en
unicode, muchos programas actualmente no interpretarían correctamente los caracteres
de dos bytes y esto entorpecería el intercambio de ficheros de texto.
Formato de Transformación Unicode (UTF)
Por eso a la hora de trabajar con caracteres almacenados (o que pertenezcan a alguna
entidad distinta del propio programa) se opta por aplicar un mecanismo de
transformación hacia/desde alguna otra codificación más local.
La más extendida es la codificación UTF-8 (Unicode Transformation Format), que
realiza la siguiente transformación:
- Mantiene intacto el estándar ascii.
Los 128 caracteres del estandar
americano, se codifican en un sólo
byte, que siempre comienza por 0.
Alfabeto americano
00000000 0 XXXXXXX
ASCII
0 XXXXXXX
desde 00000000 hasta 01111111 (0 ... 127)
- Los siguientes 1920 caracteres
unicode (correspondientes a los
alfabetos más importantes) se
codifican usando dos bytes,
precedidos respectivamente de 110
y de 10, lo cuál deja.
Otros alfabetos
00000XXX
XXXXXXXX
110 XXXXX 10 XXXXXX
desde 1100001010000000 hasta 1101111110111111 (128 ... 2047)
Del modo similar se transforman el resto de alfabetos (los menos utilizados) usando 3 ó
4 bytes (o incluso más).
nota.- El estándar unicode es un tema muy extenso y complejo. Si quieres más información podrás
encontrarla en la página del consorcio encargado de su creación, www.unicode.org
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 269
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Flujos de Entrada/Salida
La inmensa mayoría de las fuentes/destinos de datos, admiten un funcionamiento
secuencial (son capaces de comportarse secuencialmente), lo que quiere decir que
aceptarían las instrucciones de lectura/escritura básicas del acceso secuencial.
Las gran mayoría de las clases que se ocupan de la entrada/salida de datos en java
funcionan definiendo flujos de datos, canales de los que podemos leer o escribir datos
sin necesidad de preocuparnos de qué habrá al otro extremo del flujo.
Esto permite implementar métodos tremendamente flexibles y versátiles, puesto que
podrán implementarse con independencia de la entidad con la que intercambian
información.
• Cuando se lee (de alguna entrada) se entiende que se recogen datos de algún
tipo, desde una entidad que los facilita.
• Si se escribe algo (por algún flujo de salida) se entiende que se envían datos
de algún tipo, hacia alguna entidad.
Así pues, las acciones de leer y escribir en java tienen un significado polimórfico más
genérico que les permite interactuar no sólo con ficheros sino con cualquier fuente (o
destino) de datos de la que recibir (o a la que enviar) datos como circulando en fila india
a través de un flujo.
Las fuentes o destinos de estos datos pueden ser de muy diversos tipos. Podrán ser
entidades tan variopintas como ficheros almacenados en discos, arrays de memoria,
ficheros almacenados en cintas, bases de datos, otros objetos, otros programa,
pantallas, teclados, servidores remotos, etc...
Desde esta perspectiva, las operaciones básicas de los accesos secuenciales realizan las
siguientes acciones:
Abrir un flujo: preparar un
canal de comunicación con la
entidad deseada.
Leer de un flujo: recoger un
dato de un canal de entrada.
Fichero
Programa
en
ejecución
Escribir en un flujo: enviar un
dato a través de un canal de
salida.
Pantalla
Cerrar un flujo: cortar la
conexión con una entidad. (si
es un canal de salida se
confirma que todo lo enviado
ha llegado hasta su correcto
destino).
Autor : FernandoToboso Lara
Impresora
Otros...
e-mail: [email protected]
Pag. 270
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Java dispone de una extensa jerarquia de clases con las que podemos elegir diferentes
maneras trabajar con un flujo.
La jerarquía de clases de lectura y de escritura de flujos se clasifica en función de los
siguientes criterios:
1. Según los datos leídos/escritos sean caracteres, o no:
a. Clases para bytes de datos genéricos (8 bits), que deben almacenarse como copias
exactas, puesto que no sabemos a que estructura de datos pertenecen.
b. Clases para caracteres, asociados a alguno de los sistema codificación existentes
para representar cada alfabeto (latino, ruso, chino, etc...) y con conceptos propios de
los textos como las líneas.
Frecuentemente, los caracteres leídos/escritos de una entidad no utilizan el mismo
estándar unicode de java y deben ser transformados para ser correctamente
interpretados. De otro modo se podría no identificar correctamente los caracteres
que lee.
Supongamos que el siguiente programa se ejecuta en un sistema que utiliza el
alfabeto ruso. La condición del bucle no será capaz de reconocer la letra ‘Й’ cuando
la lea, por que System.in es un flujo de bytes y el código del caracter en cuestión
ocupa dos bytes.
public class HastaUnaLetra{
public static void main(String args[]) throws IOException {
char[] texto=new char[200];
char ch;
int i=0;
while((ch=(char)System.in.read())!='Й')
texto[i++]=ch;
System.out.println(texto);
}
}
Para convertir la letra Й desde su propio estándar, al estándar unicode, el programa
necesitará usar un flujo de caracteres:
public class HastaLaEñe{
public static void main(String args[]) throws IOException {
InputStreamReader entradaEstandar = new InputStreamReader(System.in);
char[] texto=new char[200];
char ch;
int i=0;
while((ch=(char)entradaEstandar.read())!='Й')
texto[i++]=ch;
System.out.println(texto);
}
}
nota.- Se presupone que la página de códigos (el alfabeto) de la máquina java que lo ejecuta es por el
ruso por defecto. Si no es así, deberá al menos disponer de dicha página para podersela asociar
a nuestro flujo... new InputStreamReader(System.in, <CpRuso>) que corresponde a la página
de códigos con los caracteres del alfabeto Ruso.
nota.- Puedes ver en el apartado dedicado al estándar unicode, más información sobre cómo se
codifican los caracteres en este estándar.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 271
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
2. Dependiendo de si se aplica algún tipo de tratamiento intermedio a los datos
leídos/escritos por otro flujo, o si entra directamente en contacto con la entidad
origen/destino de los datos.
a. Clases de profundidad (o elementales), se limitan a implementar la entrada/salida
de datos, tal como son.
b. Clases de procesamiento, aplican acciones que mejoran o simplifican el uso de
los datos leídos/escritos de flujos que han sido creados a partir de otros flujos. Es
fácil distinguirlos, sus contructores reciben la referencia al flujo sobre el que van a
trabajar.
El siguiente esquema, ofrece una visión de conjunto de las clases más importantes del
paquete java.io agrupadas según estos criterios.
Clases Base
(abstractas)
Salida
E ntrada
Bytes
DataInputStream
BufferedInputStream
PushbackInputStream
DataOutputStream
BufferedOutputStream
PrintStream
ByteArrayInputStream
PipedInputStream
FileInputStream
FilterInputStream
ObjectInputStream
SequenceInputStream
AudioInputStream
InputStream
ByteArrayOutputStream
PipedOutputStream
FileOutputStream
FilterOutputStream
ObjectOutputStream
OutputStream
Reader
Writer
HERENCIA
CLASES
CLASE
HIJAS
BASE
Caracteres
CharArrayReader
PipedReader
StringReader
InputStreamReader
BufferedReader
FilterReader
CharArrayWriter
PipedWriter
StringWriter
OutputStreamWriter
FilterWriter
BufferedWriter
PrintWriter
FileReader
LineNumberReader
PushbackReader
FileWriter
CLASES de Lectura y Escritura ELEMENTALES
LAS DEMAS CLASES de Lectura y Escritura con PROCESAMIENTO incluido
Cada clase permite instanciar objetos especializados en algún tipo de entrada salida de
datos, a la vez que sirve de base para la creación por herencia de nuevas clases más
especificas.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 272
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Cuando un programa comienza a ejecutarse, el sistema operativo suele asociarle por
defecto dos flujos de salida y un flujos de entrada. Para utilizar estos flujos desde java
hay que usar los siguiente atributos estáticos de la clase System:
-
System.out: La salida estándar (comúnmente asociado a pantalla), pertenece a
la clase PrintStream y su utilidad se ha podido comprobar en casi todos los
ejemplos propuestos, al usar sus métodos print(...) y println(...) para mostrar
mensajes.
-
System.in: La entrada estándar (frecuentemente asociado al teclado), es de
clase InputStream y hasta el momento no se ha utilizado directamente, pero sí
indirectamente, ya que los métodos de la clase Entrada lo utilizan (tal como
veremos después) para leer.
Aunque habitualmente lee del teclado podría tratarse de cualquier otra entidad de
tipo genérico, por lo que necesariamente su clase es de las que se especializan en
leer bytes, no caracteres.
-
System.err: Es de clase PrintStream como la salida estándar por lo que se
comporta del mismo modo. Su finalidad es la de posibilitar que el programa
genere sus mensajes de error por una vía diferente de la estándar. Y, aunque por
defecto suele ir también asociado a pantalla, puede ser asignado (al ejecutarse la
aplicación) a una entidad diferente para separar los mensajes normales, de los
avisos que notifican posibles errores de la ejecución.
nota.- La redirección es una operación típica de los sistemas operativos que tienen relación
directa con estos flujos por defecto. Si quieres entender más a fondo las posibilidades de
flujo de errores, consulta sobre este tema.
La clase
Entrada
Hasta este momento, cada vez que en un ejemplo se han leído datos del teclado, se ha
hecho a través de los métodos estáticos de la clase Entrada.
Esta es una clase que no pertenece a la librería estándar de java, pero ha sido utilizada
sistemáticamente con el fin de simplificar al máximo la lectura y hacer más fácil de
aprender lo que ya tiene de por sí bastante complejidad.
A continuación veremos una simplificación de algunos fragmentos extraídos del fichero
Entrada.java, con los que tendremos un primer ejemplo de cómo usar las clases
InputStreamReader y BufferedReader al tiempo que nos hacemos idea de cómo actúa la
clase Entrada para leer de System.in (la entrada estándar).
public class Entrada {
static private InputStreamReader lectorCaracteres;
static private BufferedReader lectorBloques;
static { // Inicializador estatico
try {
lectorCaracteres = new InputStreamReader(System.in,"Cp850");
} catch(UnsupportedEncodingException e) {System.out.println("codigo invalido");}
lectorBloques = new BufferedReader(lectorCaracteres);
}
.... ........
...... . . . .....
nota.- “Cp850” es el nombre de la página de códigos estandar del alfabeto español. Es el que utiliza el
interprete de comandos de Ms-Dos.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 273
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
A menudo, para realizar una simple operación de lectura o escritura, se precisa de la
acción conjunta de más de un clase, de modo que cada clase se ocupa de una de las
fases en que se realiza la acción de leer o escribir.
Podemos verlo como si varios flujos se conectasen formando uno sólo de manera que
cada flujo recibe lo que sale de otro.
lectorBloques
Programa
BufferedReader
lectorCaracteres
InputStreamReader
System.in
InputStream
-
System.in : referencia un objeto de alguna clase indeterminada (generalmente
BufferedInputStream), derivada de InputStream (que es abstracta) y representa la
entrada de bytes asociada por defecto al programa.
-
lectorCaracteres : referencia un objeto InputStreamReader que se instancia (con
new) pasando a su constructor los parámetros:
… System.in (de clase InputStream) del cual recibirá bytes.
… el nombre del estándar que utiliza la entrada System.in , para codificar (en
bytes) los caracteres que esperamos que envíe el teclado.
La creación del objeto que referencia, se realiza en un inicializador estático (en
vez de realizarse al declarar el identificador) para hacer posible atrapar la
excepción UnsupportedEncodingException que el constructor de la clase
InputStreamReader puede lanzar en caso de que el sistema no disponga de la
codificación de caracteres elegida.
La clase InputStreamReader implementa métodos que leen de la entrada que se
le indica (System.in) interpretando los bytes que recibe como caracteres de algún
estándar de codificación.
Es frecuentemente utilizado para transformar los flujos de tipo InputStream (de
bytes) en flujos tipo Reader (de caracteres).
-
lectorBloques : referencia un objeto BufferedReader que se instancia (con new)
a partir del anterior objeto lectorCaracteres del cual recibirá caracteres.
La clase BufferedReader implementa métodos para manejar el flujo que se le
indica (en este caso lectorCaracteres) como un bloque de caracteres. Entre otros,
dispone de un método llamado readLine(...) con el que podemos leer líneas, es
decir, secuencias de caracteres que acaban con el caracter especial fin de línea.
nota.- End Of LiNe (EOLN), en java es Character.LINE_SEPARATOR (una constante estática de
la clase Character).
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 274
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Los métodos estáticos que se han venido utilizado para leer del teclado en los ejemplos
vistos, readInt(), readDouble(), readChar(), ...son muy similares a esto:
... ..... ..
public static char readChar() {
char resultado=' '; // Para leer el codigo de caracter Unicode pasado a char.
try {
codigo = (char)lectorCaracteres.read(); // Puede producir una IOException.
} catch(IOException e) {
System.out.print("Error leyendo caracter");
}
return resultado;
}
.... ........
• Este método se ocupa de la lectura de caracteres procedentes del teclado,
retornándolos como datos de tipo char.
La ejecución del método lectorCaracteres.read() retorna el valor int del código
unicode correspondiente al carácter introducido. Si no hubiese ningún carácter
preparado, el método esperará hasta que lo haya.
El código obtenido, se asigna a la variable resultado, usando un casting (o
moldeado de tipo).
nota.- El casting es necesario aquí, para que el compilador acepte el cambio intencionado de tipo.
Las instrucciones van en un bloque try...catch()... para poder atrapar la excepción
IOException que podría lanzar el método read().
Los métodos de la clase Entrada con los que hemos leído datos primitivos desde el
teclado funcionan de manera parecida.
El siguiente caso es una simplificación del método Entrada.readInt(). En él se lee una
línea (con el objeto lectorBloques) que se intentará interpretar como un número entero y
retornado como dato int (-12, 324, etc...).
public static int readInt() {
String linea="";
int resultado=0;
try {
linea=lectorBloques.readLine(); // Puede generar una IOException.
resultado = Integer.parseInt(linea); // Puede generar NumberFormatException.
} catch(IOException e) {
System.out.print("Error leyendo entero");
}
return resultado;
}
nota.- Esta simplificación espera recibir un único número por cada línea, recibida.
Con el método readLine() del objeto lectorBloques obtenemos una línea de
caracteres en forma de String (con los dígitos que supuestamente forman el
número).
El String retornado se interpreta como un número entero usando el método
estático parseInt(...) de la clase Integer, que retorna el dato int correspondiente al
String que se le pasa.
nota.- Si el método parseInt(...) no tiene éxito lanza la excepción NumberFormatException que al no
atraparse llegaría a la función que haya llamado a este método. Si se produjese un error del tipo
IOException, se notifica con un mensaje y se retorna cero como numero leído por defecto.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 275
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
El resto de los métodos serían muy similares y las versiones completas se pueden
consultar al final.
A continuación analizaremos, algunas de las clases de entrada/salida del paquete java.io
que vimos en el esquema de antes.
InputStreamReader y OutputStreamWriter
Su finalidad es servir de puente para una entidad fuente/destino de datos con la que se
desee establecer un flujo para leer/escribir caracteres. Especialmente cuando dicha
fuente/destino de datos utiliza un estándar de codificación para sus caracteres distinto
del estándar utilizado por la máquina virtual java.
En los métodos de la clase Entrada hemos visto cómo se usa la clase
InputStreamReader. A continuación podemos ver el equivalente para la salida.
public class Salida {
static private OutputStreamWriter escritorCaracteres;
.. ..... ... ...
static { // Inicializador estático
try {
escritorCaracteres = new OutputStreamWriter(System.out,"Cp850");
} catch(UnsupportedEncodingException e) {
System.out.println(" Pagina de código no valida ");
}
}
. ... .. .... ...
public static void print(Object obj) {
String texto=String.valueOf(obj);
try {
escritorCaracteres.write(texto,0,texto.length());
escritorCaracteres.flush(); // asegura la escritura inmediata.
} catch(IOException e) {
System.out.print("Error escribiendo a traves de 'Salida'.");
}
}
.. .. . ... ....
}
nota.- Habitualmente el estandar coincidirá con el usado por defecto por la máquina virtual java, pero no
siempre tiene que ser así. En el Sistema Windows el estandar por defecto suele ser el del entorno
gráfico, pero el usado por el interprete de comandos (ventana MsDos) es otro distinto en función del
juego de caracteres que se le configure. (con los comandos keyb.. , mode ...). En nuestro ejemplo
suponemos que la consola MsDos usa el juego de caracteres Cp850 propios del español.
Un flujo InputStreamReader lee bytes (de un objeto InputStream) codificados en algún
estándar que se le haya especificado y los facilita convenientemente transformados al
estándar de caracteres Unicode.
De modo similar un OutputStreamWriter transforma caracteres del estándar Unicode en
bytes codificados según el estándar que se le haya especificado (o bien con el estándar
por defecto de la máquina virtual java, si no se le ha indicado ninguna) y los escribe en
el OutputStream a partir del que fué creado.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 276
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
BufferedReader y BufferedWriter
BufferedInputStream y BufferedOutputStream
Optimizan la tarea de leer/escribir del flujo, almacenando los datos temporalmente en
buffers (zonas de memoria intermedia) con el fin de reducir el número de veces que se
necesita acceder a la fuente/destino original de los datos.
nota.- Por ejemplo, al escribir en el disco de un ordenador se tarda prácticamente lo mismo en grabar un
byte que en grabar 200 bytes. Lo que más tiempo consume es empezar a grabar, por la acción física
de hacer girar físicamente el disco a la velocidad adecuada.
Generalmente los sistemas operativos suelen utilizar esta misma estrategia al hacer de
intermediarios en la lectura/escritura. Por eso en ocasiones esta clase no aportan nada
frente a las propias clases base (InputStream, Reader, OutputStream y Writer) que ya
incluyen métodos para manejar bloques de datos.
No obstante, la clase BufferedWriter implementa además el método newLine() que
escribe un carácter fin de línea, usando como tal el valor de una propiedad del sistema
llamada line.separator .
nota.- Para consultar o modificar el valor tanto de esta como de otras propiedades definidas en el sistema
en que una aplicación se ejecuta, se utilizan los métodos estáticos System.getProperty(...) y
System.setProperty(...).
Paralelamente BufferedReader implementa readLine() que lee una línea completa de
caracteres, acabando en el carácter de fin de línea ‘\r’ o ‘\n’ y la devuelve como String.
FileReader y FileWriter FileInputStream y
FileOutputStream
Se manejan como la clases de las que respectivamente heredan... InputStreamReader,
OutputStreamWriter, InputStream, OutputStream. Pero se construyen para canalizar
datos desde/hacia un fichero nativo del sistema en el que se ejecuta el programa, por lo
que el constructor debe recibir el nombre del fichero (incluyendo si es necesario el
pathname del mismo) o bien algún objeto que lo identifique, de clases como File o
FileDescriptor que más tarde se analizarán.
........... .. .....
try {
FileWriter ficheroSalida= new FileWriter("cantar.txt");
ficheroSalida.write("O bella flor de jazmín, ",0,24);
ficheroSalida.write("que hueles a calcetín\n",0,22);
ficheroSalida.write("Eres tan bella y hermosa, ",0,26);
ficheroSalida.write("como una vaca tuberculosa.\n",0,27);
ficheroSalida.close();
//
//
//
//
write escribe una parte
del String dado, desde
una posición inicial a
una final.
FileReader ficheroEntrada= new FileReader("cantar.txt");
int caracter;
while((caracter=ficheroEntrada.read())!=-1)
System.out.print((char)caracter);
ficheroEntrada.close();
} catch(IOException e) {
System.out.println("Error de E/S con fichero \"cantar.txt\" .");
}
System.out.println("\nEste bello poema ha quedado grabado en el fichero cantar.txt\n");
..... . ......
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 277
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
FileReader y FileWriter dan por supuesto que deben traducir los caracteres al estándar
por defecto, por lo que si se desea elegir otro estándar de codificación de caracteres será
necesario crear un InputStreamReader u OutputStreamWriter, pasando al constructor el
FileInputStream de origen (o FileOutputStream destino).
De este modo leemos caracteres
del InputStreamReader que éste
recoje del FileInputStream que
se le dió, traduciéndolos al
estandar asociado (o se escriben
al OutputStreamWriter para que
los envíe al FileOutputStream)
Aplicación
Java
ficheros
OutputStreamReader
FileOutputStream
InputStreamReader
FileInputStream
A pesar de que estas clases sobrecargan el método finalize(), que cierra
automáticamente el flujo, es muy recomendable llamar expresamente a su propio
método close() para asegurarse de que el flujo se cierra y controlar que se cierre lo antes
posible.
nota.- Como ya vimos, el modo de trabajar del recolector de basura puede hacer que el método finalize()
no se llegue a ejecutar.
StringReader y StringWriter
Son similares a cualquier clase de lectura/escritura de caracteres, sólo que el
origen/destino, son objetos String.
Útiles cuando se desea tratar con objetos String como si realizase una operación de
lectura/escritura genérica por el sistema de flujos.
import java.io.*;
public class EjemploStringReader {
public static void mostrarMayusculas(Reader origen) {
int caracter;
try {
while((caracter=origen.read())!=-1)
Salida.print(Character.toUpperCase((char)caracter));
} catch(IOException e) {
System.err.println("...problemas leyendo de "+origen);
}
Salida.println();
}
public static void main(String args[]) {
String frase="Los molinos de Don Quijote son fantásticos...";
StringReader flujoFrase= new StringReader(frase);
mostrarMayusculas(flujoFrase);
· ··· ····· ··
}
}
En el ejemplo se transforma un objeto String en un StringReader, para poder pasarlo a
la función mostrarMayusculas(...) como un tipo de Reader.
nota.- La clase StringReader hereda de Reader y por polimórfismo la función mostrarMayusculas(...)
leerá del StringReader que le damos. Esta muestra los caracteres que lee del flujo genérico Reader
pasados a mayúsculas.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 278
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
CharArrayReader, CharArrayWriter
ByteArrayInputStream, ByteArrayOutputStream
De modo similar a los anteriores permiten que se pueda leer/escribir de un array de
caracteres o de bytes a través de un flujo genérico de datos.
public static void main(String args[]) {
· ··· ··· ··· ···
char[] tabla={'a', 'b', 'c', '1', '2', '3', '*', '-', '+'};
CharArrayReader flujoTabla= new CharArrayReader(tabla);
mostrarMayusculas(flujoTabla);
}
}
Los flujos de salida CharArrayWriter y ByteArrayOutputStream pueden ser utilizados
como zonas de memoria intermedia para lo que se va a escribir en otro flujo.
en los que se escribe para más tarde usando
El método writeTo(...) vuelca todo lo que en ellos se haya escrito sobre el flujo que se le
indique.
import java.io.*;
public class FlujosAMemoria {
public static void main(String args[]) throws IOException {
char ch;
CharArrayWriter flujoMemoria= new CharArrayWriter();
OutputStreamWriter pantalla= new OutputStreamWriter(System.out);
while((ch=Entrada.readChar())!='*')
flujoMemoria.write(ch);
System.out.println("Volcado de todo lo escrito a la salida estandar.");
flujoMemoria.writeTo(pantalla);
pantalla.close();
flujoMemoria.close(); // Esta línea es opcional. Realmente no hace nada.
}
}
nota.- Estas clases disponen del método close() porque deben sobrecargarlo, pero su implementación
está vacía, no hace nada (recuerda que simplemente escribe en memoria).
Si se necesita acceder más libremente al flujo de memoria en el que escribimos,
podemos crear con toCharArray() y toByteArray() respectivamente las correspondientes
tabla de caracteres o de bytes (según sea el flujo).
También cabe la posibilidad de heredar de ellas, para disponer de acceso libre al
atributo buf (en donde se almacenan los datos) declarado protected en todas las clases.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 279
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
PipedReader y PipedWriter
PipedInputStream y PipedOutputStream
Flujos pensados para utilizarse emparejados, se utilizan para conectar dos tareas que se
ejecuta concurrentes (al mismo tiempo).
En java la clase Thread y la interface Runnable, permiten crear fragmentos de código
que se ejecután concurrentemente (al tiempo). A estas parte que se ejecutan en paralelo
se les llama tareas, o hilos y son casi como procesos independientes.
Este tipo de código no será tratado en este manual, pero se menciona aquí porque los
flujos Piped están pensados precisamente para estas tareas o hilos.
El modo de proceder consiste en conectar un flujo piped (entubado) de salida con otro
flujo piped de entrada. Los datos que se escriben por el flujo de salida quedan grabados
en un fichero temporal (sin nombre) del cuál se leerán con el flujo de entrada al que se
conectó la salida. El fichero temporal deja de existir en cuanto deja de ser útil.
PipedOutputStream
o
PipedWriter
PipedInputStream
o
PipedReader
Tarea 2
Tarea 1
Los datos quedan acolados de modo que se leen del flujo pipe de entrada en el mismo
orden en que se han escrito por el flujo pipe de salida.
Estas lecturas y escritura pueden simultanearse de modo que dos tareas que se ejecuten
simultaneamente pueden enviarse información por está vía (como si de una tubería se
tratase).
A menudo con esta estrategia, se hace que la salida de datos de una tarea se convierta en
la entrada de datos de otra, cuya salida nuevamente se entuba hacia una nueva tarea,
formando así secuencias de tareas que actúan como filtros encadenados.
Salida:
Listado de
calificaciones
de alumnos
Un
PipedReader
Tarea 3
Tarea 2
Tarea 1
Entrada:
Listado de
calificaciones
de alumnos
se
puede
Salida:
Listado de
calificaciones
de los alumnos
aprobados
conectar
con
Entrada:
Salida:
Listado de
Listado Ordenado
calificaciones
de calificaciones
de los alumnos
de los alumnos
aprobados
aprobados
un
PipedWriter
(o
viceversa)
Un PipedInputStream se puede conectar con un PipedOutputStream (o viceversa)
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 280
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
SequenceInputStream
Permite instanciar objetos asociados a múltiples InputStream de los cuales irá leyendo.
Las primeras lecturas llegarán del primer flujo de entrada. Cuando no le quedan datos,
las lecturas pasarán a recibirse automáticamente del flujo de entrada siguiente hasta que
nuevamente se lea su último dato. Así continuará hasta que se agoten los datos del
último InputStream.
De este modo la lectura de un SequenceInputStream se comporta como si los flujos de
entrada con los que se ha construido se hubiesen concatenado colocándose uno detrás de
otro en secuencia.
El siguente ejemplo crea un fichero resultado de concatenar el contenido de dos ficheros
de texto.
import java.io.*;
public class UneFicheros {
public static void main(String args[]) {
int dato;
try {
FileInputStream fich1, fich2;
SequenceInputStream fichConcatenados;
InputStreamReader fichOrigenTexto;
FileWriter fichDestinoTexto;
fich1 = new FileInputStream("primero.txt");
fich2 = new FileInputStream("segundo.txt");
fichConcatenados = new SequenceInputStream(fich1, fich2);
fichOrigenTexto = new InputStreamReader(fichConcatenados);
fichDestinoTexto = new FileWriter("primero_segundo.txt");
while((dato=fichOrigenTexto.read())!=-1)
fichDestinoTexto.write(dato);
fichDestinoTexto.close();
fichOrigenTexto.close();
} catch(IOException e) {
System.out.print("Error concatenando ficheros.");
}
}
}
Si el estándar de caracteres usado en los ficheros de entrada es diferente del que usa por
defecto la máquina virtual java, habría que indicarselo al constructor de
InputStreamReader.
En todo caso, si los ficheros de entrada utilizasen juegos de caracteres diferentes entre
sí, deberían ser tratados por separado (no con SequenceInputStream), interpretando
correctamente el juego de caracteres de cada cuál.
·· · ······ ··· ·····
fich1 = new FileInputStream("primero.txt");
fich2 = new FileInputStream("segundo.txt");
fichOrigenTexto1 = new InputStreamReader(fich1, “<UnEstandarDeCodigo>”);
fichOrigenTexto2 = new InputStreamReader(fich2, “<OtroEstandarDeCodigo>”);
·· ···· ·· · ····
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 281
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
FilterReader y FilterWriter FilterInputStream
y FilterOutputStream
Todas ellas realizan un papel similar, servir de clases base para crear por herencia
nuevas clases con las que podremos instanciar objetos (flujos) de entrada/salida a partir
de otros flujos ya existentes...
public class FlujoFiltro extends FilterXxxxxx {
// Constructor
public FlujoFiltro(...<flujoOriginal_Entrada_o_Salida>){
super(<flujoOriginal_Entrada_o_Salida>);
}
....... ..... ..
...para con ellos interceptar los datos que se reciban/envien aplicándoles alguna
transformación, a modo de filtro.
- Si se trata de un flujo de entrada sobrecargamos los metodos de lectura.
....... ..... ..
public int read() throws IOException {
int dato= super.read();
.....
// instrucciones de filtrado del dato que retorna
// “super.read()” leído del flujo de entrada asociado
.....
return dato;
}
public int read(char[] b, int d, int c) throws IOException {
......
//similar.
......
}
....... ..... ..
} // fin de la clase FlujoFiltro
- Si se trata de un flujo de salida los metodos de escritura.
.. .... ......
public void write(int dato) throws IOException {
.....
// instrucciones de filtrado para el parámetro dato que
// se enviará al flujo de salida usando “super.write(dato)”
.....
super.write(dato);
}
public void write(char[] b, int d, int c) throws IOException {
......
//similar.
......
}
........
} // fin de la clase FlujoFiltro
Obviamente no será necesario sobrecargar todos los métodos de la clase base elegida
sino sólo aquellos en los que se quiera aplicar un filtrado.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 282
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Es posible que nuestras clases filtro, no hereden directamente de una de estas clases
(FilterReader, FilterInputStream, FilterWriter, FilterOutputStream) sino de alguna
clase derivada de ellas, por lo que conviene contar con que dicha clase puede también
querer aplicar su propio filtro de datos. Por esto:
- Los constructores deben invocar al constructor padre “super(...);” .
- Y al leer o escribir dato se debe recibir o enviar usando el método del padre
“...super.read();” (o “super.write(...);” )
nota.- En ocasiones es posible encontrar clases de flujos que filtran sus datos usando esta misma
estrategia, pero sin heredar de estas clases. En realidad cualquier clase que disponga de
constructores con los que instanciar objetos (flujos) a partir de otros flujos ya existentes, puede
servir para este mismo fin.
PrintWriter y PrintStream
Imprimen datos formateándolos con su representación en forma de texto.
Una peculiaridad importante de estos objetos, es que no lanzan IOException, por lo que
para usarlos no es necesario preocuparse por la gestión de excepciones.
En su lugar, disponen del método checkError() que permite comprobar en cualquier
momento si se el flujo se encuentra en estado de error.
PrintWriter(OutputStream out)
PrintWriter(OutputStream out, boolean autoFlush)
PrintWriter(Writer out)
PrintWriter(Writer out, boolean autoFlush)
Los flujos PrintWriter se pueden crear a partir de un objeto OutputStream, o un
Writer (incluidos los derivados como por ejemplo los OutputStreamWriter).
nota.- Se puede construir a partir de un OutputStreamWriter para que traduzca los caracteres a un
estándar distinto del utilizado por defecto por la máquina java.
PrintStream(OutputStream out)
PrintStream(OutputStream out, boolean autoFlush)
Los PrintStream sólo se construyen sobre flujos OutputStream, por lo que sólo
imprimen con el estándar de caracteres por defecto.
Cuando se crean se puede especificar (con el parametro autoFlush a valor true) que todo
lo que se escriba se envíe rápidamente a su destino (sin optimizar el proceso usando
buffers). Si se especifica que no se imprima automáticamente, los datos permanecen en
el flujo hasta que se invoque al método flush(), o el flujo se llene y vuelque su contenido
por sí mismo.
Esto resulta especialmente útil en programas interactivos, en los cuales un usuario
espera indicaciones respecto a qué debe hacer en cada momento.
nota.- Mensajes del tipo...
Autor : FernandoToboso Lara
- Introduzca su número de usuario: _
e-mail: [email protected]
Pag. 283
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
LineNumberReader
Esta es una clase derivada de BufferedReader por lo que igualmente facilita la lectura de
caracteres agrupados por líneas. Pero además controla el número de línea por el que está
leyendo en cada momento.
Cada vez que lee el carácter retorno de carro incrementa la cuenta y en cualquier
momento se puede consultar el número de línea en la que se está leyendo.
nota.- Entiende por línea, la que termina con caracter ‘\n’, con ‘\r’, o con ambos ‘\n’ y ‘\r’ (en este orden).
PushbackReader y PushbackInputStream
Heredan respectivamente de las clases FilterReader y FilterInputStream. Ambas
aportan la capacidad de devolver datos al flujo del que leen, de manera que estos queden
en disposición de volver a ser leídos en las siguientes operaciones de lectura.
Son especialmente interesantes cuando se desean conocer anticipadamente los datos que
se van a leer, dejando sin cambios el flujo (se puede leer un dato y después “desleerlo”,
es decir devolverlo al flujo).
El método ImprimeSecuenciaCreciente(...) del siguiente ejemplo recibe un flujo de
bytes de tipo PushbackInputStream. De él va leyendo números (tipo byte -128..0..127)
que imprime en una misma línea, mientras los números recibidos siguen un orden
ascendente. Cuando encuentra uno que rompe la secuencia creciente lo devuelve al flujo
(para ser tratado más tarde) y finaliza imprimiendo un retorno de carro.
El método es invocado desde el módulo principal hasta que retorna false (no quedan
datos en el flujo), por lo que los números del flujo se imprimen en varias líneas, cada
una con un fragmento de números ordenados.
import java.io.*;
public class SeparaSecuencias {
public static void main(String args[]) {
int caracter;
byte[] tablaByte={34,36,12,17,65,2,4,7,45,90,101};
ByteArrayInputStream flujo= new ByteArrayInputStream(tablaByte);
PushbackInputStream flujoAnticipado= new PushbackInputStream(flujo);
while(ImprimeSecuenciaCreciente(flujoAnticipado));
// bucle vacio, sin sentencias.
// (ojo al punto y coma tras el while)
}
static boolean ImprimeSecuenciaCreciente (PushbackInputStream fPush) {
int numero, nuevoNumero=-1;
try {
if((numero=fPush.read())!=-1) {
System.out.print(" "+numero);
while((nuevoNumero=fPush.read())!=-1 && nuevoNumero>numero ) {
numero=nuevoNumero;
System.out.print(" "+numero);
}
if(nuevoNumero!=-1) // aun hay numeros en el flujo.
fPush.unread((int)nuevoNumero);
System.out.println();
}
} catch(IOException e) {
System.out.println("Error en flujo PushbackReader.");
}
return (nuevoNumero!=-1); //cierto si quedan numeros por leer.
}
}
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 284
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Lo que se mostraría en la salida,
serían estas líneas...
34 36
12 17 65
2 4 7 45
_
90
101
DataInputStream y DataOutputStream
Implementan respectivamente los interfaces DataInput y DataOutput, implementando
todos sus métodos.
Crean a partir de un flujo de bytes, un flujo capaz de leer/escribir datos de tipos
primitivos java en formato binario (tal como se almacenan en memoria) de modo que se
puedán recuperar exactamente tal como era antes de escribirse en el flujo.
Como el formato interno de los tipos de datos es el mismo para cualquier máquina java
(cualquiera que sea el sistema en que se esté ejecutando), los datos pueden ser
recuperados sin problemas en cualquier entorno.
nota.- Otros lenguajes utilizan variantes en el formato de sus datos dependiendo de la capacidad de de la
máquina para la que se compila su código.
Un caso especial lo forman la pareja de métodos readUTF() y writeUTF(...) capaces de
leer/escribir datos String en formato UTF, que como ya se comentó antes permite
ahorrar espacio en el almacenamiento de los caracteres al tiempo que hace portables
desde/hacia sistemas tradicionales (que no reconocen el estándar unicode).
Aquí un ejemplo en el que unos datos (primitivos y String) son leídos de la entrada
estándar para ser escritos en un fichero (en formato binario propio de cada dato) y
finalmente recuperados tal como eran antes de ser almacenados.
....... ....... .....
FileOutputStream flujoHaciaFichero= new FileOutputStream("datos.dat");
DataOutputStream datosHaciaFichero= new DataOutputStream(flujoHaciaFichero);
Salida.print("Introduce un número entero: ");
datosHaciaFichero.writeInt(Entrada.readInt());
Salida.print("Introduce un número real: ");
datosHaciaFichero.writeDouble(Entrada.readDouble());
Salida.print("Introduce un caracter: ");
datosHaciaFichero.writeChar(Entrada.readChar());
Salida.print("Introduce una frase: ");
datosHaciaFichero.writeUTF(Entrada.readString());
datosHaciaFichero.close(); // Cerrando el flujo se confirma nada queda
// pendiente se ser escrito y se liberan los
// recursos usados. En este caso el fichero datos.dat
Salida.println("\nLos datos han sido escritos en binario en el fichero \"datos.dat\"");
Salida.println("al leerlos, se reciben exactamente tal como eran, como se ve ahora:");
Salida.println("------------------------------------------------------------------\n");
InputStream flujoDesdeFichero= new FileInputStream("datos.dat");
DataInputStream datosDesdeFichero= new DataInputStream(flujoDesdeFichero);
Salida.println("Número entero: "+ datosDesdeFichero.readInt());
Salida.println("Número Real: "+
datosDesdeFichero.readDouble());
Salida.println("Caracter: "+
datosDesdeFichero.readChar());
Salida.println("Texto: "+
datosDesdeFichero.readUTF());
datosDesdeFichero.close(); // Libera el recurso, fichero de lectura.
.... ............ ...
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 285
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Los métodos de lectura readInt(),readDouble(), readChar(), readUTF(), ... retornan
respectivamente datos de tipo int, double, char, String, de modo que cada cuál podría
ser utlizado para realizar operaciones propias de su tipo si así se necesita.
··· ·· ····· ····· ·····
DataInputStream datosDesdeFichero= new DataInputStream(flujoDesdeFichero);
int numeroEntero = datosDesdeFichero.readInt();
Salida.println("El número era: "+ numeroEntero + “ y su doble es: ” + 2*numeroEntero );
· · ·· ····· ···
ObjectInputStream y ObjectOutputStream
Implementan respectivamente los interfaces ObjectInput y ObjectOutput. Estas
interfaces heredan a su vez de DataInput y DataOutput por lo que indirectamente son
también implementadas.
Son flujos parecidos al anterior visto. Sólo que es capaz de leer/escribir no sólo datos de
tipos primitivos sino incluso objetos de cualquier clase (incluso objetos de clases
desarrolladas por nosotros mismos).
class DataInputStream
interface DataInput
interface ObjectInput
interface DataOutput
interface ObjectOutput
Herencia
Implementación
class ObjectInputStream
class DataOutputStream
class ObjectOutputStream
INTERFACE
CLASE
Son básicas para el proceso denominado serialización. Serializar un dato consiste en
transformarlo en una secuencia de bytes que lo describa adecuadamente de manera que
pueda ser escrito en un flujo ObjectOutputStream y posteriormente reconstruirlo
correctamente leyendolo a través de un ObjectInputStream.
Más adelante se tratará sobre la serialización y profundizaremos en su uso.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 286
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Gestión del entidades fichero
El uso de ficheros para gestionar los datos de un sistema resulta fundamental para
cualquier sistema informático. Una gran parte de los flujos creados por un programa le
sirven para comunicarse con un fichero (leer y escribir en él).
Sin embargo existen sutiles pero importantes diferencias en el modo en que cada
sistema nombra a sus ficheros y directorios...
- Unos consideran diferentes las letras mayúsculas y las minúsculas, otros no.
- Cada cuál usa signos distintos para los separadores de los nombres de directorios.
- Unos no admiten que los nombres de fichero comienzen número, otros sí.
- Unos disponen de un sólo directorio raiz, otros tienen varios (uno por unidad o dispositivo)
- etc...
Por eso, para facilitar la codificación de programas portables de un sistema a otro, java
dispone de la clase File, cuyos objetos pueden ser utilizados, en lugar de los nombre de
ficheros y directorios, para realizar multitud de operaciones para su gestión.
clase File
Cada objeto de esta clase representa un nombre genérico de fichero o directorio
independiente del sistema en que se ejecuta la aplicación.
Esta clase permite que a veces los programas puedan trabajar con ficheros sin
necesidad de usar el nombre propio de un sistema concreto de ficheros.
Por ejemplo, en el paquete java.awt correspondiente a la librería de utilidades gráficas
podemos encontrar la clase FileDialog con la cuál un programa puede solicitar que se
le indique en una ventana el fichero que debe utilizar sin necesidad de escribir una sola
letra de su nombre, simplemente haciendo “click” con el ratón.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 287
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Este fragmento de código, al ejecutarse desde un objeto de clase Frame (del paquete
gráfico java.awt), mostraría una ventana de diálogo como la anterior, para hacerlo de
sólo lectura.
· ·· ··· ···
FileDialog ventanaFicheros = new FileDialog(this);
ventanaFicheros.show();
File nombreFichero = new File(ventanaFicheros.getFile());
nombreFichero.setReadOnly();
· ···· · ·· ···
Estas son las posibles opciones para construir objetos File.
File(File directorioBase, String nombreFichero)
File(String nombreFichero)
File(String directorioBase, String nombreFichero)
Pero debe tenerse en cuenta que sólo representa al nombre que lo identifica, el fichero
(o directorio) propiamente dicho no tiene por qué existir.
import java.io.File;
public class FileExiste {
public static void main(String args[]) {
String nombreFich=Entrada.readString();
File fich=new File(nombreFich);
System.out.print(nombreFich);
if(!fich.exists())
System.out.print(" no");
System.out.print(" existe.");
}
nota.- cuando se instancia el objeto File con
un pathname relativo se considera
como directorio actual el valor
asociado a la propiedad de sistema
“user.dir” normalmente el directorio
desde el que se invoca al interprete
Java (o máquina virtual Java)
}
Los objetos File...
- Se pasan como parámetros a los constructor de clases como FileInputStream,
FileOutputStream, FileReader, FileWriter, para indicarle el fichero con el que deben
conectar el flujo.
- Dispone de métodos con los que consultar algunas propiedades... si identifica a un
directorio, si existe (el fichero o directorio que identifica), cuándo se modificó por
última vez, etc...
- También tiene métodos con los que modificar el fichero que representa.
- Facilita el análisis de las partes del pathname de un fichero mediante métodos como
getAbsolutePathName(), getName(), getCanonicalPath(), etc... que retornan distintas
fragmentos con sentido propio, del nombre del fichero (o directorio) referenciado,
usando la nomenclatura propia del sistema en que se esté ejecutando la aplicación.
- etc...
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 288
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
La ejecución de este fragmento de programa, lee un nombre de fichero o directorio
desde la entrada estándar y lo muestra adaptando los nombres al sistema en que se
ejecuta...
......... ....
String nombreFich=Entrada.readString();
File fich=new File(nombreFich);
Salida.println("** Directorio actual");
Salida.println(System.getProperty("user.dir"));
Salida.println();
Salida.println("** Nombre dado (en el contructor)");
Salida.println(fich.getPath());
Salida.println();
Salida.println("** Nombre absoluto canonico");
Salida.println(fich.getCanonicalPath());
Salida.println();
Salida.println("** Nombre dado para el fichero (sin la ruta de directorios)");
Salida.println(fich.getName());
Salida.println();
Salida.println("** Directorio padre dado para fichero (sin el último nombre)");
Salida.println(fich.getParent());
.... ......... .......
...después intenta borrar el fichero o directorio representado por el objeto File.
asegurandose de que realmente se ha conseguido eliminar...
.... ....... .. .......
if(fich.exists())
if(fich.delete())
Salida.println("** Ya se ha borrado.");
else
Salida.println("** No se ha podido borrar.");
.. .. ... ...... .......
Este otro fragmento muestra las distintas unidades (directorios raíz) del sistema de
ficheros de la máquina que lo ejecuta (a:\ , c:\ ,...).
...... ... ...... ..... ..
File[] raices=File.listRoots();
for(int i=0; i<raices.length; i++)
Salida.println(raices[i]);
. ... ......... .......
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 289
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
interfaces FilenameFilter y FileFilter
Ambas interfaces describen el comportamiento de objetos que hacen de filtro de
ficheros, decidiendo si aceptan un determinado fichero en función de si cumple o no
un determinado patrón (una condición, o grupo de condiciones).
Una clase que implemente FilenameFilter
debe definir el método...
La clase que implementa FileFilter
definirá estos...
public boolean accept(File directorio, String nombre)
public boolean accept(File referenciaFichero)
public String getDescription()
Cada método accept(...) resuelve si acepta, o no el fichero que referencian los
parámetros que se le pasan, retornando true o false como resultado.
En el siguiente ejemplo la clase Selector, acepta sólo los nombre de fichero con
extensión “.java” independientemente del directorio en el que se encuentre...
public class Selector implements FilenameFilter{
public boolean accept(File d, String n) {
try {
String extension=n.substring(n.length()-5);
return extension.equalsIgnoreCase(".java");
} catch(IndexOutOfBoundsException e) {
return false;
}
}
}
Existen clases java con métodos que implementan utilidades concretas a partir de estos
objetos de filtrado de ficheros.
Por ejemplo, a partir de un objeto File que referencie un directorio podemos usar estos
métodos...
String[] list( FilenameFilter... )
File[]
listFiles( FileFilter... )
...de manera que éstos retornen la selección de ficheros (File o String), que cumplan el
criterio aplicado por el métdodo accept(...) que haya definido el filtro de ficheros que
se les pase.
Con estos métodos podemos generar listados compuestos solamente por aquellos
ficheros que el FilenameFilter, o FileFilter, acepte.
.... ........ ......
String[] listado=fich.list(new Selector());
for(int i=0; i<listado.length; i++)
Salida.println(listado[i]);
...... .....
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 290
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Si modificamos ligeramente en el ejemplo visto en el apartado anterior, en el que
solicitaba el nombre de un fichero mediante una ventana gráfica de diálogo, podemos
hacer que dicha ventana sólo muestre los nombres de fichero que pasen el filtro del
objeto Selector (los terminados en .java).
· ·· ··· ···
FileDialog ventanaFicheros = new FileDialog(this);
ventanaFicheros.setFilenameFilter(new Selector());
ventanaFicheros.show();
· ···· · ·· ···
nota.- Desgraciadamente la implementación del método setFilenameFilter(...) no funciona
correctamente en las máquinas java de windows 95, 98, ni NT 4.0 , por lo que sobre
estas plataformas el ejemplo no aplicará el filtro a la lista de ficheros ofrecidos.
La interface FileFilter se utiliza de manera similar, allí donde se necesite.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 291
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Serialización de Objetos
Cuando necesitamos almacenar los objetos de nuestros programas de manera persistente
escribiéndolos hacia un flujo, es necesario transformar todos los elementos que forman
parte de ellos en una secuencia de bytes compacta que permita más tarde leerlos y
reconstruirlos exactamente tal y como erán.
Lo mismo ocurre cuando diferentes tareas o procesos desean transmitirse objetos, a
través de algún tipo de conexión entre ellos.
Piensa que muchos objetos se componen de datos referenciados probablemente situados
el sitios dispersos por memoria del sistema y que no son las clases sino los objetos los
que necesitamos serializar. Para ello, debemos utilizarse las clases ObjectOutputStream
y ObjectInputStream en combinación con la interface Serializable.
Para serializar (escribir) y deserializar (leer) objetos, estas clases disponen entre otros de
los métodos writeObject(...) y readObject(...).
public final void writeObject(Object obj) throws IOException
public final Object readObject() throws ClassNotFoundException,IOException
Pero esto no basta, además los objetos deben ser serializables por lo que sus clases
deberán implementar la interface Serializable.
interface Serializable {
}
De manera similar a como ocurría con la interface Cloneable, esta interface no declara
ningún elemento. Su utilidad reside igualmente en permitir determinar si una clase de
objetos puede ser serializada o no.
public MiClase implements Serializable {
···········
}
En la mayoría de los casos, para que una clase de objetos sea serializable simplemente
tendrá que declarar que implementa la interface Serializable. Si no lo hace así las
llamadas a los métodos writeObject() y readObject() lanzarán una excepción
NotSerializableException.
nota.- De igual modo a cómo los objetos que no implementan la interface Cloneable lanzaban excepción
al intentar ser clonados.
Los métodos writeObject() y readObject() actuarán escribiendo o leyendo los objetos
siguiendo extrictamente este orden:
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 292
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Punto 1. La definición de la clase a la que pertenece el objeto.
nota.- incluida una firma que permite diferenciarla de otras versiones de sí
misma, o de otras clases con igual nombre.
Punto 2. Todos los atributos miembro heredados de la clase base (incluso los de
acceso private). Siempre que ésta sea también serializable.
- Si la clase base no implementa Serializable, los atributos heredados no se
serializan (a no ser que se serialicen manualmente como veremos
después). A la hora de deserializar un objetos así, se ejecuta
automáticamente el constructor sin argumentos de su clase base (inicializar
adecuadamente los atributos que no fueron serializados). Si no dispone de
este constructor, se lanzará la excepción InvalidClassException (derivada
de IOException).
Punto 3. Por defecto se ejecuta el método defaultWriteObject() o bien el método
defaultReadObject() (según corresponda) que se ocupará de la serialización
de los atributos miembros del objeto no heredados (excepto los declarados
static y transient).
nota.- Los elementos estáticos no son miembros del objeto y los transitorios como su propio
nombre indica no se consideran precisos para la descripción de los objetos y por
tanto no merece la pena almacenarlos.
Cada vez que se serializa o deserializa un atributo (heredado o propio) se procede del
siguiente modo:
-
Si el atributo es de tipo primitivo, se utiliza el método que corresponda al tipo
de cada atributo...
· writeBoolean(...), writeFloat(...), etc... para serializar.
· readBoolean(...), readFloat(...), etc... para deserializar.
nota.- Recuerda que estas clases implementan ObjectOutput y ObjectInput, las cuales heredan
de DataOutput y DataInput. Por lo que también implementa estos métodos.
-
Si el atributo es un objeto, este deberá poder ser correctamente serializado.
De este modo se produce la serialización en aquellos objetos cuyas clases sólo se
ocupan de implementar la interface Serializable, dejando que el proceso de la
serialización se desarrolle por defecto.
Sin embargo, cuando la clase del objeto a serializar define además sus propios
métodos...
private void writeObject(OutputObjectStream obj)
private Object readObject(InputObjectStream obj)
nota.- es necesario usar estrictamente estos prototipos, incluida la cláusula private.
... writeObject() y readObject() de OutputObjectStream y InputObjectStream, no
ejecutarán los métodos defaultWriteObject() y defaultReadObject() (en el -Punto 3-) sino
que en su lugar ejecutarán los métodos writeObject() y readObject() definidos en el
propio objeto.
En la llamada se les facilitará la referencia al propio flujo y se delega en ellos la
responsabilidad de serializar adecuadamente los atributos no heredados del objeto.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 293
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
De esto modo podemos tomar el control de cómo se serializan y deserializan los objetos
de nuestras propias clases.
...class MiClase implements Serializable {
····· ······
private void writeObject (OutputObjectStream out) throws IOException {
// serializa automáticamente los atributos no transitorios del objeto
out.defaultWriteObject();
···· ···· ·
// termina la serialización hacia out personalizándola.
···· ···· ·
}
private Object readObject(InputObjectStream in) throws IOException,ClassNotFoundException {
// deserializa automáticamente los atributos no transitorios del objeto
in.defaultReadObject();
···· ···· ·
// termina la deserialización desde in personalizándola.
···· ···· ·
}
· ··· ···
}
Es muy recomendable incluir en estos métodos, las llamadas a defaultWriteObject() y
defaultReadObject() del flujo, para que se ocupen de serializar automáticamente los
atributos del objeto del -Punto 3- a no ser que deseemos seleccionar manualmente qué
atributos se deben serializar y cuáles no.
nota.- Si no se desea que un atributo sea serializado, lo más sencillo es declararlo transient.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 294
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
interface Externalizable
Como se ha podido observar, con un objeto Serializable que defina sus propios métodos
writeObject() y readObject(), podemos controlar la serialización de los atributos que se
definen en nuestra propia clase, pero no los que heredamos.
Si deseamos que nuestras clases se hagan cargo de la serialización de todos sus atributos
(tanto los propios como los heredados) deberán implementar la interface Externalizable.
interface Externalizable extends Serializable {
public void writeExternal(ObjectOutput out)
public void readExternal(ObjectInput in)
}
Y evidentemente implementar ambos métodos, incluyendo en ellos las instrucciones
que se encarguen de serializar todos lo atributos (heredados y propios).
Los métodos writeObject(...) y readObject(...) de los flujos OutputObjectStream y
InputObjectStream los invocarán después de ocuparse exclusivamente de serializar la
definición de la clase ( -Paso 1- ).
nota.- Estos métodos son necesariamente públicos, por lo que pueden poner en peligro la integridad de
los atributos del objeto si alguien los utiliza para cargar mediante ellos datos extraños o
manipulados. Deben por tanto usarse con extremo cuidado y sólo si son estrictamente necesarios.
Debe considerarse igualmente el hecho de que todos los datos serializados sobre un fichero se
ofrecen más o menos accesibles dependiendo solamente de los permisos de acceso que dicho
fichero tenga en el sistema que lo almacene.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 295
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
Entidades de almacenamiento de información
específicas
La utilización de flujos para la entrada/salida tiene la ventaja de generalizar la estrategia
de lectura/escritura para cualquiera que sea el origen/destino de la información.
Por contra, tiene el inconveniente de que probablemente no se aprovechen todas las
capacidades del dispositivo al que se accede.
nota.- Por ejemplo, cuando la salida estándar es una pantalla, el flujo de datos permitirá que se le envíe
una secuencia de caracteres para que se muestren pero difícilmente se podrán elegir las coordenadas de
pantalla en las que se desea que aparezcan dichos caracteres.
Por esto, cuando se manejan características particulares de una entidad (pantallas, bases
de datos, ficheros, etc...) se suele hacer uso de otras clases específicamente diseñadas.
Por ejemplo, la clase ya antes vista File dispone de una serie de métodos específicos
para manipulación específica de ficheros, con operaciones como:
-
crear un fichero temporal (*.tmp)
eliminar un fichero.
comprobar los permisos de acceso de un fichero.
comprobar si un determinado fichero existe.
etc...
Acceso a Bases de Datos.
Una Base de Datos es sistema complejo de almacenamiento de datos, generalmente
basado en ficheros, que garantiza seguridad, coherencia, accesibilidad y otras muchas
propiedades para la información que alberga.
Asociado a cada Base de Datos, existe un gestor de base de datos, un programa que se
ocupa de hacer de intermediario entre los datos almacenados y las aplicaciones que
desean consultar datos, modificarlos, añadir otros nuevos, etc...
Para comunicar al gestor de B.D. (Base de Datos) la operación que se desea realizar, se
utiliza un lenguaje estándar llamado SQL, utilizado por la práctica totalidad de las B.D.
que existen.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 296
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
A través del gestor y usando sentencias SQL, accedemos a los datos en forma de tablas,
en las que una fila vendría a ser la información de una determinada entidad y las
columnas los distintos datos almacenados sobre dicha entidad.
NOMBRE
IDENTIF_PERSONA
(varchar
(integer - entero)
texto)
243
12
54
9
····
Juan Jose
Antonia
Carmen
Engracia
········
-
APELLIDOS
(varchar - texto)
Bernillo Fajardo
Lopez Liesa
Garmota Mote
Barcian Lafuente
······ ·····
alumno
padre
IDENTIF_PERSONA IDENTIF_PERSONA
1
2
35
45
···
EDAD
(integer - entero)
SEXO
(char
caracter)
32
48
32
27
····
-
IDENTIF_DIRECCION
(integer - entero)
V
M
M
M
···
1
2
6
89
···
alumno
madre
IDENTIF_PERSONA IDENTIF_PERSONA
243
243
1
2
35
3
···
203
····
12
12
9
24
····
IDENTIF_DIRECCION DIRECCION_POSTAL
1
2
6
89
···
Camino viejo del Alamillo, 32 (TOLEDO)
Av. Los Almendrejos, 1 - 3º C (TOLEDO)
Callejon del Gato, 3 - Bajo La Roda (ALBACETE)
Pasaje del fuego tinto, 3 - 7º (LAS PALMAS G.C.)
··············
nota.- Como puede observarse las tablas segunda y tercera están relacionadas con la primera para
documentar el parentesco paternal y materno de cada alumno. Y la última columna de la primera
tabla idetifica la dirección postal de cada persona.
Los identificadores en negrita, quieren decir que no pueden estar repetidos en la misma tabla.
Podríamos pedir al gestor de la B.D. que crease la primera tabla transmitiéndole las
siguientes sentencias SQL:
CREATE TABLE DATOS ( IDENTIF_PERSONA INTEGER,
NOMBRE VARCHAR(20),
APELLIDOS VARCHAR(30),
EDAD INTEGER,
SEXO CHAR,
IDENTIF_DIRECCION INTEGER
);
Pero cómo podemos comunicarnos desde Java con un gestor de una B.D. para acceder a
su contenido. Fácilmente usando clases como Connection, Statement, etc... que el
paquete java.sql pone a nuestra disposición y siguiendo los siguientes de pasos:
1.- Establecer una conexión que nos permita comunicarnos con la B.D. en cuestión
(téngase en cuenta que ésta puede estar en un ordenador distinto del que ejecuta
nuestra aplicación.
1a.- cargar el driver responsable de la conexión con la B.D. (no olvides que también
debes importar todo lo que uses del paquete java.sql )
·· ·· ··· ···
Class.forName(“sun.jdbc.odbc.JdbcOdbcDriver”);
··· ····· ····
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 297
¡¡¡ P a r a q u e a p r en d a s . . . !!! P r o g r a m a c i ó n u s a n d o JAVA
1b.- mediante el método estático getConnection(...) de la clase DriverManager,
obtener un objeto Connection. Para ello deberemos facilitar la identificación de
la B.D. a la que deseamos acceder y a menudo un usuario y una contraseña con
privilegios de acceso a la misma.
··· ····· ····
String baseDatos = <nombre de la B.D.>;
String usuario = <nombre de usuario>;
String clave = <clave del usuario>;
Connection conex=DriverManager.getConnection(bd, usuario, clave);
· ····· ·······
nota.- El formato para el nombre de la base de datos dependerá de driver con el que se realiza
la conexión. Para ampliar información busca en un manual específico en esta materia.
2 .- usando el método createStatement() del anterior objeto conex (de clase Connection)
obtener un objeto Statement mediante el cual comunicaremos al gestor de la B.D.
los Strings con las sentencias SQL que sean necesarias.
Statement mediador = conex.createStatement();
3 .- utilizar los métodos de este objeto para comunicar la sentencias SQL al gestor de la
B.D. así como para recibir resultados en caso de sentencias de consulta.
Por ejemplo para crear la primera tabla de datos con nombres, edades y sexo, en una
B.D. a la que accederemos por el nombre “jdbc:odbc:agenda” podemos usar un código
como este:
import java.sql.*;
··· · ······ ····
Class.forName(“sun.jdbc.odbc.JdbcOdbcDriver”);
String baseDatos = “jdbc:odbc:agenda”;
Connection conex=DriverManager.getConnection(baseDatos, “”, “” );
Statement mediador = conex.createStatement();
String sentenciaSQL = “CREATE TABLE DATOS (” +
“IDENTIF_PERSONA INTEGER, ” +
“NOMBRE VARCHAR(20), ” +
“APELLIDO VARCHAR(30), ” +
“EDAD INTEGER, ” +
“SEXO CHAR, ” +
“IDENTIF_DIRECCION INTEGER” +
“)”;
mediador.executeUpdate(sentenciaSQL);
· ·· ··· ·· ·
nota.- Aquí no profundizaremos más sobre el estándar JDBC (que así es como se le conoce).
Si quieres profundizar en temas relacionados con bases de datos, necesitarás conocer el
lenguaje SQL, además de estudiar más a fondo las muchas posibilidades que ofrecen las
clases que hemos utilizado.
Autor : FernandoToboso Lara
e-mail: [email protected]
Pag. 298

Documentos relacionados