G.Bordel >Docencia >TAP | Técnicas Actuales de Programación | (curso 2010-2011) | |||||||
|
|||||||||
tema_anterior | Tema 6: El contenido de la biblioteca de clases | tema_siguiente |
|
6.3- Clases de utilidad (java.util
) /iterator/
Un paquete interesante por contener clases e interfaces de gran utilidad general es el java.util. Antes de nada mencionaremos que un buen grupo de clases pertenecientes a este paquete son estructuras de datos complejas que tienen en común el hecho de tratarse de estructuras capaces de almacenar colecciones de objetos y como tal comparten un determinado comportamiento similar aparte de las particularidades de cada una. En realidad el conjunto de clases e interfaces relacionados con las colecciones forman un "framework" bien definido que dejaremos para el final. Comenzaremos por comentar algunas de las clases e interfaces que se utilizan más frecuentemente de entre las que nos ofrece este paquete (Los elementos de los que es interesante echar un vistazo tienen un enlace a la documentación).
StringTokenizer
esta clase es interesante para ilustrar un patrón de diseño, si bien se trata de una clase mantenida en las actuales versiones sólo por compatibilidad. La clase permite extraer de un texto (de una String
) trozos de texto (los "tokens", en el caso más habitual palabras). El proceso será una iteración en la que se obtiene en cada ocasión el siguiente elemento a procesar del StringTokenizer
utilizando el método nextToken()
. Es posible asegurarse de que el siguiente elemento existe mediante el método hasMoreTokens()
|
El patrón en cuestión es este mecanismo de extracción de todos los elementos que está generalizado con la introducción del interfaz Enumeration
que implica la escritura de los métodos hasMoreElements
y nextElement
aplicable a otras muchas clases en Java. Con la introducción de las colecciones en la versión 1.2, este patrón quedó representado por el interfaz Iterator
que implica la escritura de los métodos hasNext
y next
además de introducir el remove
.
Volviendo a StringTokenizer
, mencionabamos que es una clase de uso no recomendado, de modo que la alternativa válida actualmente consiste en el uso del método split
de la clase String
.
|
Date
es una clase que nos permite trabajar con fechas además de obtener la actual. Algunos de sus métodos han sido declarados "deprecated" siendo sustituida su funcionalidad por otros de la clase Calendar
. Esta es una clase abstracta de paso a otras que se refieren a calendarios concretos. Dentro de este mismo paquete nos encontramos con un calendario en concreto que es el que utilizamos nosotros: el GregorianCalendar
La clase BitSet
, como su propio nombre indica es un tipo de dato adecuado para representar conjuntos de bits. Dispone de capacidades para activar, desactivar y cambiar el estado de los bits así como para realizar operaciones lógicas.
Random
es una clase para instanciar un generador de números pseudo-aleatorios (una distribución uniforme). Se trata de una implementación compleja por lo que, cuando los requerimientos no son excesivamente exigentes es mas adecuado utilizar el método Math.random
que es más sencillo y menos costoso computacionalmente (p.ej.: para elegir un elemento al azar de una lista podemos utilizar Math.random
, mientras que para realizar una integración por el método de MonteCarlo utilizaremos un objeto de clase Random
.
Las clases Timer
y TimerTask
permiten programar el instante de tiempo en que una tarea se comenzará a realizar.
Properties
es una clase para manejar "propiedades", es decir, pares nombre-valor. Esta soportada por una de las estructuras del framework "Colecciones": el "mapa" básico Hashtable que se verá más adelante. En particular es interesante la capadidad que proporciona esta clase para leer y escribir propiedades en formato texto (una linea por propiedad) y en formato XML.
ResourceBundle
permite escribir programas independientes de las especificidades locales, es decir, principalmente el idioma pero además otros elementos como el tipo de moneda, las convenciones de escritura de fechas o cantidades, etc. Mediante la gestión de un ResourceBundle
ligado a cada situación local los programas pueden ajustar su ejecución en cada caso bien por selección del usuario o incluso por detección del "locale" especificado en el sistema operativo (p. ej. un mismo programa tendrá sus textos en inglés o en español en función de que la máquina en que se ejecute este configurada para uno u otro lenguaje).
Scanner
es una clase introducida en la versión 1.5 cuya función consiste en procesar textos interpretando sus contenidos en función de las expresiones regulares que se le proporcionen.
Formatter
es también una clase introducida en la versión 1.5 y viene a simplificar algo que hasta el momento resultaba complejo en Java: la escritura de datos de salida con formato. Se ha adoptado un modo de funcionar muy próximo al del lenguaje C, pero naturalmente con las especificidades y mayores capacidades de Java.
Como hemos mencionado al principio, una buena parte de los elementos definidos en el paquete que nos ocupa se agrupan dentro de un esquema consistente conocido como "colecciones". En realidad la relación entre el paquete y las colecciones no es estricta ya que hay elementos pertenecientes a otros paquetes que forman parte de las colecciones (sin ir más lejos un interface -el Comparable
- relacionado con el que veremos más adelante y que pasamos por alto al ver el paquete java.lang
)
En realidad para comprender a fondo el "Framework" colecciones ha de tenerse conocimiento sobre una serie de estructuras de datos más o menos complejas, así como de algún patron de diseño y un cierto dominio de la orientación a objetos. Por tanto, dado que no presupondremos esto y no es objeto del presente curso, comentaremos someramente estas colecciones.
Una visión general nos permite ver que este "Framework" está formado por una serie de interfaces, clases, algoritmos y utilidades para Arrays. A su vez las clases pueden clasificarse como generales (las básicas), específicas (para situaciones concretas), heredadas(mantenidas de versiones Java anteriores a este framework), concurrentes, envoltorios (patrón "wrapper": una clase que recubre a otra), de conveniencia (implementaciones extremadamente específicas y de alto rendimiento) y abstractas (para estructurar adecuadamente el conjunto de clases). A continuación comentamos los elementos básicos de este Framework:
El elemento más simple de las colecciones es la lista, representado por el interfaz List
. La clase básica que implementa este interfaz es Vector
, una clase heredada de una utilidad importante. Un vector permite almacenar una lista de objetos de longitud indeterminada. Su estructura interna puede ser ignorada (como corresponde en general a la orientación a objeto) si bien sbremos que se trata de un array autodimensionable qe podrá ser utilizado para introducir tantos elementos como sea necesario sin más limitación de espacio que la disponibilidad de memoria. En principio, los elementos a almacenar pueden ser heterogéneos, es decir, de diferentes clases, quedando referenciados por el vector como Object
, lo que implica que en caso de querer reasignarlos a una referencia de su clase concreta será necesario un cast.
|
En el ejemplo anterior hacemos uso del conocimiento de la estructura de array del vector, pero en caso de necesitar recorrer toda la estructura también puede trabajarse mediante iteradores. A continuación vemos el modo de imprimir el contenido de un vector homogéneo de fechas mediante el acceso a sus posiciones y mediante el uso de una enumeración.
|
|
Desde la reciente versión 1.5 de Java es posible "caracterizar" las colecciones -y en particular los vectores- de modo que en caso de ser homogéneos, el compilador tenga control de la consistencia de su uso y además esto permite eliminar la necesidad de los cast.
|
Para terminar con el Vector
diremos que pueden instanciarse vectores vacios de tres modos distintos en función del control que deseemos sobre su política de uso de memoria. El constructor sin parámetros deja a la implementación interna el control del tamaño inicial y del redimensionamiento (comienza con 10 una reserva). Un parámetro entero determinará el tamaño inicial a reservar por el vector, mientras que dos parámetros enteros se interpretan como tamaño inicial y e incremento a aplicar en cada caso en que el espacio reservado se haga insuficiente. Por otro lado puede construirse un vector inicializado con los elementos de cualquier colección.
Una subclase de Vector
es Stack
. Esta estructura es la conocida "pila" (o LIFO - Last In First Out) que añade a las capacidades del Vector
las propias de gestion de la lista como una pila: push
, pop
, peek
y empty
(en realidad añade una más que no pertenece al tipo abstracto pila: search
)
Otro tipo de lista es el
ArrayList
. Esta es una implementación básicamente igual a Vector
pero más ligera por ser no sincronizada (recordemos que sucedía lo mismo con StringBuffer
y StringBuilder
como veíamos en el apartado anterior). Es decir, la clase más segura pero también más costosa computacionalmente es Vector
, pero si no programamos tareas paralelas que actúen sobre un mismo Vector
, éste puede ser sustituido por un ArrayList
.
Por último mencionaremos otro tipo de lista:
LinkedList
. Se trata de una lista no soportada por arrays (no contigua), sino que enlaza elementos mediante estructuras diseminadas por la memoria(enlaces). En realidad se trata de una lista doblemente ligada (puede recorrerse en los dos sentidos puesto que tiene enlaces en ambos) e implementa los métodos que permiten utilizarla como una pila (Stack o LIFO) además de los del tipo abstrarto de datos "cola" (Queue o FIFO - First In First Out) implementando el interfaz Queue
que mencionamos más adelante. Se trata de una implementación no sincronizada.
El interfaz Map
se corresponde con el tipo abstracto de datos "mapa" consistente en una relación entre elementos de dos conjuntos que se caracteriza por que uno de ellos (denominado conjunto de "llaves"), tiene todos sus elementos distintos y cada uno de ellos se relaciona con sólo un elemento del otro conjunto (conjunto de "valores").
Con los mapas sucede exactamente lo mismo que hemos visto con las listas: tenemos una clase heredada que se basa en estructuras contiguas (hasta cierto punto como veremos enseguida) y es sincronizada -Hashtable
-, una clase semejante pero no sincronizada -HashMap
- y otra basada en estructuras ligadas -LinkedHashMap
-. Todas ellas utilizan la técnnica de "hashing" para optimizar los tiempos de acceso por lo que es necesario que los objetos almacenados pertenezcan a clases con su método hashcode
correctamente definido (recuérdese el estudio de la clase Object
en el apartado anterior).
Para quienes no conozcan las técnicas de "hash" diremos únicamente que se trata de estrategias de almacenamiento en las cuales la posición que ocupa un objeto depende de su "hashcode", un entero calculado en función de su contenido con una función normalmente estudiada cuidadosamente para obtener los mejores resultados de distribución de elementos en estas estructuras. El código hash permite un acceso calculado el elemento, pero como normalmente el espacio de códigos será menor que el espacio de objetos a almacenar pueden darse "colisiones" (dos objetos con un mismo código), y en tal caso se dispone de una sub-estructura de almacenamiento de todos los objetos que colisionan con un mismo "hashcode". El objetivo de las funciones es por tanto que estas subestructuras tengan dimensiones semejantes.
Disponemos dentro de las colecciones de otro interfaz de tipo mapa que en este caso asegura el orden en sus elementos: el
SortedMap
. Una clase implementa este interface: la TreeMap
. La estructura interna de esta clase es un árbol binario conocido como "arbol rojo-negro" que manteniendo la relación de orden de sus elementos (mayores de un nodo en subarbol derecho y menores en subarbol izquierdo o viceversa) se encuentra siempre equilibrado (no hay ramas cortas y largas, sino que la máxima diferencia es de 2 niveles) gracias a unas operaciones de "rotación" aplicadas en la inserción y extracción de elementos.
Como ya podrá intuir el lector dada la regularidad de los nombre de estas clases e interfaces en relación con lo visto hasta ahora, de nuevo nos encontramos con una estructura abstracta de datos representada por dos interfaces, uno permitiendo el orden en los elementos almacenados y otro no, y una serie de clases que los implementan haciendo uso de técnnicas de hash sobre estructuras contiguas y ligadas y sobre "árboles rojo-negros". La diferencia estriba en que ahora la estructura abstracta es el conjunto, es decir una colección de objetos que no admite que objetos duplicados (es decir no hay dos elementos e1 y e2 en el conjunto tales que e1.equals(e2)).
A continuación se proporcionan los enlaces al interfaz Set
y las clases que lo implementan: HashSet
y LinkedHashSet
, así como al interfaz SortedSet
y a la clase TreeSet
que lo implementa.
La estructura abstracta de datos "cola" (FIFO) que ya ha aparecido anteriormente al hablar de las listas y en concreto de la LinkedList
esta representada por el interfaz Queue
. La mencionada clase es la implementación básica de este interfaz (y por el hecho de implementar también el interfaz List
apareció anticipadamente). No obstante en ocasiones el tipo abstracto "cola" representa algo más ámplio en el sentido de que no ha de ser estrictamente una gestión FIFO y este es el caso del interfaz Queue
de Java. Esto permite otra implementación en la que el orden de salida viene determinado por un valor de prioridad asociado a cada objeto: es la PriorityQueue
y está soportada por una estructura interna de tipo "heap" como corresponde a su funcionalidad.
Al comentar la clase StringTokenizer
se han mencionado dos interfaces pertenecientes a este paquete (Enumeration
y Iterator
). Otro interfaz de interés es el Comparator
que permite establecer un criterio de comparación específico para los elementos de la clase que lo implemente. Implica la escritura de dos métodos, uno para determinar la igualdad y otro para comparar de modo que se establezca una relación de orden entre los objetos.
Aunque hay alguna otra pequeña cosa referente a Arrays, básicamente la aportación al respecto dentro de este Framework consiste en la clase Arrays
que proporciona métodos para su manipulación entre los que destacan las búsquedas y ordenaciones ("search" and "sort").
Aunque no se trate de algo perteneciente al paquete java.util
, no dejaremos pasar por alto la existencia de subpaquetes, y en particular de java.util.zip
, ya que nos proporciona una capacidad muy interesante en ocasiones consistente en permitir trabajar directamente con datos compactados en el formato estándar de ZIP y GZIP. Es interesante mencionar que esta capacidad facilita la ejecución de programas que han de procesar grandes cantidades de datos repartidos en numerosos ficheros (u otros origenes) ya que si estos se encuentran agrupados y compactados el número de operaciones de lectura se reduce "dramáticamente" presentando una potencia incomparable frente a programas que no hagan uso de esta capacidad.
Siguiente punto: 6.4- Genéricos
Plataforma de soporte a curso y contenidos (c) German Bordel 2005. |