m16 Deitel Como-programar-En-java Se 10ed c16 684-728 XXXX-X
m16 Deitel Como-programar-En-java Se 10ed c16 684-728 XXXX-X
m16 Deitel Como-programar-En-java Se 10ed c16 684-728 XXXX-X
Paul Deitel
Deitel & Associates, Inc.
Harvey Deitel
Deitel & Associates, Inc.
Traducción
Alfonso Vidal Romero Elizondo
Ingeniero en Sistemas Electrónicos
Instituto Tecnológico y de Estudios Superiores de Monterrey - Campus Monterrey
Revisión técnica
Sergio Fuenlabrada Velázquez
Edna Martha Miranda Chávez
Judith Sonck Ledezma
Mario Alberto Sesma Martínez
Mario Oviedo Galdeano
José Luis López Goytia
Departamento de Sistemas
Unidad Profesional Interdisciplinaria de Ingeniería y Ciencias Sociales
y Administrativas, Instituto Politécnico Nacional, México
16
Creo que ésta es la colección más
extraordinaria de talento, de
Colecciones de genéricos
Objetivos
En este capítulo aprenderá:
■ Qué son las colecciones.
■ A utilizar la clase Arrays para
manipulaciones de arreglos.
■ A usar las clases de envoltura
de tipos que permiten a los
programas procesar valores de
datos primitivos como objetos.
■ A utilizar las estructuras de
datos genéricas prefabricadas
del marco de trabajo de
colecciones.
■ A utilizar iteradores para
“recorrer” una colección.
■ A utilizar las tablas hash
persistentes que se manipulan
con objetos de la clase
Properties.
16.1 Introducción
En la sección 7.16 presentamos la colección de genéricos ArrayList, que es una estructura de datos
tipo arreglo que puede ajustar dinámicamente su tamaño y almacenar referencias a objetos de un tipo que
se especifica al momento de crear el objeto ArrayList. En este capítulo continuaremos nuestra explicación
del marco de trabajo de colecciones de Java, que contiene muchas otras estructuras de datos genéricas
prefabricadas.
Algunos ejemplos de colecciones son sus canciones favoritas almacenadas en su teléfono inteligente
o reproductor de audio, su lista de contactos, las tarjetas que posee en un juego de cartas, los miembros
de su equipo deportivo favorito y los cursos que tomó alguna vez en la escuela.
En este capítulo hablaremos de las interfaces del marco de trabajo de colecciones, las cuales declaran
las posibilidades de cada tipo de colección; también hablaremos de varias clases que implementan a estas
interfaces, de los métodos que procesan los objetos de las colecciones y de los iteradores que “recorren” las
colecciones.
Java SE 8
Después de leer el capítulo 17, Lambdas y flujos de Java SE 8, podrá volver a implementar muchos de
los ejemplos del capítulo 16 de una manera más concisa y elegante, y de modo tal que facilite la paraleliza-
ción para mejorar el desempeño en los sistemas multinúcleo de la actualidad. En el capítulo 23 (en inglés,
en el sitio web del libro), aprenderá a mejorar el desempeño en los sistemas multinúcleo mediante el uso
de las colecciones concurrentes y las operaciones de flujos paralelos de Java.
Interfaz Descripción
Collection La interfaz raíz en la jerarquía de colecciones a partir de la cual se derivan las interfaces Set, Queue
y List.
Set Una colección que no contiene duplicados.
List Una colección ordenada que puede contener elementos duplicados.
Map Una colección que asocia claves con valores y no puede contener claves duplicadas. Map no se deriva
de Collection.
Queue Por lo general es una colección del tipo primero en entrar, primero en salir, que modela a una línea
de espera; pueden especificarse otros órdenes.
Colecciones de genéricos
Para eliminar este problema, el marco de trabajo de colecciones se mejoró con las herramientas de gené-
ricos que presentamos con los objetos ArrayList genéricos en el capítulo 7 y que veremos con más
detalle en el capítulo 20. Los genéricos nos permiten especificar el tipo exacto que se almacenará en una
colección y nos dan los beneficios de la comprobación de tipos en tiempo de ejecución; el compilador emi-
te mensajes de error si se usan tipos inapropiados en las colecciones. Una vez que especifique el
tipo almacenado en una colección, cualquier referencia que obtenga de la colección tendrá ese tipo.
Esto elimina la necesidad de conversiones de tipo explícitas que pueden lanzar excepciones Class-
CastException cuando el objeto referenciado no es del tipo apropiado. Además, las colecciones de
genéricos son compatibles con versiones anteriores de código Java que se haya escrito antes de que se in-
trodujeran los genéricos.
su tarea, con base en el número de elementos de datos que se vayan a procesar. Después de leer el capítu-
lo 19 comprenderá mejor las características de rendimiento de cada colección, según lo descrito en la
documentación en línea.
En este caso, la conversión autoboxing ocurre al asignar un valor int (10) a arregloEntero[0], ya que
arregloEntero almacena referencias a objetos Integer, no valores int. La conversión auto-boxing
ocurre al asignar arregloEntero[0] a la variable int valor, ya que esta variable almacena un valor int,
no una referencia a un objeto Integer. Las conversiones boxing también ocurren en las condiciones,
que se pueden evaluar como valores boolean primitivos u objetos Boolean. Muchos de los ejemplos en
los capítulos 16 a 21 usan estas conversiones para almacenar valores primitivos en estructuras de datos
y recuperarlos de éstas.
La clase Collections proporciona métodos static que buscan, ordenan y realizan otras operaciones
sobre las colecciones. En la sección 16.7 hablaremos más acerca de los métodos de Collections. También
cubriremos los métodos de envoltura de la clase Collections, los cuales nos permiten tratar a una co-
lección como una colección sincronizada (sección 16.13) o una colección no modificable (sección 16.14).
Las colecciones sincronizadas son para usarse con la tecnología multihilos (que veremos en el capítulo 23),
la cual permite a los programas realizar operaciones en paralelo. Cuando dos o más hilos de un programa
comparten una colección, podrían ocurrir problemas. Como una breve analogía, considere una inter-
sección de tráfico. No podemos permitir que todos los automóviles accedan a una intersección al mismo
tiempo; si lo hiciéramos, ocurrirían accidentes. Por esta razón, se colocan semáforos en las interseccio-
nes para controlar el acceso a cada intersección. De manera similar, podemos sincronizar el acceso a una
colección para asegurar que sólo un subproceso a la vez manipule la colección. Los métodos de envol-
tura de sincronización de la clase Collections devuelven las versiones sincronizadas de las coleccio-
nes que pueden compartirse entre los hilos en un programa. Las colecciones no modificables son útiles
cuando un cliente de una clase necesita ver los elementos de una colección, pero no se le debe permitir
que modifique la colección, agregando y eliminando elementos.
16.6 Listas
Un objeto List (conocido como secuencia) es un objeto Collection ordenado que puede contener ele-
mentos duplicados. Al igual que los índices de arreglos, los índices de objetos List empiezan desde cero
(es decir, el índice del primer elemento es cero). Además de los métodos de interfaz heredados de
Collection, List proporciona métodos para manipular elementos a través de sus índices, para manipu-
lar un rango especificado de elementos, para buscar elementos y para obtener un objeto ListIterator
para acceder a los elementos.
La interfaz List es implementada por varias clases, incluyendo a ArrayList, LinkedList y Vector.
La conversión autoboxing ocurre cuando se agregan valores de tipo primitivo a objetos de estas clases,
ya que sólo almacenan referencias a objetos. Las clases ArrayList y Vector son implementaciones de
un objeto List como arreglos que pueden modificar su tamaño. Insertar un elemento entre los elemen-
tos existentes de un objeto ArrayList o Vector es una operación ineficiente, ya que hay que quitar del
camino a todos los elementos que van después del nuevo, lo cual podría ser una operación costosa en una
colección con una gran cantidad de elementos. Un objeto LinkedList permite la inserción (o elimina-
ción) eficiente de elementos a la mitad de una colección, pero es mucho menos eficiente que un objeto
ArrayList para saltar a un elemento específico en la colección. En el capítulo 21 hablaremos sobre la
arquitectura de las listas enlazadas.
ArrayList y Vector tienen comportamientos casi idénticos. Las operaciones en los objetos de la
clase Vector están sincronizadas de manera predeterminada, mientras que las de los objetos ArrayList no.
Además, la clase Vector es de Java 1.0, antes de que se agregara el marco de trabajo de colecciones
a Java. Como tal, Vector tiene varios métodos que no forman parte de la interfaz List y que no se
implementan en la clase ArrayList. Por ejemplo, los métodos addElement y add de Vector anexan un
elemento a un objeto Vector, pero sólo el método add está especificado en la interfaz List y se imple-
menta mediante ArrayList. Las colecciones desincronizadas proporcionan un mejor rendimiento que las
sincronizadas. Por esta razón, ArrayList se prefiere comúnmente a Vector en programas que no compar-
ten una colección entre hilos. La API de colecciones de Java proporciona envolturas de sincronización
independientes (sección 16.13) que pueden usarse para agregar sincronización a las colecciones desin-
cronizadas; además hay disponibles varias colecciones sincronizadas poderosas en las API de concurrencia
de Java.
En las siguientes tres subsecciones se demuestran las herramientas de List y Collection con varios
ejemplos. La sección 16.6.1 se enfoca en eliminar elementos de un objeto ArrayList mediante un ob-
jeto Iterator. La sección 16.6.2 se enfoca en ListIterator y varios métodos específicos de List y de
LinkedList.
Fig. 16.2 冷 Demostración de la interfaz Collection mediante un objeto ArrayList (parte 1 de 2).
ArrayList:
MAGENTA ROJO BLANCO AZUL CYAN
Fig. 16.2 冷 Demostración de la interfaz Collection mediante un objeto ArrayList (parte 2 de 2).
29 y 30 también se pudo haber usado la instrucción for mejorada (que demostraremos con las coleccio-
nes en otros ejemplos).
En la línea 33 se hace una llamada al método eliminarColores (líneas 43 a 55), y se pasan lista
y eliminarLista como argumentos. El método eliminarColores elimina los objetos String especifi-
cados en eliminarLista de los objetos String en lista. En las líneas 38 y 39 se imprimen en pantalla
los elementos de lista, una vez que eliminarColores completa su tarea.
El método eliminarColores declara dos parámetros de tipo Collection<String> (líneas 43 y 44);
pueden pasarse dos objetos Collection cualesquiera que contengan objetos String como argumentos.
El método accede a los elementos del primer objeto Collection (coleccion1) mediante un objeto
Iterator. En la línea 47 se llama al método iterator de Collection, el cual obtiene un objeto Iterator
para el objeto Collection. Las interfaces Collection e Iterator son tipos genéricos. En la condición de
continuación de ciclo (línea 50) se hace una llamada al método hasNext de Iterator para determinar
si hay más elementos por los cuales iterar. El método hasNext devuelve true si otro elemento existe, y
devuelve false en caso contrario.
La condición del if en la línea 52 llama al método next de Iterator para obtener una referencia al
siguiente elemento, y después utiliza el método contains del segundo objeto Collection (coleccion2)
para determinar si coleccion2 contiene el elemento devuelto por next. De ser así, en la línea 53 se
hace una llamada al método remove de Iterator para eliminar el elemento del objeto coleccion1 de
Collection.
En este caso, Java usa el tipo en los paréntesis angulares del lado izquierdo de la declaración (es decir,
String) mientras se crea el tipo almacenado en el objeto ArrayList del lado derecho de la declaración.
Usaremos esta sintaxis para el resto de los ejemplos en el capítulo.
16.6.2 LinkedList
En la figura 16.3 se demuestran varias operaciones con objetos LinkedList. El programa crea dos objetos
LinkedList que contienen objetos String. Los elementos de un objeto List se agregan al otro. Después,
todos los objetos String se convierten a mayúsculas, y se elimina un rango de elementos.
lista:
negro amarillo verde azul violeta plateado dorado blanco cafe azul gris plateado
lista:
NEGRO AMARILLO VERDE AZUL VIOLETA PLATEADO DORADO BLANCO CAFE AZUL GRIS PLATEADO
Lista inversa:
PLATEADO GRIS AZUL CAFE BLANCO AZUL VERDE AMARILLO NEGRO
En las líneas 14 y 22 se crean los objetos LinkedList llamados lista1 y lista2 de tipo String.
LinkedList es una clase genérica que tiene un parámetro de tipo, para el cual especificamos el argumento
de tipo String en este ejemplo. En las líneas 16 a 17 y 24 a 25 se hace una llamada al método add de List
para anexar elementos de los arreglos colores y colores2 al final de lista1 y lista2, respectivamente.
En la línea 27 se hace una llamada al método addAll de List para anexar todos los elementos de
lista2 al final de lista1. En la línea 28 se establece lista2 en null, ya que lista2 no se necesita más.
En la línea 29 se hace una llamada al método imprimirLista (líneas 41 a 49) para mostrar el contenido
de lista1. En la línea 31 se hace una llamada al método convertirCadenaAMayusculas (líneas 52 a 61)
para convertir cada elemento String a mayúsculas, y después en la línea 32 se hace una llamada nueva-
mente a imprimirLista para mostrar los objetos String modificados. En la línea 35 se hace una llamada
al método eliminarElementos (líneas 64 a 68) para eliminar el rango de elementos empezando desde
el índice 4 hasta, pero sin incluir, el índice 7 de la lista. En la línea 37 se hace una llamada al método
imprimirListaInversa (líneas 71 a 80) para imprimir la lista en orden inverso.
Método convertirCadenasAMayusculas
El método convertirCadenasAMayusculas (líneas 52 a 61) cambia los elementos String en minúscu-
las del argumento List por objetos String en mayúsculas. En la línea 54 se hace una llamada al método
listIterator de List para obtener un iterador bidireccional (es decir, un iterador que pueda recorrer
un objeto Lista hacia delante o hacia atrás) para el objeto List. ListIterator es también una clase
genérica. En este ejemplo, el objeto ListIterator hace referencia a objetos String, ya que el método
listIterator se llama en un objeto List que contiene objetos String. En la línea 56 se hace una llamada
al método hasNext para determinar si el objeto List contiene otro elemento. En la línea 58 se obtiene el
siguiente objeto String en el objeto List. En la línea 59 se hace una llamada al método toUpperCase
de String para obtener una versión en mayúsculas del objeto String y se hace una llamada al método
set de Iterator para reemplazar el objeto String actual al que hace referencia iterador con el objeto
String devuelto por el método toUpperCase. Al igual que el método toUpperCase, el método toLower-
Case de String devuelve una versión del objeto String en minúsculas.
Método eliminarElementos
El método eliminarElementos (líneas 64 a 68) elimina un rango de elementos de la lista. En la línea 67 se
hace una llamada al método subList de List para obtener una porción del objeto List (lo que se conoce
como sublista). A esto se le conoce como método de vista de rango, el cual permite al programa ver
una parte de la lista. La sublista es simplemente otra vista hacia el interior del objeto List desde el que se
hace la llamada a subList. El método subList recibe dos argumentos: el índice inicial para la sublista y
el índice final. El índice final no forma parte del rango de la sublista. En este ejemplo, la línea 35 pasa el 4
para el índice inicial y 7 para el índice final a subList. La sublista devuelta es el conjunto de elementos
con los índices 4 a 6. A continuación, el programa hace una llamada al método clear de List en la sublis-
ta para eliminar los elementos que ésta contiene del objeto List. Cualquier cambio realizado a una su-
blista se hace en el objeto List original.
Método imprimirListaInversa
El método imprimirListaInversa (líneas 71 a 80) imprime la lista al revés. En la línea 73 se hace una
llamada al método listIterator de List con un argumento que especifica la posición inicial (en nuestro
caso, el último elemento en la lista) para obtener un iterador bidireccional para la lista. El método size de
List devuelve el número de elementos en el objeto List. En la condición del ciclo while (línea 78) se hace
una llamada al método hasPrevious de ListIterator para determinar si hay más elementos mientras
se recorre la lista hacia atrás. En la línea 79 se hace una llamada al método previous de ListIterator
para obtener el elemento anterior de la lista y se envía como salida al flujo de salida estándar.
colores:
cyan
negro
azul
amarillo
verde
rojo
rosa
Fig. 16.4 冷 Ver arreglos como objetos List y convertir objetos List en arreglos.
En la línea 12 se construye un objeto LinkedList de objetos String, el cual contiene los elementos
del arreglo colores. El método asList de Arrays devuelve una vista del arreglo como un objeto List, y
después la usa para inicializar el objeto LinkedList con un constructor que recibe un objeto Collection
como argumento (un objeto List es un objeto Collection). En la línea 14 se hace una llamada al método
addLast de LinkedList para agregar “rojo” al final de enlaces. En las líneas 15 y 16 se hace una llamada
al método add de LinkedList para agregar “rosa” como el último elemento y “verde” como el elemento
en el índice 3 (es decir, el cuarto elemento). El método addLast (línea 14) es idéntico en función al méto-
do add (línea 15). En la línea 17 se hace una llamada al método addFirst de LinkedList para agregar
“cyan” como el nuevo primer elemento en el objeto LinkedList. Las operaciones add están permitidas
debido a que operan en el objeto LinkedList, no en la vista devuelta por asList. [Nota: cuando se agrega
“cyan” como el primer elemento, “verde” se convierte en el quinto elemento en el objeto LinkedList].
En la línea 20 se hace una llamada al método toArray de la interfaz List para obtener un arreglo
String de enlaces. El arreglo es una copia de los elementos de la lista, por lo que si se modifica el conte-
nido del arreglo no se modifica la lista. El arreglo que se pasa al método toArray debe ser del mismo tipo
que se desee que devuelva el método toArray. Si el número de elementos en el arreglo es mayor o igual que
el número de elementos en el objeto LinkedList, toArray copia los elementos de la lista en su argumento
tipo arreglo y devuelve ese arreglo. Si el objeto LinkedList tiene más elementos que el número de elemen-
tos en el arreglo que se pasa a toArray, este método asigna un nuevo arreglo del mismo tipo que recibe como
argumento, copia los elementos de la lista en el nuevo arreglo y devuelve este nuevo arreglo.
Método Descripción
Ordenamiento ascendente
En la figura 16.6 se utiliza el método sort de Collections para ordenar los elementos de un objeto List
en forma ascendente (línea 17). El método sort realiza un ordenamiento de combinación iterativo (en la
sección 19.8 demostramos un ordenamiento por combinación recursivo). En la línea 14 se crea lista
como un objeto List de objetos String. En cada una de las líneas 15 y 18 se utiliza una llamada implícita
al método toString de lista para imprimir el contenido de la lista en el formato que se muestra en los
resultados.
Ordenamiento descendente
En la figura 16.7 se ordena la misma lista de cadenas utilizadas en la figura 16.6, en orden descendente. El
ejemplo introduce la interfaz Comparator, la cual se utiliza para ordenar los elementos de un objeto
Collection en un orden distinto. En la línea 18 se hace una llamada al método sort de Collections para
ordenar el objeto List en orden descendente. El método static reverseOrder de Collections devuel-
ve un objeto Comparator que ordena los elementos de la colección en forma inversa.
Fig. 16.8 冷 Clase Comparator personalizada que compara dos objetos Tiempo2 (parte 1 de 2).
14
15 int diferenciaMinuto = tiempo1.obtenerMinuto() - tiempo2.obtenerMinuto();
16
17 if (diferenciaMinuto != 0) // después evalúa el minuto
18 return diferenciaMinuto;
19
20 int diferenciaSegundo = tiempo1.obtenerSegundo() - tiempo2.obtenerSegundo();
21 return diferenciaSegundo;
22 }
23 } // fin de la clase ComparadorTiempo
Fig. 16.8 冷 Clase Comparator personalizada que compara dos objetos Tiempo2 (parte 2 de 2).
Fig. 16.9 冷 El método sort de Collections con un objeto Comparator personalizado (parte 1 de 2).
Fig. 16.9 冷 El método sort de Collections con un objeto Comparator personalizado (parte 2 de 2).
Fig. 16.10 冷 Barajar y repartir cartas con el método shuffle de Collections (parte 1 de 3).
27 return cara;
28 }
29
30 // devuelve el palo de la Carta
31 public Palo obtenerPalo()
32 {
33 return palo;
34 }
35
36 // devuelve la representación String de la Carta
37 public String toString()
38 {
39 return String.format(“%s of %s”, cara, palo);
40 }
41 } // fin de la clase Carta
42
43 // declaración de la clase MazoDeCartas
44 public class MazoDeCartas
45 {
46 private List<Carta> lista; // declara objeto List que almacenará los objetos Carta
47
48 // establece mazo de objetos Carta y baraja
49 public MazoDeCartas()
50 {
51 Carta[] mazo = new Carta[52];
52 int cuenta = 0; // número de cartas
53
54 // llena el mazo con objetos Carta
55 for (Carta.Palo palo: Carta.Palo.values())
56 {
57 for (Carta.Cara cara : Carta.Cara.values())
58 {
59 mazo[cuenta] = new Carta(cara, palo);
60 ++cuenta;
61 }
62 }
63
64 lista = Arrays.asList(mazo); // obtiene objeto List
65 Collections.shuffle(lista); // baraja el mazo
66 } // fin del constructor de MazoDeCartas
67
68 // imprime el mazo
69 public void imprimirCartas()
70 {
71 // muestra las 52 cartas en dos columnas
72 for (int i = 0; i < lista.size(); i++)
73 System.out.printf(“%-19s%s”, lista.get(i),
74 ((i + 1) % 4 == 0) ? “%n” : “”);
75 }
76
77 public static void main(String[] args)
78 {
Fig. 16.10 冷 Barajar y repartir cartas con el método shuffle de Collections (parte 2 de 3).
Fig. 16.10 冷 Barajar y repartir cartas con el método shuffle de Collections (parte 3 de 3).
La clase Carta (líneas 8 a 41) representa a una carta en un mazo de cartas. Cada Carta tiene una cara
y un palo. Las líneas 10 a 12 declaran dos tipos enum (Cara y Palo) que representan la cara y el palo de la
carta, respectivamente. El método toString (líneas 37 a 40) devuelve un objeto String que contiene
la cara y el palo de la Carta, separados por la cadena “ de ”. Cuando una constante enum se convierte en
una cadena, el identificador de la constante se utiliza como la representación de String. Por lo general,
utilizamos letras mayúsculas para las constantes enum. En este ejemplo, optamos por usar letras ma-
yúsculas sólo para la primera letra de cada constante enum, porque queremos que la carta se muestre con
letras iniciales mayúsculas para la cara y el palo (por ejemplo, “As de Bastos”).
En las líneas 55 a 62 se llena el arreglo mazo con cartas que tienen combinaciones únicas de cara y
palo. Tanto Cara como Palo son tipos public static enum de la clase Carta. Para usar estos tipos enum
fuera de la clase Carta, debe calificar el nombre de cada tipo enum con el nombre de la clase en la que
reside (es decir, Carta) y un separador punto (.). Así, en las líneas 55 y 57 se utilizan Carta.Palo y
Carta.Cara para declarar las variables de control de las instrucciones for. Recuerde que el método
values de un tipo enum devuelve un arreglo que contiene todas las constantes del tipo enum. En las
líneas 55 a 62 se utilizan instrucciones for mejoradas para construir 52 nuevos objetos Carta.
La acción de barajar las cartas ocurre en la línea 65, en la cual se hace una llamada al método static
shuffle de la clase Collections para barajar los elementos del arreglo. El método shuffle requiere
un argumento List, por lo que debemos obtener una vista List del arreglo antes de poder barajarlo.
En la línea 64 se invoca el método static asList de la clase Arrays para obtener una vista List del
arreglo mazo.
El método imprimirCartas (líneas 69 a 75) muestra el mazo de cartas en dos columnas. En cada
iteración del ciclo (las líneas 73 y 74) se imprime una carta justificada a la izquierda, en un campo de
19 caracteres seguido de una nueva línea o de una cadena vacía, con base en el número de cartas mos-
tradas hasta ese momento. Si el número de cartas es un múltiplo de 4, se imprime una nueva línea;
en caso contrario, se imprime un tabulador.
lizar un objeto List. El método copy recibe dos argumentos: un objeto List de destino y un objeto List
de origen. Cada elemento del objeto List de origen se copia al objeto List de destino. El objeto
List de destino debe tener cuando menos la misma longitud que el objeto List de origen; de lo contra-
rio, se producirá una excepción IndexOutOfBoundsException. Si el objeto List de destino es más
largo, los elementos que no se sobrescriban permanecerán sin cambio.
Cada uno de los métodos que hemos visto hasta ahora opera en objetos List. Los métodos min y max
operan en cualquier objeto Collection. El método min devuelve el elemento más pequeño en un objeto
Collection y el método max devuelve el elemento más grande en un objeto Collection. Ambos méto-
dos pueden llamarse con un objeto Comparator como segundo argumento para realizar comparaciones
personalizadas entre objetos, como el objeto ComparadorTiempo en la figura 16.9. En la figura 16.11 se
demuestra el uso de los métodos reverse, fill, copy, max y min.
Fig. 16.11 冷 Los métodos reverse, fill, copy, max y min de Collections (parte 1 de 2).
Lista inicial:
La lista es: P C M
Max: P Min: C
Después de copy:
La lista es: M C P
Max: P Min: C
Fig. 16.11 冷 Los métodos reverse, fill, copy, max y min de Collections (parte 2 de 2).
En la línea 13 se crea la variable lista de tipo List<Character> y se inicializa con una vista List del
arreglo letras tipo Character. En las líneas 14 y 15 se imprime en pantalla el contenido actual del objeto
List. En la línea 18 se hace una llamada al método reverse de Collections para invertir el orden
de lista. El método reverse recibe un argumento List. Como lista es una vista List del arreglo
letras, los elementos del arreglo están ahora en orden inverso. El contenido inverso se imprime en
pantalla en las líneas 19 y 20. En la línea 27 se copian los elementos de lista en copiaLista, usando
el método copy de Collections. Los cambios a copiaLista no cambian a letras, ya que copiaLista
es un objeto List independiente que no es una vista List del arreglo letras. El método copy requiere
dos argumentos List: el objeto List de destino y el de origen. En la línea 32 se hace una llamada al
método fill de Collections para colocar el caracter ‘R’ en cada elemento de lista. Como lista es
una vista List del arreglo letras, esta operación cambia cada elemento en letras a ‘R’. El método fill
requiere un objeto List como primer argumento, y un objeto Object como segundo argumento. En este
caso, el objeto Object es la versión embalada del carácter ‘R’. En las líneas 45 y 46 se hace una llamada
a los métodos max y min de Collections para buscar el elemento más grande y más pequeño de la colec-
ción, respectivamente. Recuerde que la interfaz List extiende a la interfaz Collection, por lo que un
objeto List es un objeto Collection.
calculando primero el punto de inserción y cambiando el signo del punto de inserción a negativo.
Después, binarySearch resta 1 al punto de inserción para obtener el valor de retorno, el cual garantiza
que el método binarySearch devolverá números positivos (>= 0), sí y sólo si se encuentra el objeto.
Si varios elementos en la lista coinciden con la clave de búsqueda, no hay garantía de que uno se localice
primero. En la figura 16.12 se utiliza el método binarySearch para buscar una serie de cadenas en un
objeto ArrayList.
ArrayList ordenado: [amarillo, azul, blanco, carne, morado, negro, rojo, rosa]
Buscando: negro
Se encontro en el indice 5
Buscando: rojo
Se encontro en el indice 6
Buscando: rosa
Se encontro en el indice 7
Buscando: aqua
No se encontro (-2)
Buscando: gris
No se encontro (-5)
Buscando: verdeazulado
No se encontro (-9)
En las líneas 15 y 16 se inicializa lista con un ArrayList que contiene una copia de los elementos
en el arreglo colores. El método binarySearch de Collections espera que los elementos del argumen-
to List estén en orden ascendente, por lo que la línea 18 se usa el método sort de Collections para or-
denar la lista. Si los elementos del argumento List no están ordenados, el resultado de usar binarySearch
es indefinido. En la línea 19 se imprime la lista ordenada en la pantalla. En las líneas 22 al 27 se hacen
llamadas al método imprimirResultadosBusqueda (líneas 31 a 43) para realizar la búsqueda e imprimir
los resultados en pantalla. En la línea 37 se hace una llamada al método binarySearch de Collections
para buscar en lista la clave especificada. El método binarySearch recibe un objeto List como primer
argumento, y un objeto Object como segundo argumento. En las líneas 39 a 42 se imprimen en pantalla
los resultados de la búsqueda. Una versión sobrecargada de binarySearch recibe un objeto Comparator
como tercer argumento, el cual especifica la forma en que binarySearch debe comparar la clave de
búsqueda con los elementos del objeto List.
Fig. 16.13 冷 Los métodos addAll, frequency y disjoint de Collections (parte 1 de 2).
7
8 public class Algoritmos2
9 {
10 public static void main(String[] args)
11 {
12 // inicializa lista1 y lista2
13 String[] colores = {“rojo”, “blanco”, “amarillo”, “azul”};
14 List<String> lista1 = Arrays.asList(colores);
15 ArrayList<String> lista2 = new ArrayList<>();
16
17 lista2.add(“negro”); // agrega “negro” al final de lista2
18 lista2.add(“rojo”); // agrega “rojo” al final de lista2
19 lista2.add(“verde”); // agrega “verde” al final de lista2
20
21 System.out.print(“Antes de addAll, lista2 contiene: “);
22
23 // muestra los elementos en lista2
24 for (String s : lista2)
25 System.out.printf(“%s “, s);
26
27 Collections.addAll(lista2, colores); // agrega los objetos String de colores
a lista2
28
29 System.out.printf(“%nDespues de addAll, lista2 contiene: “);
30
31 // muestra los elementos en lista2
32 for (String s : lista2)
33 System.out.printf(“%s “, s);
34
35 // obtiene la frecuencia de “rojo”
36 int frecuencia = Collections.frequency(lista2, “rojo”);
37 System.out.printf(
38 “%nFrecuencia de rojo en lista2: %d%n”, frecuencia);
39
40 // comprueba si lista1 y lista2 tienen elementos en común
41 boolean desunion = Collections.disjoint(lista1, lista2);
42
43 System.out.printf(“lista1 y lista2 %s elementos en comun%n”,
44 (desunion ? “no tienen” : “tienen”));
45 }
46 } // fin de la clase Algoritmos2
Fig. 16.13 冷 Los métodos addAll, frequency y disjoint de Collections (parte 2 de 2).
En la línea 14 se inicializa lista1 con los elementos en el arreglo colores, y en las líneas 17 a 19
se agregan los objetos String “negro”, “rojo” y “verde” a lista2. En la línea 27 se invoca el método
addAll para agregar los elementos en el arreglo colores a lista2. En la línea 36 se obtiene la frecuen-
cia del objeto String “rojo” en lista2, usando el método frequency. En la línea 41 se invoca el método
disjoint para evaluar si los objetos Collections lista1 y lista2 tienen elementos en común, lo cual
es cierto en este ejemplo.
41 emptyStackException.printStackTrace();
42 }
43 }
44
45 // muestra el contenido de Pila
46 private static void imprimirPila(Stack<Number> pila)
47 {
48 if (pila.isEmpty())
49 System.out.printf(“la pila esta vacia%n%n”); // la pila está vacía
50 else // la pila no está vacía
51 System.out.printf(“la pila contiene: %s (cima)%n”, pila);
52 }
53 } // fin de la clase PruebaStack
Se metio 12L
la pila contiene: [12] (cima)
Se metio 34567
la pila contiene: [12, 34567] (cima)
Se metio 1.0F
la pila contiene: [12, 34567, 1.0] (cima)
Se metio 1234.5678
la pila contiene: [12, 34567, 1.0, 1234.5678] (cima)
Se saco 1234.5678
la pila contiene: [12, 34567, 1.0] (cima)
Se saco 1.0
la pila contiene: [12, 34567] (cima)
Se saco 34567
la pila contiene: [12] (cima)
Se saco 12
La pila esta vacia
java.util.EmptyStackException
at java.util.Stack.peek(Unknown Source)
at java.util.Stack.pop(Unknown Source)
at PruebaStack.main(PruebaStack.java:34)
En la línea 10 del constructor se crea un objeto Stack vacío de tipo Number. La clase Number (en el
paquete java.lang) es la superclase de la mayoría de las clases de envoltura (como Integer, Double) para
los tipos primitivos. Al crear un objeto Stack de objetos Number, se pueden meter en la pila objetos de
cualquier clase que extienda a la clase Number. En cada una de las líneas 13, 16, 19 y 22 se hace una llamada
al método push de Stack para agregar objetos Number a la cima de la pila. Observe las literales 12L (línea
13) y 1.0F (línea 19). Cualquier literal entera que tenga el sufijo L es un valor long. Cualquier literal
entera sin un sufijo es un valor int. De manera similar, cualquier literal de punto flotante que tenga el
sufijo F es un valor float. Una literal de punto flotante sin un sufijo es un valor double. Puede aprender
más acerca de las literales numéricas en la Especificación del lenguaje Java, en el sitio web http://docs.
oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.8.1.
Un ciclo infinito (líneas 32 a 37) llama al método pop de Stack para eliminar el elemento superior
de la pila. El método devuelve una referencia Number al elemento eliminado. Si no hay elementos en
el objeto Stack, el método pop lanza una excepción EmptyStackException, la cual termina el ciclo.
La clase Stack también declara el método peek. Este método devuelve el elemento superior de la pila
sin sacarlo.
El método imprimirPila (líneas 46 a 52) muestra el contenido de la pila. La cima actual de la pila
(el último valor que se metió a ésta) es el primer valor que se imprime. En la línea 48 se hace una llama-
da al método isEmpty de Stack (heredado por Stack de la clase Vector) para determinar si la pila está
vacía. Si está vacía, el método devuelve true; en caso contrario, devuelve false.
16.10 Conjuntos
Un objeto Set es un objeto Collection desordenado que contiene elementos únicos (es decir, sin elementos
duplicados). El marco de trabajo de colecciones contiene varias implementaciones de Set, incluyendo a
HashSet y TreeSet. HashSet almacena sus elementos en una tabla de hash, y TreeSet almacena sus ele-
mentos en un árbol. El concepto de las tablas de hash se presenta en la sección 16.11. Hablaremos sobre
los árboles en la sección 21.7.
En la figura 16.16 se utiliza un objeto HashSet para eliminar las cadenas duplicadas de un objeto
List. Recuerde que tanto List como Collection son tipos genéricos, por lo que en la línea 16 se crea
un objeto List que contiene objetos String, y en la línea 20 se pasa un objeto Collection de objetos
String al método imprimirSinDuplicados. El método imprimirSinDuplicados (líneas 24 a 35) recibe
un argumento Collection. En la línea 27 se crea un objeto HashSet<String> a partir del argumento
Collection<String>. Por definición, los objetos Set no contienen valores duplicados, por lo que cuan-
do se construye el objeto HashSet, éste elimina cualquier valor duplicado en el objeto Collection. En las
líneas 31 y 32 se imprimen en pantalla los elementos en el objeto Set.
Fig. 16.16 冷 Uso de un objeto HashSet para eliminar valores duplicados de un arreglo de cadenas (parte 1 de 2).
5 import java.util.HashSet;
6 import java.util.Set;
7 import java.util.Collection;
8
9 public class PruebaSet
10 {
11 public static void main(String[] args)
12 {
13 // crea y muestra un objeto List<String>
14 String[] colores = {“rojo”, “blanco”, “azul”, “verde”, “gris”,
15 “naranja”, “carne”, “blanco”, “cyan”, “durazno”, “gris”, “naranja”};
16 List<String> lista = Arrays.asList(colores);
17 System.out.printf(“List: %s%n”, lista);
18
19 // elimina duplicados y luego imprime los valores únicos
20 imprimirSinDuplicados(lista);
21 }
22
23 // crea un objeto Set a partir de un objeto Collection para eliminar duplicados
24 private static void imprimirSinDuplicados(Collection<String> valores)
25 {
26 // crea un objeto HashSet
27 Set<String> conjunto = new HashSet<>(valores);
28
29 System.out.printf(“%nLos valores sin duplicados son: “);
30
31 for (String valor : conjunto)
32 System.out.printf(“%s “, valor);
33
34 System.out.println();
35 }
36 } // fin de la clase PruebaSet
List: [rojo, blanco, azul, verde, gris, naranja, carne, blanco, cyan, durazno, gris,
naranja]
Los valores sin duplicados son: durazno gris verde azul blanco rojo cyan carne naranja
Fig. 16.16 冷 Uso de un objeto HashSet para eliminar valores duplicados de un arreglo de cadenas (parte 2 de 2).
Conjuntos ordenados
El marco de trabajo de colecciones también incluye la interfaz SortedSet (que extiende a Set) para los
conjuntos que mantengan a sus elementos ordenados; ya sea en el orden natural de los elementos (por
ejemplo, los números se encuentran en orden ascendente) o en un orden especificado por un objeto
Comparator. La clase TreeSet implementa a SortedSet. El programa de la figura 16.17 coloca obje-
tos String en un objeto TreeSet. Estos objetos String se ordenan al ser agregadas al objeto TreeSet.
Este ejemplo también demuestra los métodos de vista de rango, los cuales permiten a un programa ver
una porción de una colección.
En la línea 14 se crea un objeto TreeSet<String> que contiene los elementos del arreglo colores,
y luego se asigna el nuevo objeto TreeSet<String> a la variable arbol Sorted<String>. En la línea 17
se imprime en pantalla el conjunto inicial de cadenas, utilizando el método imprimirConjunto (líneas
33 a 39), sobre el cual hablaremos en breve. En la línea 31 se hace una llamada al método headSet de
TreeSet para obtener un subconjunto del objeto TreeSet, en el que todos los elementos serán menores
conjunto ordenado: amarillo blanco carne gris naranja negro rojo verde
headSet (“naranja”): amarillo blanco carne gris
tailSet (“naranja”): naranja negro rojo verde
primero: amarillo
ultimo : verde
16.11 Mapas
Los objetos Map asocian claves a valores. Las claves en un objeto Map deben ser únicas, pero los valores
asociados no. Si un objeto Map contiene claves y valores únicos, se dice que implementa una asociación
de uno a uno. Si sólo las claves son únicas, se dice que el objeto Map implementa una asociación de varios
a uno; muchas claves pueden asociarse a un solo valor.
Los objetos Map difieren de los objetos Set en tanto que los primeros contienen claves y valores,
mientras que los segundos contienen solamente valores. Tres de las muchas clases que implementan a la
interfaz Map son Hashtable, HashMap y TreeMap. Los objetos Hashtable y HashMap almacenan elemen-
tos en tablas de hash, y los objetos TreeMap almacenan elementos en árboles. En esta sección veremos
las tablas de hash y proporcionaremos un ejemplo en el que se utiliza un objeto HashMap para almacenar
pares clave-valor. La interfaz SortedMap extiende a Map y mantiene sus claves en orden; ya sea el orden
natural de los elementos o un orden especificado por un objeto Comparator. La clase TreeMap imple-
menta a SortedMap.
http://www.socialsecurity.gov/employer/randomization.html
Esto es impráctico para casi todas las aplicaciones que utilizan números de seguro social como claves.
Un programa que tuviera un arreglo de ese tamaño podría lograr un alto rendimiento para almacenar
y recuperar registros de empleados con sólo usar el número de seguro social como índice del arreglo.
Hay muchas aplicaciones con este problema; entre otras, que las claves son del tipo incorrecto (por
ejemplo, enteros no positivos que corresponden a los subíndices del arreglo) o que son del tipo correcto,
pero se esparcen escasamente sobre un enorme rango. Lo que se necesita es un esquema de alta velocidad
para convertir claves, como números de seguro social, números de piezas de inventario y demás, en índi-
ces únicos de arreglo. Así, cuando una aplicación necesite almacenar algo, el esquema podría convertir
rápidamente la clave de la aplicación en un índice, y el registro podría almacenarse en esa posición del
arreglo. Para recuperar datos se hace lo mismo: una vez que la aplicación tenga una clave para la que
desee obtener un registro de datos, simplemente aplica la conversión a la clave; esto produce el índice del
arreglo en el que se almacenan y obtienen los datos.
El esquema que describimos aquí es la base de una técnica conocida como hashing. ¿Por qué ese
nombre? Al convertir una clave en un índice de arreglo, literalmente revolvemos los bits, formando un
tipo de número “desordenado”. En realidad, el número no tiene un significado real más allá de su utilidad
para almacenar y obtener un registro de datos específico.
Un fallo en este esquema se denomina colisión; esto ocurre cuando dos claves distintas se asocian a la
misma celda (o elemento) en el arreglo. No podemos almacenar dos valores en el mismo espacio, por lo que
necesitamos encontrar un hogar alternativo para todos los valores más allá del primero, que se asocie con
un índice de arreglo específico. Hay muchos esquemas para hacer esto. Uno de ellos es “hacer hash de
nuevo” (es decir, aplicar otra transformación de hashing a la clave para proporcionar la siguiente celda como
candidato en el arreglo). El proceso de hashing está diseñado para distribuir los valores en toda la tabla, por
lo que se asume que se encontrará una celda disponible con sólo unas cuantas transformaciones de hashing.
Otro esquema utiliza un hash para localizar la primera celda candidata. Si esa celda está ocupada,
se buscan celdas sucesivas en orden, hasta que se encuentra una disponible. El proceso de recuperación de
datos funciona de la misma forma: se aplica hash a la clave una vez para determinar la función inicial
y comprobar si contiene los datos deseados. Si es así, la búsqueda termina. En caso contrario, se busca
linealmente en las celdas sucesivas hasta encontrar los datos deseados.
La solución más popular a las colisiones en las tablas de hash es hacer que cada celda de la tabla sea
una “cubeta” de hash, que por lo general viene siendo una lista enlazada de todos los pares clave/valor
que se asocian con esa celda. Ésta es la solución que implementan las clases Hashtable y HashMap (del
paquete java.util). Tanto Hashtable como HashMap implementan a la interfaz Map. Las principales
diferencias entre ellas son que HashMap no está sincronizada (varios subprocesos no deben modificar un
objeto HashMap en forma concurrente); además permite claves y valores null.
El factor de carga de una tabla de hash afecta al rendimiento de los esquemas de hashing. El factor de
carga es la proporción del número de celdas ocupadas en la tabla de hash, con respecto al número total
de celdas en la tabla. Entre más se acerque esta proporción a 1.0, mayor será la probabilidad de colisiones.
Los estudiantes de ciencias computacionales estudian los esquemas de hashing en cursos titulados
“Estructuras de datos” y “Algoritmos”. Las clases Hashtable y HashMap permiten a los programadores
utilizar la técnica de hashing sin tener que implementar los mecanismos de las tablas de hash: un clásico
ejemplo de reutilización. Este concepto es muy importante en nuestro estudio de la programación orien-
tada a objetos. Como vimos en capítulos anteriores, las clases encapsulan y ocultan la complejidad (es decir,
los detalles de implementación) y ofrecen interfaces amigables para el usuario. La construcción apropiada
de clases para exhibir tal comportamiento es una de las habilidades más valiosas en el campo de la progra-
mación orientada a objetos. En la figura 16.18 se utiliza un objeto HashMap para contar el número de
ocurrencias de cada palabra en una cadena.
Fig. 16.18 冷 Programa que cuenta el número de ocurrencias de cada palabra en un objeto String (parte 1 de 3).
7 import java.util.Scanner;
8
9 public class ConteoTipoPalabras
10 {
11 public static void main(String[] args)
12 {
13 // crea HashMap para almacenar claves String y valores Integer
14 Map<String, Integer> miMap = new HashMap<>();
15
16 crearMap(miMap); // crea un mapa con base en la entrada del usuario
17 mostrarMap(miMap); // muestra el contenido del mapa
18 }
19
20 // crea mapa a partir de la entrada del usuario
21 private static void crearMap(Map<String, Integer> mapa)
22 {
23 Scanner scanner = new Scanner(System.in); // crea scanner
24 System.out.println(“Escriba una cadena:”); // pide la entrada del usuario
25 String entrada = scanner.nextLine();
26
27 // divide la entrada en tokens
28 String[] tokens = entrada.split(“ “);
29
30 // procesamiento del texto de entrada
31 for (String token : tokens)
32 {
33 String palabra = token.toLowerCase(); // obtiene una palabra en minúsculas
34
35 // si el mapa contiene la palabra
36 if (mapa.containsKey(palabra)) // ¿está la palabra en el mapa?
37 {
38 int cuenta = mapa.get(palabra); // obtiene la cuenta actual
39 mapa.put(palabra, cuenta + 1); // incrementa la cuenta
40 }
41 else
42 mapa.put(palabra, 1); // agrega una nueva palabra con una cuenta de 1 al mapa
43 }
44 }
45
46 // muestra el contenido del mapa
47 private static void mostrarMap(Map<String, Integer> mapa)
48 {
49 Set<String> claves = mapa.keySet(); // obtiene las claves
50
51 // ordena las claves
52 TreeSet<String> clavesOrdenadas = new TreeSet<>(claves);
53
54 System.out.printf(“%nEl mapa contiene:%nClave/t/tValor%n”);
55
56 // genera la salida para cada clave en el mapa
57 for (String clave : clavesOrdenadas)
58 System.out.printf(“%-10s%10s%n”, clave, mapa.get(clave));
59
Fig. 16.18 冷 Programa que cuenta el número de ocurrencias de cada palabra en un objeto String (parte 2 de 3).
60 System.out.printf(
61 “%nsize: %d%nisEmpty: %b%n”, mapa.size(), mapa.isEmpty());
62 }
63 } // fin de la clase ConteoTipoPalabras
El mapa contiene:
Clave Valor
es 2
esa 1
la 1
mas 1
no 1
noble 1
o 1
pregunta 1
ser 1
si 1
sufrir 1
size: 12
isEmpty: false
Fig. 16.18 冷 Programa que cuenta el número de ocurrencias de cada palabra en un objeto String (parte 3 de 3).
En la línea 14 se crea un objeto HashMap vacío con una capacidad inicial predeterminada (16 ele-
mentos) y un factor de carga predeterminado (0.75); estos valores predeterminados están integrados
en la implementación de HashMap. Cuando el número de posiciones ocupadas en el objeto HashMap
se vuelve mayor que la capacidad multiplicada por el factor de carga, la capacidad se duplica en
forma automática. HashMap es una clase genérica que recibe dos argumentos: el tipo de clave (es de-
cir, String) y el tipo de valor (es decir, Integer). Recuerde que los argumentos de tipo que se pasan a
una clase genérica deben ser tipos de referencias, por lo cual el segundo argumento de tipo es Integer,
no int.
En la línea 16 se hace una llamada al método crearMap (líneas 21 a 44), el cual usa un objeto
Map para almacenar el número de ocurrencias de cada palabra en la oración. En la línea 25 se obtiene
la entrada del usuario y en la línea 28 se descompone en tokens. En las líneas 31 a 43 se convierte el si-
guiente token en minúsculas (línea 33) y luego se hace una llamada al método containsKey de Map
(línea 36) para determinar si la palabra está en el mapa (y por ende, que ha ocurrido antes en la ca-
dena). Si el objeto Map no contiene la palabra, en la línea 42 se utiliza el método put de Map para crear
una nueva entrada en el mapa, con la palabra como la clave y un objeto Integer que contiene 1 como
valor. La conversión autoboxing ocurre cuando el programa pasa el entero 1 al método put, ya que
el mapa almacena el número de ocurrencias de la palabra como un objeto Integer. Si la palabra no
existe en el mapa, en la línea 38 se utiliza el método get de Map para obtener el valor asociado de la
clave (la cuenta) en el mapa. En la línea 39 se incrementa ese valor y se utiliza put para reemplazar el
valor asociado de la clave. El método put devuelve el valor anterior asociado con la clave, o null si la
clave no estaba en el mapa.
El método mostrarMap (líneas 47 a 62) muestra todas las entradas en el mapa. Utiliza el método key-
Set de HashMap (línea 49) para obtener un conjunto de las claves. Estas claves tienen el tipo String en el
mapa, por lo que el método keySet devuelve un tipo genérico Set con el parámetro de tipo especificado
como String. En la línea 52 se crea un objeto TreeSet de las claves, en el cual se ordenan éstas. El ciclo en
las líneas 57 a 58 accede a cada clave y a su valor en el mapa. En la línea 58 se muestra cada clave y su valor,
usando el especificador de formato %-10s para alinear cada clave a la izquierda, y el especificador de forma-
to %10s para alinear cada valor a la derecha. Las claves se muestran en orden ascendente. En la línea 61 se
hace una llamada al método size de Map para obtener el número de pares clave-valor en el objeto Map. En
la línea 61 se hace una llamada al método isEmpty de Map, el cual devuelve un valor boolean que indica si
el objeto Map está vacío o no.
21
22 // reemplaza el valor de una propiedad
23 tabla.setProperty(“color”, “rojo”);
24
25 System.out.println(“Despues de reemplazar propiedades”);
26 listarPropiedades(tabla);
27
28 guardarPropiedades(tabla);
29
30 tabla.clear(); // vacía la tabla
31
32 System.out.println(“Despues de borrar propiedades”);
33 listarPropiedades(tabla);
34
35 cargarPropiedades(tabla);
36
37 // obtiene el valor de la propiedad color
38 Object valor = tabla.getProperty(“color”);
39
40 // comprueba si el valor está en la tabla
41 if (valor != null)
42 System.out.printf(“El valor de la propiedad color es %s%n”, valor);
43 else
44 System.out.println(“La propiedad color no está en la tabla”);
45 }
46
47 // guarda las propiedades en un archivo
48 private static void guardarPropiedades(Properties props)
49 {
50 // guarda el contenido de la tabla
51 try
52 {
53 FileOutputStream salida = new FileOutputStream(“props.dat”);
54 props.store(salida, “Propiedades de ejemplo”); // guarda las propiedades
55 salida.close();
56 System.out.println(“Despues de guardar las propiedades”);
57 listarPropiedades(props);
58 }
59 catch (IOException ioException)
60 {
61 ioException.printStackTrace();
62 }
63 }
64
65 // carga las propiedades de un archivo
66 private static void cargarPropiedades(Properties props)
67 {
68 // carga el contenido de la tabla
69 try
70 {
71 FileInputStream entrada = new FileInputStream(“props.dat”);
72 props.load(entrada); // carga las propiedades
73 entrada.close();
En la línea 38 se llama al método getProperty de Properties para localizar el valor asociado con
la clave especificada. Si la clave no se encuentra en este objeto Properties, getProperty devuelve null.
Una versión sobrecargada de este método recibe un segundo argumento, el cual especifica el valor prede-
terminado a devolver si getProperty no puede localizar la clave.
En la línea 54 se hace una llamada al método store de Properties para guardar el contenido del
objeto Properties en el objeto OutputStream especificado como el primer argumento (en este caso,
un objeto FileOutputStream). El segundo argumento, un objeto String, es una descripción que se es-
cribe en el archivo. El método list de la clase Properties, que recibe un argumento PrintStream, es
útil para mostrar la lista de propiedades.
En la línea 72 se hace una llamada al método load de Properties para restaurar el contenido del
objeto Properties a partir del objeto InputStream especificado como el primer argumento (en este caso,
un objeto FileInputStream). En la línea 86 se hace una llamada al método keySet de Properties para
obtener un objeto Set de los nombres de las propiedades. Puesto que la clase Properties almacena su
contenido en forma de objetos Object, se devuelve un objeto Set de referencias Object. En la línea 91 se
obtiene el valor de una propiedad, para lo cual se pasa una clave al método getProperty.
genérico. Por ejemplo, el siguiente código crea un objeto List no modificable (lista2) que almacena
objetos String:
16.16 Conclusión
En este capítulo se presentó el marco de trabajo de colecciones de Java. Conoció la jerarquía de colec-
ciones y aprendió a utilizar las interfaces del marco de trabajo de colecciones para programar con las
colecciones mediante el polimorfismo. Usó las clases ArrayList y LinkedList, las cuales implementan
a la interfaz List. Presentamos las interfaces y clases integradas de Java para manipular pilas y colas.
Usó varios métodos predefinidos para manipular colecciones. Aprendió a usar la interfaz Set y la clase
HashSet para manipular una colección desordenada de valores únicos. Continuamos nuestra presen-
tación de los conjuntos con la interfaz SortedSet y la clase TreeSet para manipular una colección
ordenada de valores únicos. Luego aprendió sobre las interfaces y clases de Java para manipular pares
clave-valor: Map, SortedMap, Hashtable, HashMap y TreeMap. Hablamos sobre la clase Properties espe-
cializada para manipular pares clave-valor de objetos String que pueden almacenarse en un archivo y
recuperarse de un archivo. Por último, hablamos sobre los métodos static de la clase Collections para
obtener vistas no modificables y sincronizadas de colecciones. Si desea información adicional sobre el
marco de trabajo de colecciones, visite http://docs.oracle.com/javase/7/docs/technotes/guides/
collections. En el capítulo 17, Lambdas y flujos de Java SE 8, usará las nuevas herramientas de pro-
gramación funcional de Java SE 8 para simplificar las operaciones con colecciones. En el capítulo 23
(en inglés, en el sitio web del libro) aprenderá a mejorar el rendimiento en los sistemas multinúcleo
mediante las colecciones concurrentes y las operaciones de flujos en paralelo de Java.
Resumen
Sección 16.1 Introducción
• El marco de trabajo de colecciones de Java proporciona acceso a las estructuras de datos prefabricadas, así como a
los métodos para manipularlas.
• El método subList (pág. 694) devuelve una vista de una porción de un objeto List. Cualquier modificación rea-
lizada en esta vista se realiza también en el objeto List.
Ejercicios de autoevaluación
16.1 Complete las siguientes oraciones:
a) Un objeto ______ se utiliza para iterar a través de una colección y puede eliminar elementos de la
colección durante la iteración.
b) Para acceder a un elemento en un objeto List, se utiliza el ______ del elemento.
c) Suponiendo que miArreglo contenga referencias a objetos Double, ______ ocurre cuando se ejecuta la
instrucción “miArreglo[0] = 1.25;”.
d) Las clases ______ y ______ de Java proporcionan las herramientas de estructuras de datos tipo arreglo,
que pueden cambiar su tamaño en forma dinámica.
e) Si usted no especifica un incremento de capacidad, el sistema ______ el tamaño del objeto Vector cada
vez que se requiere una capacidad adicional.
f ) Puede utilizar un ______ para crear una colección que ofrezca acceso de sólo lectura a los demás, mien-
tras que a usted le permita el acceso de lectura/escritura.
g) Suponiendo que miArreglo contenga referencias a objetos Double, ______ ocurre cuando se ejecuta la
instrucción “double numero = miArreglo[0];”.
h) El algoritmo ______ de Collections determina si dos colecciones tienen elementos en común.
16.2 Conteste con verdadero o falso a cada una de las siguientes proposiciones; en caso de ser falso, explique
por qué.
a) Los valores de tipos primitivos pueden almacenarse directamente en una colección.
b) Un objeto Set puede contener valores duplicados.
16.2 a) Falso. La conversión autoboxing ocurre cuando se agrega un tipo primitivo a una colección, lo cual sig-
nifica que el tipo primitivo se convierte en su clase de envoltura de tipo correspondiente.
b) Falso. Un objeto Set no puede contener valores duplicados.
c) Falso. Un objeto Map no puede contener claves duplicadas.
d) Verdadero.
e) Falso. Collections es una clase; Collection es una interfaz (interface).
f ) Verdadero.
g) Falso. A medida que aumenta el factor de carga, hay menos posiciones disponibles, relativas al número
total de posiciones, por lo que la probabilidad de una colisión se incrementa.
h) Falso. Al tratar de insertar un elemento null se produce una excepción NullPointerException.
Ejercicios
16.3 Defina cada uno de los siguientes términos:
a) Collection
b) Colecciones
c) Comparator
d) List
e) factor de carga
f ) colisión
g) concesión entre espacio y tiempo en hashing
h) HashMap
16.4 Explique brevemente la operación de cada uno de los siguientes métodos de la clase Vector:
a) add
b) set
c) remove
d) removeAllElements
e) removeElementAt
f ) firstElement
g) lastElement
h) contains
i) indexOf
j) size
k) capacity
16.5 Explique por qué la operación de insertar elementos adicionales en un objeto Vector, cuyo tamaño actual
sea menor que su capacidad, es una operación relativamente rápida, y por qué la inserción de elementos adicionales
en un objeto Vector, cuyo tamaño actual sea igual a la capacidad, es una operación relativamente baja.
16.6 Al extender la clase Vector, los diseñadores de Java pudieron crear rápidamente la clase Stack. ¿Cuáles son
los aspectos negativos de este uso de la herencia, en especial para la clase Stack?
16.8 Explique brevemente la operación de cada uno de los siguientes métodos relacionados con Iterator:
a) iterator
b) hasNext
c) next
16.9 Explique brevemente la operación de cada uno de los siguientes métodos de la clase HashMap:
a) put
b) get
c) isEmpty
d) containsKey
e) keySet
16.10 Determine si cada uno de los siguientes enunciados es verdadero o falso. Si es falso, explique por qué.
a) Los elementos en un objeto Collection deben almacenarse en orden ascendente, antes de poder reali-
zar una búsqueda binaria mediante binarySearch.
b) El método first obtiene el primer elemento en un objeto TreeSet.
c) Un objeto List creado con el método asList de Arrays puede cambiar su tamaño.
16.11 Explique la operación de cada uno de los siguientes métodos de la clase Properties:
a) load
b) store
c) getProperty
d) list
16.12 Vuelva a escribir las líneas 16 a 25 en la figura 16.3 para que sean más concisas; utilice el método asList y
el constructor de LinkedList que recibe un argumento Collection.
16.13 (Eliminación de duplicados) Escriba un programa que lea una serie de nombres de pila y elimine duplica-
dos almacenándolos en un objeto Set. Permita al usuario buscar un nombre de pila.
16.14 (Conteo de letras) Modifique el programa de la figura 16.18 para contar el número de ocurrencias de cada
letra, en vez de cada palabra. Por ejemplo, la cadena “HOLA A TODOS” contiene una H, tres O, una L, dos A, una T, una D
y una S. Muestre los resultados.
16.15 (Selector de colores) Use un objeto HashMap para crear una clase reutilizable y elegir uno de los 13 colores
predefinidos en la clase Color. Los nombres de los colores deben usarse como claves, y los objetos Color predefinidos
deben usarse como valores. Coloque esta clase en un paquete que pueda importarse en cualquier programa en Java.
Use su nueva clase en una aplicación que permita al usuario seleccionar un color y dibujar una figura en ese color.
16.16 (Conteo de palabras duplicadas) Escriba un programa que determine e imprima el número de pala-
bras duplicadas en un enunciado. Trate a las letras mayúsculas y minúsculas de igual forma. Ignore los signos de
puntuación.
16.17 (Insertar elementos en un objeto LinkedList en orden) Escriba un programa que inserte 25 enteros aleato-
rios de 0 a 100 en orden, en un objeto LinkedList. El programa debe ordenar los elementos, para luego calcular la
suma de éstos y su promedio de punto flotante.
16.18 (Copiar e invertir objetos LinkedList) Escriba un programa que cree un objeto LinkedList de 10 carac-
teres; después el programa debe crear un segundo objeto LinkedList que contenga una copia de la primera lista,
pero en orden inverso.
16.19 (Números primos y factores primos) Escriba un programa que reciba una entrada tipo número entero de un
usuario, y que determine si es primo. Si el número no es primo, muestre sus factores primos únicos. Recuerde que los
factores de un número primo son sólo 1 y el mismo número primo. Todo número que no sea primo tiene una facto-
rización prima única. Por ejemplo, considere el número 54. Los factores primos de 54 son 2, 3, 3 y 3. Cuando los
valores se multiplican entre sí, el resultado es 54. Para el número 54, los factores primos a imprimir deben ser 2 y 3.
Use objetos Set como parte de su solución.
16.20 (Ordenar palabras con un objeto TreeSet) Escriba un programa que utilice el método split de String para
dividir en tokens una línea de texto introducida por el usuario, y que coloque cada token en un objeto TreeSet. Imprima
los elementos del objeto TreeSet. [Nota: esto debe hacer que se impriman los elementos en orden ascendente].
16.21 (Cambiar el orden de un objeto PriorityQueue) Los resultados de la figura 16.15 muestran que Priority-
Queue ordena elementos Double en orden ascendente. Vuelva a escribir la figura 16.15, de manera que ordene los
elementos Double en forma descendente (es decir, 9.8 debe ser el elemento de mayor prioridad, en vez de 3.2).