| [en-usa] [Traduzir] [+ Trad.] |
Isto é apenas um simples programa que possui somente uma quantidade fixa de objetos com tempo de vida conhecidos. |
| [en-usa] [Traduzir] [+ Trad.] |
De um modo geral, seus programas sempre criarão novos objetos, com base em alguns critérios que somente serão conhecidos durante a execução. Até que isto aconteça, você não tem como saber a quantidade ou mesmo o tipo exato de objetos que serão necessários. Para resolver o problema de programação para o caso geral, você deve ser capaz de criar qualquer quantidade de objetos, a qualquer momento e em qualquer lugar. Portanto, você não pode contar com a criação de uma referência com um nome específico para guardar cada um dos seus objetos: |
| [en-usa] [Traduzir] [+ Trad.] |
MyObject myReference; |
| [en-usa] [Traduzir] [+ Trad.] |
porque você nunca saberá a quantidade destes objetos que será realmente necessária.Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
A maioria das linguagens provê alguma solução para este problema crucial. Java possui diversas formas de armazenar objetos (ou melhor, referências para os objetos). O tipo natural é o array, que já foi discutido. A biblioteca de utilitários Java tem um conjunto de container classes (também conhecidas como collection classes, mas devido ao fato de que as bibliotecas Java 2 utilizam o termo Collection para se referir a um subconjunto específico da biblioteca, eu também utilizarei o termo mais abrangente “container”). Os containers disponibilizam formas sofisticadas de armazenamento e até de manipulação de seus objetos. Comentários (em inglês) Arrays |
| [en-usa] [Traduzir] [+ Trad.] |
A base introdutória aos arrays foi apresentada na última seção do Capítulo 4, onde foram mostrados os procedimentos de definição e inicialização de um array. O foco deste capítulo é o armazenamento de objetos e um array é exatamente uma forma de se armazenar objetos. Mas se existem tantas outras formas de armazenamento, o que é que torna o array especial?Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Existem três características que distinguem os arrays dos outros tipos de containers: eficiência, modelo e maneira de armazenar os elementos. O array é o meio mais eficiente em Java para se armazenar e acessar randômicamente uma seqüência de referências a objetos. O array consiste em uma seqüência linear simples, o que torna o acesso a um elemento rápido; entretanto, essa velocidade tem seu preço: quando você cria um objeto na forma de um array, seu tamanho é fixo e não pode ser alterado durante todo o período de existência do objeto. Você poderia sugerir que fosse criado um array de um determinado tamanho e que, sempre que o espaço reservado se tornasse insufiente, houvesse a criação de um novo e, em seguida, todas as referências do antigo fossem movidas para o novo. Este é o comportamento da classe ArrayList que será estudada mais tarde neste capítulo. Entretanto, devido ao advento desta flexibilidade, um ArrayList é perceptivelmente menos eficiente do que um array.Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Em C++, a classe vector, uma classe do tipo contêiner, conhece o tipo dos objetos armazenados, mas apresenta um defeito, quando comparada com os arrays em Java: O operador do vector em C++, [], não verifica seus limites, de modo que você pode ultrapassar seu final.[51] Em Java, os limites são verificados, independentemente de você estar usando um array ou um contêiner; será lançada uma RuntimeException se você ultrapassar os limites. Este tipo de exceção indica um erro do programador e, portanto, você não precisa fazer esta verificação ao escrever seu código. Um detalhe: C++ não faz esta verificação no vector para ganhar em velocidade; em Java, o desempenho fica prejudicado pelo esforço constante de verificação de limites tanto nos arrays como nos contêiners.Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Todas as outras classes genéricas do tipo contêiner que serão estudadas neste capítulo, List, Set, e Map, tratam os objetos como se não tivessem um tipo específico. Isto é, tratam-nos a todos como sendo de um único tipo, o Object, a raiz de todas as classes em Java. Isto funciona perfeitamente de um ponto de vista: você precisa construir apenas um contêiner, e qualquer objeto Java será armazenado nele. (Excetuando-se as primitivas, que poderão ser colocadas em contêiners como constantes, utilizando as classes Java que encapsulam as primitivas, ou como valores variáveis encapsulados na classe criada pelo programador.) Este é o segundo aspecto em que um array é superior aos contêiners genéricos: quando você cria um array, você escolhe que tipo específico será armazenado (o que está relacionado a um terceiro fator—um array pode armazenar primitivas, ao passo que um contêiner não pode). Isto significa que você tem uma verificação em tempo de compilação que previne a inserção de um tipo errado ou o equívoco de considerar que foi extraído um tipo diferente daquele que realmente ocorreu. Naturalmente, Java vai impedir que você envie uma mensagem inadequada a um objeto seja em tempo de compilação ou de execução. Portanto, uma maneira não é mais arriscada que a outra, é apenas mais fácil se o compilador apontar o erro para você, antes do tempo de execução, o que torna menos provável que o usuário final seja surpreendido por uma exceção. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Por questões de eficiência e verificação de tipo, é sempre mais interessante tentar utilizar um array. Entretanto, quando você estiver resolvendo um problema mais geral, os arrays podem se tornar muito restritivos. Após considerar os arrays, o restante do capítulo será dedicado às classes contêiners fornecidas com Java. Comentários (em inglês)
Arrays
as primeiras classes de objetos
|
| [en-usa] [Traduzir] [+ Trad.] |
Independentemente do tipo de array com o qual você esteja trabalhando, o identificador do array é, na verdade, uma referência para o objeto real que foi criado na pilha. Este é o objeto que guarda as referências para os outros objetos, e pode ser criado tanto implicitamente, como parte da sintaxe de inicialização do array, como explicitamente, com a expressão new . Uma parte do objeto array (de fato, o único campo ou método acessível) é o length, do tipo somente leitura, que informa a você quantos elementos podem ser armazenados naquele objeto array. A sintaxe [] é o único outro meio para você acessar o objeto array. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
O exemplo a seguir mostra as diversas formas pelas quais um array pode ser inicializado, e como as referências do array podem ser associadas a diferentes objetos array. O exemplo mostra também que o uso de arrays de objetos é praticamente idêntico ao de arrays de primitivas. A única diferença é que arrays de objetos armazenam referências, enquanto arrays de primitivas armazenam diretamente os valores das primitivas. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:ArraySize.java
// Initialization & re-assignment of arrays.
import com.bruceeckel.simpletest.*;
class Weeble {} // A small mythical creature
public class ArraySize {
private static Test monitor = new Test();
public static void main(String[] args) {
// Arrays of objects:
Weeble[] a; // Local uninitialized variable
Weeble[] b = new Weeble[5]; // Null references
Weeble[] c = new Weeble[4];
for(int i = 0; i < c.length; i++)
if(c[i] == null) // Can test for null reference
c[i] = new Weeble();
// Aggregate initialization:
Weeble[] d = {
new Weeble(), new Weeble(), new Weeble()
};
// Dynamic aggregate initialization:
a = new Weeble[] {
new Weeble(), new Weeble()
};
System.out.println("a.length=" + a.length);
System.out.println("b.length = " + b.length);
// The references inside the array are
// automatically initialized to null:
for(int i = 0; i < b.length; i++)
System.out.println("b[" + i + "]=" + b[i]);
System.out.println("c.length = " + c.length);
System.out.println("d.length = " + d.length);
a = d;
System.out.println("a.length = " + a.length);
// Arrays of primitives:
int[] e; // Null reference
int[] f = new int[5];
int[] g = new int[4];
for(int i = 0; i < g.length; i++)
g[i] = i*i;
int[] h = { 11, 47, 93 };
// Compile error: variable e not initialized:
//!System.out.println("e.length=" + e.length);
System.out.println("f.length = " + f.length);
// The primitives inside the array are
// automatically initialized to zero:
for(int i = 0; i < f.length; i++)
System.out.println("f[" + i + "]=" + f[i]);
System.out.println("g.length = " + g.length);
System.out.println("h.length = " + h.length);
e = h;
System.out.println("e.length = " + e.length);
e = new int[] { 1, 2 };
System.out.println("e.length = " + e.length);
monitor.expect(new String[] {
"a.length=2",
"b.length = 5",
"b[0]=null",
"b[1]=null",
"b[2]=null",
"b[3]=null",
"b[4]=null",
"c.length = 4",
"d.length = 3",
"a.length = 3",
"f.length = 5",
"f[0]=0",
"f[1]=0",
"f[2]=0",
"f[3]=0",
"f[4]=0",
"g.length = 4",
"h.length = 3",
"e.length = 3",
"e.length = 2"
});
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
O array a é uma variável local não iniciailzada e o compilador impede que você faça qualquer coisa com esta referência até que você a inicialize adequadamente. O array b é inicializado de forma a apontar para um array de referências Weeble, mas nenhum objeto Weeble foi armazenado naquele array. Entretanto, você pode perguntar qual é o tamanho do array, pois b está apontando para um objeto que realmente existe. Este fato implica em um leve defeito: você não pode descobrir quantos objetos estão armazenados em um array, pois o resultado de length indica apenas quantos elementos podem ser armazenados no array; ou seja, o tamanho do objeto array e não o número de elementos armazenados de fato. Entretanto, quando um objeto array é criado é criado, suas referências são automaticamente inicializadas com null, de modo que você pode verificar se em uma dada posição do array está ou não armazenado um objeto, verificando se seu conteúdo é null. Analogamente, um array de primitivas é automaticamente inicializado com zeros, para os tipos numéricos, com (char)0 para char, e com false para boolean. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
O array c mostra a criação de um objeto array seguida da associação de objetos Weeble a todas as suas posições. O array d mostra a sintaxe de “inicialização agregada” que faz com que o objeto array seja criado (implicitamente com new na pilha, da mesma forma que para o array c) e inicializado com objetos Weeble, tudo em um único comando. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
A próxima inicialização de array poderia ser entendida como uma “inicialização agregada dinâmica.” A inicialização agregada, utilizada por d deve ser usada no ponto onde d é definido, mas com a segunda sintaxe você pode inicializar um objeto array em qualquer ponto. Por exemplo, suponha que hide( ) seja um método que receba um array de objetos Weeble. Você poderia chamá-lo com: |
| [en-usa] [Traduzir] [+ Trad.] |
hide(d); |
| [en-usa] [Traduzir] [+ Trad.] |
mas você também pode criar dinamicamente o array que será passado como argumento: |
| [en-usa] [Traduzir] [+ Trad.] |
hide(new Weeble[] { new Weeble(), new Weeble() }); |
| [en-usa] [Traduzir] [+ Trad.] |
Em muitas situações esta sintaxe propicia uma mehor forma de se escrever o código. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
A expressão: |
| [en-usa] [Traduzir] [+ Trad.] |
a = d; |
| [en-usa] [Traduzir] [+ Trad.] |
mostra como você pode usar uma referência que está associada a um objeto array para associa-la a outro objeto array, exatamente como você pode fazer com qualquer outro tipo de referência de objeto. Agora ambos, a e d estão apontando para o mesmo objeto array do heap. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
A segunda parte de ArraySize.java mostra que arrays de tipos primitivos funcionam exatamente como arrays de objetos except que arrays de tipos primitivos guardam valores primitivos diretamente. Comentários (em inglês)
Contêineres de
tipos primitivos
|
| [en-usa] [Traduzir] [+ Trad.] |
Classes contêineres podem referenciar somente Object Um array, entretanto, pode ser criado para guardar tipos primitivos diretamente, e também para guardar referências a Object. É possível usar classes wrapper, tais como Integer, Double, etc. para guardar valores primitivos em contêineres, mas as classes envelopadoras podem ser desajeitadas. Em adição, é muito mais eficiente criar e acessar um array de tipos primitivos do que um contêiner de tipos primitivos envelopados. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
É claro, se você estiver usando um tipo primitivo e você precisa da flexibilidade de um contêiner que automaticamente se expanda quando precisar de mais espaço, o array não funcionará e você será forçado a usar um contêiner de tipos primitivos envelopados. Você deve imaginar que deveria existir um tipo especializado de ArrayList para cada um dos tipos primitivos de dados, mas Java não fornece isto para você. [52] Comentários (em inglês) Retornando um array |
| [en-usa] [Traduzir] [+ Trad.] |
Suponha que você esteja escrevendo um método e deseje que seu retorno seja, não apenas uma única informação, mas um conjunto de informações. Linguagens como C e C++ tornam esta tarefa difícil, pois você não pode simplesmente retornar um array, mas somente um ponteiro para um array. Isto causa problemas porque o controle do tempo de vida do array fica confuso, facilitando estouros de memória.
Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Java tem um comportamento semelhante, mas você apenas “retorna um array.” Diferentemente de C++, com Java você nunca se preocupa com o tempo de vida desse array-ele existirá enquanto for necessário e o coletor de lixo o levará embora quando você não precisar mais dele.Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Como um exemplo, considere o retorno de um array de String: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:IceCream.java
// Returning arrays from methods.
import com.bruceeckel.simpletest.*;
import java.util.*;
public class IceCream {
private static Test monitor = new Test();
private static Random rand = new Random();
public static final String[] flavors = {
"Chocolate", "Strawberry", "Vanilla Fudge Swirl",
"Mint Chip", "Mocha Almond Fudge", "Rum Raisin",
"Praline Cream", "Mud Pie"
};
public static String[] flavorSet(int n) {
String[] results = new String[n];
boolean[] picked = new boolean[flavors.length];
for(int i = 0; i < n; i++) {
int t;
do
t = rand.nextInt(flavors.length);
while(picked[t]);
results[i] = flavors[t];
picked[t] = true;
}
return results;
}
public static void main(String[] args) {
for(int i = 0; i < 20; i++) {
System.out.println(
"flavorSet(" + i + ") = ");
String[] fl = flavorSet(flavors.length);
for(int j = 0; j < fl.length; j++)
System.out.println("\t" + fl[j]);
monitor.expect(new Object[] {
"%% flavorSet\\(\\d+\\) = ",
new TestExpression("%% \\t(Chocolate|Strawberry|"
+ "Vanilla Fudge Swirl|Mint Chip|Mocha Almond "
+ "Fudge|Rum Raisin|Praline Cream|Mud Pie)", 8)
});
}
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
O método flavorSet( ) cria um array de String chamado results. O tamanho deste array, n, é determinado pelo argumento que você passa para o método. Então ele escolhe, aleatoriamente, n sabores dentre aqueles armazenados no array flavors e os coloca no array results, que ele retorna quando termina a tarefa. Retornar um array é exatamente igual a retornar qualquer outro objeto—é uma referência. Para isto, não importa se o array foi criado dentro do método flavorSet( ), ou em qualquer outro lugar. O coletor de lixo se encarrega de eliminar o array quando você tiver terminado de trabalhar com ele, e o array persistirá enquanto for necessário. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Como um aparte, observe que quando flavorSet( ) escolhe sabores aleatoriamente, ele se assegura de que uma determinada escolha já não tenha sido selecionada. Esta propriedade é implementada em um laço do que fica fazendo escolhas aleatórias até encontrar uma que ainda não tenha sido escolhida para ser armazenada no array. (Naturalmente, poderia ter sido feita uma comparação de String para verificar se o sabor escolhido aleatoriamente já estava no array results.) Quando esta condição é satisfeita, a entrada é adicionada e a próxima é procurada ( i é incrementado). Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
main( ) imprime 20 conjuntos completos de sabores, de forma que você pode verificar que o método flavorSet( ) escolhe os sabores em uma ordem aleatória a cada vez. É fácil confirmar isto se você redirecionar a saída para um arquivo. E, enquanto você estiver examinando o arquivo, lembre-se de que você apenas deseja o sorvete e não precisa dele. Comentários (em inglês) A classe Arrays |
| [en-usa] [Traduzir] [+ Trad.] |
Em java.util, você encontrará a classe Arrays, que tem um conjunto de métodos static para executar funções necessárias para a manipulação dos arrays: equals( ), para comparar dois arrays em termos de igualdade; fill( ), para preencher um array com um valor; sort( ), para ordenar o array; e binarySearch( ), para procurar um elemento em um array ordenado. Todos estes métodos são sobrecarregados para todos os tipos primitivos e Objects. Além disto, há um método específico asList( ) que transforma qualquer array em um contêiner List, sobre o qual você aprenderá mais tarde neste capítulo.Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Embora útil, a classe Arrays logo deixa de ser completamente funcional. Por exemplo, seria conveniente poder imprimir facilmente os elementos de um array sem precisar toda vez escrever no código um laço for. E como você verá, o método fill( ) pega apenas um elemento de cada vez para colocá-lo no array, de forma que se você desejar preencher um array com números gerados aleatoriamente, o método fill( ) não será adequado. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Portanto, faz sentido complementar a classe Arrays com algumas funcionalidades adicionais, que serão colocadas no pacote com.bruceeckel.util para sua comodidade. Estas imprimirão um array de qualquer tipo e preencherão um array com valores ou objetos criados por um objeto denominado generator que você pode definir. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Como o código precisa ser criado para cada tipo primitiva e para Object, há uma grande quantidade de código praticamente duplicado.[53] Por exemplo, uma interface "generator" é necessária para cada tipo porque o tipo de retorno de next( ) deve ser diferente em cada um dos casos: Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
//: com:bruceeckel:util:Generator.java
package com.bruceeckel.util;
public interface Generator { Object next(); } ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
//: com:bruceeckel:util:BooleanGenerator.java
package com.bruceeckel.util;
public interface BooleanGenerator { boolean next(); } ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
//: com:bruceeckel:util:ByteGenerator.java
package com.bruceeckel.util;
public interface ByteGenerator { byte next(); } ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
//: com:bruceeckel:util:CharGenerator.java
package com.bruceeckel.util;
public interface CharGenerator { char next(); } ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
//: com:bruceeckel:util:ShortGenerator.java
package com.bruceeckel.util;
public interface ShortGenerator { short next(); } ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
//: com:bruceeckel:util:IntGenerator.java
package com.bruceeckel.util;
public interface IntGenerator { int next(); } ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
//: com:bruceeckel:util:LongGenerator.java
package com.bruceeckel.util;
public interface LongGenerator { long next(); } ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
//: com:bruceeckel:util:FloatGenerator.java
package com.bruceeckel.util;
public interface FloatGenerator { float next(); } ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
//: com:bruceeckel:util:DoubleGenerator.java
package com.bruceeckel.util;
public interface DoubleGenerator { double next(); } ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
Arrays2 contém uma variedade de métodos toString( ), sobrecarregados para cada tipo. Estes métodos permitem que você imprima facilmente um array. O código toString( ) introduz o uso de StringBuffer ao invés de objetos String. Isto é um indício de eficiência; quando você está construindo uma string em um método que poderá ser chamado muitas vezes, é mais aconselhável usar o StringBuffer que é mais eficiente, e não as operações mais convenientes de String. Aqui, StringBuffer é criado com um valor inicial e são acrescentadas Strings, uma após outra. Finalmente, o resultado é convertido para uma String como o valor de retorno: Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
//: com:bruceeckel:util:Arrays2.java
// A supplement to java.util.Arrays, to provide additional
// useful functionality when working with arrays. Allows
// any array to be converted to a String, and to be filled
// via a user-defined "generator" object.
package com.bruceeckel.util;
import java.util.*;
public class Arrays2 {
public static String toString(boolean[] a) {
StringBuffer result = new StringBuffer("[");
for(int i = 0; i < a.length; i++) {
result.append(a[i]);
if(i < a.length - 1)
result.append(", ");
}
result.append("]");
return result.toString();
}
public static String toString(byte[] a) {
StringBuffer result = new StringBuffer("[");
for(int i = 0; i < a.length; i++) {
result.append(a[i]);
if(i < a.length - 1)
result.append(", ");
}
result.append("]");
return result.toString();
}
public static String toString(char[] a) {
StringBuffer result = new StringBuffer("[");
for(int i = 0; i < a.length; i++) {
result.append(a[i]);
if(i < a.length - 1)
result.append(", ");
}
result.append("]");
return result.toString();
}
public static String toString(short[] a) {
StringBuffer result = new StringBuffer("[");
for(int i = 0; i < a.length; i++) {
result.append(a[i]);
if(i < a.length - 1)
result.append(", ");
}
result.append("]");
return result.toString();
}
public static String toString(int[] a) {
StringBuffer result = new StringBuffer("[");
for(int i = 0; i < a.length; i++) {
result.append(a[i]);
if(i < a.length - 1)
result.append(", ");
}
result.append("]");
return result.toString();
}
public static String toString(long[] a) {
StringBuffer result = new StringBuffer("[");
for(int i = 0; i < a.length; i++) {
result.append(a[i]);
if(i < a.length - 1)
result.append(", ");
}
result.append("]");
return result.toString();
}
public static String toString(float[] a) {
StringBuffer result = new StringBuffer("[");
for(int i = 0; i < a.length; i++) {
result.append(a[i]);
if(i < a.length - 1)
result.append(", ");
}
result.append("]");
return result.toString();
}
public static String toString(double[] a) {
StringBuffer result = new StringBuffer("[");
for(int i = 0; i < a.length; i++) {
result.append(a[i]);
if(i < a.length - 1)
result.append(", ");
}
result.append("]");
return result.toString();
}
// Fill an array using a generator:
public static void fill(Object[] a, Generator gen) {
fill(a, 0, a.length, gen);
}
public static void
fill(Object[] a, int from, int to, Generator gen) {
for(int i = from; i < to; i++)
a[i] = gen.next();
}
public static void
fill(boolean[] a, BooleanGenerator gen) {
fill(a, 0, a.length, gen);
}
public static void
fill(boolean[] a, int from, int to,BooleanGenerator gen){
for(int i = from; i < to; i++)
a[i] = gen.next();
}
public static void fill(byte[] a, ByteGenerator gen) {
fill(a, 0, a.length, gen);
}
public static void
fill(byte[] a, int from, int to, ByteGenerator gen) {
for(int i = from; i < to; i++)
a[i] = gen.next();
}
public static void fill(char[] a, CharGenerator gen) {
fill(a, 0, a.length, gen);
}
public static void
fill(char[] a, int from, int to, CharGenerator gen) {
for(int i = from; i < to; i++)
a[i] = gen.next();
}
public static void fill(short[] a, ShortGenerator gen) {
fill(a, 0, a.length, gen);
}
public static void
fill(short[] a, int from, int to, ShortGenerator gen) {
for(int i = from; i < to; i++)
a[i] = gen.next();
}
public static void fill(int[] a, IntGenerator gen) {
fill(a, 0, a.length, gen);
}
public static void
fill(int[] a, int from, int to, IntGenerator gen) {
for(int i = from; i < to; i++)
a[i] = gen.next();
}
public static void fill(long[] a, LongGenerator gen) {
fill(a, 0, a.length, gen);
}
public static void
fill(long[] a, int from, int to, LongGenerator gen) {
for(int i = from; i < to; i++)
a[i] = gen.next();
}
public static void fill(float[] a, FloatGenerator gen) {
fill(a, 0, a.length, gen);
}
public static void
fill(float[] a, int from, int to, FloatGenerator gen) {
for(int i = from; i < to; i++)
a[i] = gen.next();
}
public static void fill(double[] a, DoubleGenerator gen){
fill(a, 0, a.length, gen);
}
public static void
fill(double[] a, int from, int to, DoubleGenerator gen) {
for(int i = from; i < to; i++)
a[i] = gen.next();
}
private static Random r = new Random();
public static class
RandBooleanGenerator implements BooleanGenerator {
public boolean next() { return r.nextBoolean(); }
}
public static class
RandByteGenerator implements ByteGenerator {
public byte next() { return (byte)r.nextInt(); }
}
private static String ssource =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private static char[] src = ssource.toCharArray();
public static class
RandCharGenerator implements CharGenerator {
public char next() {
return src[r.nextInt(src.length)];
}
}
public static class
RandStringGenerator implements Generator {
private int len;
private RandCharGenerator cg = new RandCharGenerator();
public RandStringGenerator(int length) {
len = length;
}
public Object next() {
char[] buf = new char[len];
for(int i = 0; i < len; i++)
buf[i] = cg.next();
return new String(buf);
}
}
public static class
RandShortGenerator implements ShortGenerator {
public short next() { return (short)r.nextInt(); }
}
public static class
RandIntGenerator implements IntGenerator {
private int mod = 10000;
public RandIntGenerator() {}
public RandIntGenerator(int modulo) { mod = modulo; }
public int next() { return r.nextInt(mod); }
}
public static class
RandLongGenerator implements LongGenerator {
public long next() { return r.nextLong(); }
}
public static class
RandFloatGenerator implements FloatGenerator {
public float next() { return r.nextFloat(); }
}
public static class
RandDoubleGenerator implements DoubleGenerator {
public double next() {return r.nextDouble();}
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
Para preencher um array de elementos utilizando um gerador, o método fill( ) faz referência a uma interface geradora apropriada, que deve ter um método next( ) que produzirá de alguma maneira um objeto do tipo correto (dependendo de como a interface é implementada). O método fill( ) simplesmente chama next( ), até que a faixa desejada esteja preenchida. Assim você pode criar qualquer gerador implementando a interface apropriada e usar seu gerador com fill( ). Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Geradores de dados aleatórios são úteis para testes, então um conjunto de classes internas é criado para implementar todas as interfaces de geradores de primitivas, além do gerador String para representar Object. Você pode ver que o RandStringGenerator usa o RandCharGenerator para preencher um array de caracteres, que é então transformado em uma String. O tamanho do array é determinado pelo argumento do construtor.Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Para gerar números que não são grandes demais, o valor default para o módulo, no RandIntGenerator, é 10,000, mas o construtor sobrecarregado permite a você escolher um valor menor.Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Aqui é um programa que testa a biblioteca e demonstra como este é utilizado: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:TestArrays2.java
// Test and demonstrate Arrays2 utilities.
import com.bruceeckel.util.*;
public class TestArrays2 {
public static void main(String[] args) {
int size = 6;
// Or get the size from the command line:
if(args.length != 0) {
size = Integer.parseInt(args[0]);
if(size < 3) {
System.out.println("arg must be >= 3");
System.exit(1);
}
}
boolean[] a1 = new boolean[size];
byte[] a2 = new byte[size];
char[] a3 = new char[size];
short[] a4 = new short[size];
int[] a5 = new int[size];
long[] a6 = new long[size];
float[] a7 = new float[size];
double[] a8 = new double[size];
Arrays2.fill(a1, new Arrays2.RandBooleanGenerator());
System.out.println("a1 = " + Arrays2.toString(a1));
Arrays2.fill(a2, new Arrays2.RandByteGenerator());
System.out.println("a2 = " + Arrays2.toString(a2));
Arrays2.fill(a3, new Arrays2.RandCharGenerator());
System.out.println("a3 = " + Arrays2.toString(a3));
Arrays2.fill(a4, new Arrays2.RandShortGenerator());
System.out.println("a4 = " + Arrays2.toString(a4));
Arrays2.fill(a5, new Arrays2.RandIntGenerator());
System.out.println("a5 = " + Arrays2.toString(a5));
Arrays2.fill(a6, new Arrays2.RandLongGenerator());
System.out.println("a6 = " + Arrays2.toString(a6));
Arrays2.fill(a7, new Arrays2.RandFloatGenerator());
System.out.println("a7 = " + Arrays2.toString(a7));
Arrays2.fill(a8, new Arrays2.RandDoubleGenerator());
System.out.println("a8 = " + Arrays2.toString(a8));
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
O parâmetro size tem um valor default, mas você pode também modificá-lo através da linha de comando.Comentários (em inglês) Preenchendo um array |
| [en-usa] [Traduzir] [+ Trad.] |
A biblioteca padrão Java Arrays também tem um método fill( ), porém este é bastante trivial, já que apenas duplica um único valor e o coloca em todas as posições, ou, no caso de objetos, copia a mesma referência em cada uma das posições. Usando Arrays2.toString( ), os métodos Arrays.fill( ) podem ser facilmente demonstrados: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:FillingArrays.java
// Using Arrays.fill()
import com.bruceeckel.simpletest.*;
import com.bruceeckel.util.*;
import java.util.*;
public class FillingArrays {
private static Test monitor = new Test();
public static void main(String[] args) {
int size = 6;
// Or get the size from the command line:
if(args.length != 0)
size = Integer.parseInt(args[0]);
boolean[] a1 = new boolean[size];
byte[] a2 = new byte[size];
char[] a3 = new char[size];
short[] a4 = new short[size];
int[] a5 = new int[size];
long[] a6 = new long[size];
float[] a7 = new float[size];
double[] a8 = new double[size];
String[] a9 = new String[size];
Arrays.fill(a1, true);
System.out.println("a1 = " + Arrays2.toString(a1));
Arrays.fill(a2, (byte)11);
System.out.println("a2 = " + Arrays2.toString(a2));
Arrays.fill(a3, 'x');
System.out.println("a3 = " + Arrays2.toString(a3));
Arrays.fill(a4, (short)17);
System.out.println("a4 = " + Arrays2.toString(a4));
Arrays.fill(a5, 19);
System.out.println("a5 = " + Arrays2.toString(a5));
Arrays.fill(a6, 23);
System.out.println("a6 = " + Arrays2.toString(a6));
Arrays.fill(a7, 29);
System.out.println("a7 = " + Arrays2.toString(a7));
Arrays.fill(a8, 47);
System.out.println("a8 = " + Arrays2.toString(a8));
Arrays.fill(a9, "Hello");
System.out.println("a9 = " + Arrays.asList(a9));
// Manipulating ranges:
Arrays.fill(a9, 3, 5, "World");
System.out.println("a9 = " + Arrays.asList(a9));
monitor.expect(new String[] {
"a1 = [true, true, true, true, true, true]",
"a2 = [11, 11, 11, 11, 11, 11]",
"a3 = [x, x, x, x, x, x]",
"a4 = [17, 17, 17, 17, 17, 17]",
"a5 = [19, 19, 19, 19, 19, 19]",
"a6 = [23, 23, 23, 23, 23, 23]",
"a7 = [29.0, 29.0, 29.0, 29.0, 29.0, 29.0]",
"a8 = [47.0, 47.0, 47.0, 47.0, 47.0, 47.0]",
"a9 = [Hello, Hello, Hello, Hello, Hello, Hello]",
"a9 = [Hello, Hello, Hello, World, World, Hello]"
});
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
Você pode preencher todo o array ou, como mostram os dois últimos comandos, um intervalo de elementos. Mas como você só pode adotar um único valor de preenchimento usando Arrays.fill( ), os métodos Arrays2.fill( ) produzem resultados muito mais interessantes. Comentários (em inglês)
Copiando um array
|
| [en-usa] [Traduzir] [+ Trad.] |
A biblioteca padrão Java disponibiliza um método static, System.arraycopy( ), que pode efetuar cópias de um array de modo muito mais rápido do que você conseguiria se fosse usar um laço for para efetuar a cópia manualmente. System.arraycopy( ) é sobrecarregado para manipular todos os tipos. A seguir há um exemplo que manipula arrays de int: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:CopyingArrays.java
// Using System.arraycopy()
import com.bruceeckel.simpletest.*;
import com.bruceeckel.util.*;
import java.util.*;
public class CopyingArrays {
private static Test monitor = new Test();
public static void main(String[] args) {
int[] i = new int[7];
int[] j = new int[10];
Arrays.fill(i, 47);
Arrays.fill(j, 99);
System.out.println("i = " + Arrays2.toString(i));
System.out.println("j = " + Arrays2.toString(j));
System.arraycopy(i, 0, j, 0, i.length);
System.out.println("j = " + Arrays2.toString(j));
int[] k = new int[5];
Arrays.fill(k, 103);
System.arraycopy(i, 0, k, 0, k.length);
System.out.println("k = " + Arrays2.toString(k));
Arrays.fill(k, 103);
System.arraycopy(k, 0, i, 0, k.length);
System.out.println("i = " + Arrays2.toString(i));
// Objects:
Integer[] u = new Integer[10];
Integer[] v = new Integer[5];
Arrays.fill(u, new Integer(47));
Arrays.fill(v, new Integer(99));
System.out.println("u = " + Arrays.asList(u));
System.out.println("v = " + Arrays.asList(v));
System.arraycopy(v, 0, u, u.length/2, v.length);
System.out.println("u = " + Arrays.asList(u));
monitor.expect(new String[] {
"i = [47, 47, 47, 47, 47, 47, 47]",
"j = [99, 99, 99, 99, 99, 99, 99, 99, 99, 99]",
"j = [47, 47, 47, 47, 47, 47, 47, 99, 99, 99]",
"k = [47, 47, 47, 47, 47]",
"i = [103, 103, 103, 103, 103, 47, 47]",
"u = [47, 47, 47, 47, 47, 47, 47, 47, 47, 47]",
"v = [99, 99, 99, 99, 99]",
"u = [47, 47, 47, 47, 47, 99, 99, 99, 99, 99]"
});
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
The arguments to arraycopy( ) are the source array, the offset into the source array from whence to start copying, the destination array, the offset into the destination array where the copying begins, and the number of elements to copy. Naturally, any violation of the array boundaries will cause an exception. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
The example shows that both primitive arrays and object arrays can be copied. However, if you copy arrays of objects, then only the references get copiedtheres no duplication of the objects themselves. This is called a shallow copy (see Appendix A). Comentários (em inglês)
Comparing
arrays
|
| [en-usa] [Traduzir] [+ Trad.] |
Arrays provides the overloaded method equals( ) to compare entire arrays for equality. Again, these are overloaded for all the primitives and for Object. To be equal, the arrays must have the same number of elements, and each element must be equivalent to each corresponding element in the other array, using the equals( ) for each element. (For primitives, that primitives wrapper class equals( ) is used; for example, Integer.equals( ) for int.) For example: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:ComparingArrays.java
// Using Arrays.equals()
import com.bruceeckel.simpletest.*;
import java.util.*;
public class ComparingArrays {
private static Test monitor = new Test();
public static void main(String[] args) {
int[] a1 = new int[10];
int[] a2 = new int[10];
Arrays.fill(a1, 47);
Arrays.fill(a2, 47);
System.out.println(Arrays.equals(a1, a2));
a2[3] = 11;
System.out.println(Arrays.equals(a1, a2));
String[] s1 = new String[5];
Arrays.fill(s1, "Hi");
String[] s2 = {"Hi", "Hi", "Hi", "Hi", "Hi"};
System.out.println(Arrays.equals(s1, s2));
monitor.expect(new String[] {
"true",
"false",
"true"
});
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
Originalmente, a1 e a2 são exatamente iguais, de forma que a saída é "true," mas então um dos elementos é alterado, o que torna o resultado "false." No último caso, todos os elementos de s1 apontam para o mesmo objeto, enquanto s2 tem cinco objetos iguais. Entretanto, a igualdade de array é baseada em seu conteúdo (verificada por meio de Object.equals( )) , de forma que o resultado é "true".Comentários (em inglês)
Comparações entre elementos de
arrays
|
| [en-usa] [Traduzir] [+ Trad.] |
Uma das características que faltavam nas bibliotecas Java 1.0 e 1.1 era a realização de operações algorítmicas- mesmo um simples ordenamento. Isto gerava uma certa confusão para alguém que esperasse uma biblioteca padrão adequada. Afortunadamente, Java 2 remediou esta situação, pelo menos para o problema de ordenamento. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Um problema para se escrever código genérico de ordenamento é que para esta operação é necessário que se façam comparações baseadas no tipo real do objeto. Naturalmente, uma aproximação é escrever um método de ordenamento diferente para cada um dos diferentes tipos, mas como você deve perceber, eata prática não produz código que seja facilmente reutilizado para novos tipos.
Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Um objetivo primordial de projetos de programação é "separar coisas que variam de coisas que permanecem as mesmas," e, no nosso caso, o código que permanece o mesmo no algoritmo geral de ordenamento, mas o que muda de uma aplicação para outra é a forma como os objetos são comparados. Então, ao invés de colocar o código de ordenamento em muitas subrotinas distintas, utiliza-se a técnica de callback. Com um callback, a parte do código que varia de caso para caso é separada e a parte do código que permanece sempre a mesma chamará de volta o código que varia. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Java tem duas maneiras de disponibilizar a funcionalidade de comparação. A primeira utiliza o método "natural" de comparação que é passado para uma classe pela implementação da interface java.lang.Comparable. Esta é uma interface muito simples, com um único método compareTo( ). Este método recebe um outro Object como argumento e produz um valor negativo, se o objeto corrente for menor do que o argumento, zero, se os dois forem iguais, e um valor positivo se o objeto corrente for maior do que o argumento.Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
A seguir temos uma classe que implementa Comparable e demonstra a comparação feita utilizando o método padrão da biblioteca Java Arrays.sort( ): |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:CompType.java
// Implementing Comparable in a class.
import com.bruceeckel.util.*;
import java.util.*;
public class CompType implements Comparable {
int i;
int j;
public CompType(int n1, int n2) {
i = n1;
j = n2;
}
public String toString() {
return "[i = " + i + ", j = " + j + "]";
}
public int compareTo(Object rv) {
int rvi = ((CompType)rv).i;
return (i < rvi ? -1 : (i == rvi ? 0 : 1));
}
private static Random r = new Random();
public static Generator generator() {
return new Generator() {
public Object next() {
return new CompType(r.nextInt(100),r.nextInt(100));
}
};
}
public static void main(String[] args) {
CompType[] a = new CompType[10];
Arrays2.fill(a, generator());
System.out.println(
"before sorting, a = " + Arrays.asList(a));
Arrays.sort(a);
System.out.println(
"after sorting, a = " + Arrays.asList(a));
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
Ao definir um método de comparação, torna-se responsabilidade sua decidir o que significa comparar um de seus objetos com um outro. Neste caso, apenas os valores i são utilizados na comparação, e os valores j são ignorados.Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
O método static randInt( ) produz valores positivos entre 0 e 100 e o método generator( ) produz um objeto que implementa a interface Generator pela criação de uma classe interna anônima (veja capítulo 8). Esta constrói objetos CompType através de sua inicialização com valores aleatórios. Na main( ), o gerador é usado para preencher um array de CompType, que, em seguida, é ordenado. Se Comparable não tivesse sido implementada, então você obteria uma ClassCastException em tempo de execução quando você tentasse chamar sort( ). Isto porque sort( ) transforma seu argumento para Comparable. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Agora suponha que alguém disponibilize para você uma classe que não implementa Comparable, ou disponibilize esta clase que implementa Comparable, mas você decida que não gosta da forma como ela funciona e prefira ter um método de comparação diferente para cada tipo. A solução poderia ser escrever um código de comparação em cada objeto diferente. Ao invés disto, é utilizado o padrão de projeto strategy (estratégia) [54]. Com uma estratégia, a porção do código que varia é encapsulada em sua própria classe (o objeto de estratégia). Você passa um objeto de estratégia para a porção do código que permanece sempre a mesma, a qual usa a estratégia para completar o algoritmo. Desta forma, você pode fazer diferentes objetos para expressar diferentes formas de comparação e entregá-los ao mesmo código de ordenamento. Neste caso, você cria uma estratégia definindo uma classe separada que implementa uma interface chamada Comparator. Esta tem dois métodos, compare( ) e equals( ). Entretanto, você não tem que implementar equals( ) a não ser nos casos em que haja necessidade de um desempenho especial, porque sempre que você criar uma classe, ela herdará implicitamente de Object, que tem um método equals( ). Então você pode simplesmente usar o método default Object equals( ) e satisfazer a condição imposta pela interface. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
A classe Collections (que veremos um pouco mais adiante) comtém um único Comparator que inverte a ordem natural de ordenamento. Esta pode ser facilmente aplicada ao CompType: Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:Reverse.java
// The Collecions.reverseOrder() Comparator
import com.bruceeckel.util.*;
import java.util.*;
public class Reverse {
public static void main(String[] args) {
CompType[] a = new CompType[10];
Arrays2.fill(a, CompType.generator());
System.out.println(
"before sorting, a = " + Arrays.asList(a));
Arrays.sort(a, Collections.reverseOrder());
System.out.println(
"after sorting, a = " + Arrays.asList(a));
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
A chamada ao método Collections.reverseOrder( ) produz a referência para o Comparator. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Como um segundo exemplo, o Comparator seguinte compara objetos CompType com base em seus valores j e não em seus valores i: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:ComparatorTest.java
// Implementing a Comparator for a class.
import com.bruceeckel.util.*;
import java.util.*;
class CompTypeComparator implements Comparator {
public int compare(Object o1, Object o2) {
int j1 = ((CompType)o1).j;
int j2 = ((CompType)o2).j;
return (j1 < j2 ? -1 : (j1 == j2 ? 0 : 1));
}
}
public class ComparatorTest {
public static void main(String[] args) {
CompType[] a = new CompType[10];
Arrays2.fill(a, CompType.generator());
System.out.println(
"before sorting, a = " + Arrays.asList(a));
Arrays.sort(a, new CompTypeComparator());
System.out.println(
"after sorting, a = " + Arrays.asList(a));
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
O método compare( ) deve retornar um número inteiro negativo, nulo ou positivo, quando o primeiro argumento for, respectivamente, menor, igual ou maior do que o segundo. Comentários (em inglês) Ordenando um array |
| [en-usa] [Traduzir] [+ Trad.] |
Com os métodos de ordenação embutidos, você pode ordenar qualquer array de tipos primitivos, ou qualquer array de objetos que implementem Comparable ou tenham um Comparator associado. Isto cobre uma grande lacuna nas bibliotecas java; acreditem ou não, em Java 1.0 ou 1.1, não havia nenhum suporte para ordenação de Strings! Aqui temos um exemplo que gera objetos String aleatórios e os ordena: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:StringSorting.java
// Sorting an array of Strings.
import com.bruceeckel.util.*;
import java.util.*;
public class StringSorting {
public static void main(String[] args) {
String[] sa = new String[30];
Arrays2.fill(sa, new Arrays2.RandStringGenerator(5));
System.out.println(
"Before sorting: " + Arrays.asList(sa));
Arrays.sort(sa);
System.out.println(
"After sorting: " + Arrays.asList(sa));
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
Uma coisa que você vai perceber sobre a saída do algoritmo de ordenação de String é que ele é lexicográfico, e, portanto, coloca todas as palavras que começam com letras maiúsculas antes das palavras que começam com letras minúsculas. (Esta ordenação é típica de listas telefônicas). Você pode desejar agrupar as palavras independentemente de começarem por maiúsculas ou minúsculas e você pode fazer isto definindo uma classe Comparator , que sobrecarregue o comportamento String Comparable default. Para reutilização, esta deve ser adicionada ao pacote "util": Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
//: com:bruceeckel:util:AlphabeticComparator.java
// Keeping upper and lowercase letters together.
package com.bruceeckel.util;
import java.util.*;
public class AlphabeticComparator implements Comparator {
public int compare(Object o1, Object o2) {
String s1 = (String)o1;
String s2 = (String)o2;
return s1.toLowerCase().compareTo(s2.toLowerCase());
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
Se você tentar usar o método com o tipo errado, moldando-o no início como String, você vai obter uma exceção. Cada String é convertida para letras minúsculas antes da comparação. O método para comparações de String embutido em compareTo( ) providencia a funcionalidade desejada. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Aqui está um teste usando AlphabeticComparator: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:AlphabeticSorting.java
// Keeping upper and lowercase letters together.
import com.bruceeckel.util.*;
import java.util.*;
public class AlphabeticSorting {
public static void main(String[] args) {
String[] sa = new String[30];
Arrays2.fill(sa, new Arrays2.RandStringGenerator(5));
System.out.println(
"Before sorting: " + Arrays.asList(sa));
Arrays.sort(sa, new AlphabeticComparator());
System.out.println(
"After sorting: " + Arrays.asList(sa));
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
O algoritmo de ordenação que é usado na biblioteca padrão Java é projetado se forma otimizada para o tipo particular que você está ordenando- um Quicksort para tipos primitivos e um merge e sort estável para objetos. Portanto, você não precisa dispender tempo se preocupando com o desempenho a menos que seu profiler indique que seu processo de ordenação seja um gargalo. Comentários (em inglês)
Buscando em um
array ordenado
|
| [en-usa] [Traduzir] [+ Trad.] |
Uma vez que um array esteja ordenado, você pode realizar uma busca rápida por um determinado item usando Arrays.binarySearch( ). Entretanto, é muito importante que você não tente usar binarySearch( ) em um array não ordenado; os resultados serão imprevisíveis. O exemplo a seguir usa um RandIntGenerator para preencher um array e depois usa o mesmo gerador para produzir valores para serem buscados:Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:ArraySearching.java
// Using Arrays.binarySearch().
import com.bruceeckel.util.*;
import java.util.*;
public class ArraySearching {
public static void main(String[] args) {
int[] a = new int[100];
Arrays2.RandIntGenerator gen =
new Arrays2.RandIntGenerator(1000);
Arrays2.fill(a, gen);
Arrays.sort(a);
System.out.println(
"Sorted array: " + Arrays2.toString(a));
while(true) {
int r = gen.next();
int location = Arrays.binarySearch(a, r);
if(location >= 0) {
System.out.println("Location of " + r +
" is " + location + ", a[" +
location + "] = " + a[location]);
break; // Out of while loop
}
}
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
In the while loop, random values are generated as search items until one of them is found. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Arrays.binarySearch( ) produces a value greater than or equal to zero if the search item is found. Otherwise, it produces a negative value representing the place that the element should be inserted if you are maintaining the sorted array by hand. The value produced is |
| [en-usa] [Traduzir] [+ Trad.] |
-(insertion point) - 1 |
| [en-usa] [Traduzir] [+ Trad.] |
The insertion point is the index of the first element greater than the key, or a.size( ), if all elements in the array are less than the specified key. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
If the array contains duplicate elements, there is no guarantee which one will be found. The algorithm is thus not really designed to support duplicate elements, but rather to tolerate them. If you need a sorted list of nonduplicated elements, use a TreeSet (to maintain sorted order) or LinkedHashSet (to maintain insertion order), which will be introduced later in this chapter. These classes take care of all the details for you automatically. Only in cases of performance bottlenecks should you replace one of these classes with a hand-maintained array. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
If you have sorted an object array using a Comparator (primitive arrays do not allow sorting with a Comparator), you must include that same Comparator when you perform a binarySearch( ) (using the overloaded version of the method thats provided). For example, the AlphabeticSorting.java program can be modified to perform a search: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:AlphabeticSearch.java
// Searching with a Comparator.
import com.bruceeckel.simpletest.*;
import com.bruceeckel.util.*;
import java.util.*;
public class AlphabeticSearch {
private static Test monitor = new Test();
public static void main(String[] args) {
String[] sa = new String[30];
Arrays2.fill(sa, new Arrays2.RandStringGenerator(5));
AlphabeticComparator comp = new AlphabeticComparator();
Arrays.sort(sa, comp);
int index = Arrays.binarySearch(sa, sa[10], comp);
System.out.println("Index = " + index);
monitor.expect(new String[] {
"Index = 10"
});
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
The Comparator must be passed to the overloaded binarySearch( ) as the third argument. In this example, success is guaranteed because the search item is selected from the array itself. Comentários (em inglês) Array summary |
| [en-usa] [Traduzir] [+ Trad.] |
Resumindo o que você viu até aqui, sua primeira e mais eficiente escolha para armazenar um grupo de objetos deve ser um array, e esta é a única opção se você desejar armazenar um grupo de tipos primitivos. No restante deste capítulo trataremos do caso mais geral, em que no momento de escrever o programa, você ainda não sabe de quantos objetos vai precisar, ou se você precisar de uma forma mais sofisticada para armazenar seus objetos. Java fornece uma biblioteca de classes contêineres para resolver este problema, sendo os tipos básicos List, Set, e Map. Você pode resolver um número surpreendente de problemas usando estas ferramentas. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Among their other characteristicsSet, for example, holds only one object of each value, and Map is an associative array that lets you associate any object with any other objectthe Java container classes will automatically resize themselves. So, unlike arrays, you can put in any number of objects and you dont need to worry about how big to make the container while youre writing the program. Comentários (em inglês) Introduction to containers |
| [en-usa] [Traduzir] [+ Trad.] |
A meu ver, as classes contêineres constituem uma das ferramentas mais poderosas para desenvolvimento básico, porque elas aumentam significativamente seu potencial de programação. Os contêineres Java 2 representam um novo projeto, totalmente reestruturado [55] em relação àqueles muito mais pobres em Java 1.0 e 1.1. Alguns dos novos projetos tornam as coisas mais amarradas e mais lógicas. Também complementam a funcionalidade da biblioteca dos contêineres, com o comportamento das listas encadeadas, queues e deques (queues com dois finais, pronuncia-se "decks"). Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
O projeto de uma biblioteca de contêineres é difícil (o que é verdade para a maioria dos problemas de projetos de bibliotecas). Em C++, as classes contêineres cobrem o básico com muitas classes diferentes. Isto foi melhor do que o que havia disponível antes das classes contêineres do C++ (nada), mas estas não se traduziram satisfatoriamente para o Java. No outro extremo, eu tenho visto uma biblioteca de contêineres que consiste de uma única classe, "contêiner," que funciona de uma forma que se assemelha ao mesmo tempo a uma seqüência linear e a um array associativo. A biblioteca contêiner do Java 2 atingiu um equilíbrio: a completa funcionalidade que você espera de uma biblioteca contêiner madura, mas mais fácil de aprender e usar do que as classes contêineres do C++ e outras bibliotecas contêineres similares. O resultado pode parecer um pouco estranho em algumas situações. Diferentemente de algumas decisões tomadas nas bibliotecas Java mais antigas, estas estranhezas não foram acidentais, mas decisões cuidadosamente tomadas com base na redução da complexidade. Pode ser que você precise de um pouco de tempo para se sentir confortável com alguns aspectos da biblioteca, mas eu penso que você se verá rapidamente adquirindo e usando estas novas ferramentas.Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
The Java 2 container library takes the issue of holding your objects and divides it into two distinct concepts:
|
| [en-usa] [Traduzir] [+ Trad.] |
We will first look at the general features of containers, then go into details, and finally learn why there are different versions of some containers and how to choose between them. Comentários (em inglês) Printing containers |
| [en-usa] [Traduzir] [+ Trad.] |
Unlike arrays, the containers print nicely without any help. Heres an example that also introduces you to the basic types of containers: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:PrintingContainers.java
// Containers print themselves automatically.
import com.bruceeckel.simpletest.*;
import java.util.*;
public class PrintingContainers {
private static Test monitor = new Test();
static Collection fill(Collection c) {
c.add("dog");
c.add("dog");
c.add("cat");
return c;
}
static Map fill(Map m) {
m.put("dog", "Bosco");
m.put("dog", "Spot");
m.put("cat", "Rags");
return m;
}
public static void main(String[] args) {
System.out.println(fill(new ArrayList()));
System.out.println(fill(new HashSet()));
System.out.println(fill(new HashMap()));
monitor.expect(new String[] {
"[dog, dog, cat]",
"[dog, cat]",
"{dog=Spot, cat=Rags}"
});
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
As mentioned before, there are two basic categories in the Java container library. The distinction is based on the number of items that are held in each location of the container. The Collection category only holds one item in each location (the name is a bit misleading, because entire container libraries are often called collections). It includes the List, which holds a group of items in a specified sequence, and the Set, which only allows the addition of one item of each type. The ArrayList is a type of List, and HashSet is a type of Set. To add items to any Collection, theres an add( ) method. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
The Map holds key-value pairs, rather like a mini database. The preceding example uses one flavor of Map, the HashMap. If you have a Map that associates states with their capitals and you want to know the capital of Ohio, you look it upalmost as if you were indexing into an array. (Maps are also called associative arrays.) To add elements to a Map, theres a put( ) method that takes a key and a value as arguments. The example only shows adding elements and does not look up the elements after theyre added. That will be shown later. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
The overloaded fill( ) methods fill Collections and Maps, respectively. If you look at the output, you can see that the default printing behavior (provided via the containers various toString( ) methods) produces quite readable results, so no additional printing support is necessary as it was with arrays. A Collection is printed surrounded by square brackets, with each element separated by a comma. A Map is surrounded by curly braces, with each key and value associated with an equal sign (keys on the left, values on the right). Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
You can also immediately see the basic behavior of the different containers. The List holds the objects exactly as they are entered, without any reordering or editing. The Set, however, only accepts one of each object, and it uses its own internal ordering method (in general, you are only concerned with whether or not something is a member of the Set, not the order in which it appearsfor that youd use a List). And the Map also only accepts one of each type of item, based on the key, and it also has its own internal ordering and does not care about the order in which you enter the items. If maintaining the insertion sequence is important, you can use a LinkedHashSet or LinkedHashMap. Comentários (em inglês) Filling containers |
| [en-usa] [Traduzir] [+ Trad.] |
Although the problem of printing the containers is taken care of, filling containers suffers from the same deficiency as java.util.Arrays. Just like Arrays, there is a companion class called Collections containing static utility methods, including one called fill( ). This fill( ) also just duplicates a single object reference throughout the container, and also only works for List objects and not Sets or Maps: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:FillingLists.java
// The Collections.fill() method.
import com.bruceeckel.simpletest.*;
import java.util.*;
public class FillingLists {
private static Test monitor = new Test();
public static void main(String[] args) {
List list = new ArrayList();
for(int i = 0; i < 10; i++)
list.add("");
Collections.fill(list, "Hello");
System.out.println(list);
monitor.expect(new String[] {
"[Hello, Hello, Hello, Hello, Hello, " +
"Hello, Hello, Hello, Hello, Hello]"
});
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
This method is made even less useful by the fact that it can only replace elements that are already in the List and will not add new elements. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
To be able to create interesting examples, here is a complementary Collections2 library (part of com.bruceeckel.util for convenience) with a fill( ) method that uses a generator to add elements and allows you to specify the number of elements you want to add( ). The Generator interface defined previously will work for Collections, but the Map requires its own generator interface since a pair of objects (one key and one value) must be produced by each call to next( ). Here is the Pair class: |
| [en-usa] [Traduzir] [+ Trad.] |
//: com:bruceeckel:util:Pair.java
package com.bruceeckel.util;
public class Pair {
public Object key, value;
public Pair(Object k, Object v) {
key = k;
value = v;
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
Next, the generator interface that produces the Pair: |
| [en-usa] [Traduzir] [+ Trad.] |
//: com:bruceeckel:util:MapGenerator.java
package com.bruceeckel.util;
public interface MapGenerator { Pair next(); } ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
With these, a set of utilities for working with the container classes can be developed: |
| [en-usa] [Traduzir] [+ Trad.] |
//: com:bruceeckel:util:Collections2.java
// To fill any type of container using a generator object.
package com.bruceeckel.util;
import java.util.*;
public class Collections2 {
// Fill an array using a generator:
public static void
fill(Collection c, Generator gen, int count) {
for(int i = 0; i < count; i++)
c.add(gen.next());
}
public static void
fill(Map m, MapGenerator gen, int count) {
for(int i = 0; i < count; i++) {
Pair p = gen.next();
m.put(p.key, p.value);
}
}
public static class
RandStringPairGenerator implements MapGenerator {
private Arrays2.RandStringGenerator gen;
public RandStringPairGenerator(int len) {
gen = new Arrays2.RandStringGenerator(len);
}
public Pair next() {
return new Pair(gen.next(), gen.next());
}
}
// Default object so you don't have to create your own:
public static RandStringPairGenerator rsp =
new RandStringPairGenerator(10);
public static class
StringPairGenerator implements MapGenerator {
private int index = -1;
private String[][] d;
public StringPairGenerator(String[][] data) {
d = data;
}
public Pair next() {
// Force the index to wrap:
index = (index + 1) % d.length;
return new Pair(d[index][0], d[index][1]);
}
public StringPairGenerator reset() {
index = -1;
return this;
}
}
// Use a predefined dataset:
public static StringPairGenerator geography =
new StringPairGenerator(CountryCapitals.pairs);
// Produce a sequence from a 2D array:
public static class StringGenerator implements Generator{
private String[][] d;
private int position;
private int index = -1;
public StringGenerator(String[][] data, int pos) {
d = data;
position = pos;
}
public Object next() {
// Force the index to wrap:
index = (index + 1) % d.length;
return d[index][position];
}
public StringGenerator reset() {
index = -1;
return this;
}
}
// Use a predefined dataset:
public static StringGenerator countries =
new StringGenerator(CountryCapitals.pairs, 0);
public static StringGenerator capitals =
new StringGenerator(CountryCapitals.pairs, 1);
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
Both versions of fill( ) take an argument that determines the number of items to add to the container. In addition, there are two generators for the map: RandStringPairGenerator, which creates any number of pairs of gibberish Strings with length determined by the constructor argument; and StringPairGenerator, which produces pairs of Strings given a two-dimensional array of String. The StringGenerator also takes a two-dimensional array of String but generates single items rather than Pairs. The static rsp, geography, countries, and capitals objects provide prebuilt generators, the last three using all the countries of the world and their capitals. Note that if you try to create more pairs than are available, the generators will loop around to the beginning, and if you are putting the pairs into a Map, the duplicates will just be ignored. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Here is the predefined dataset, which consists of country names and their capitals: |
| [en-usa] [Traduzir] [+ Trad.] |
//: com:bruceeckel:util:CountryCapitals.java
package com.bruceeckel.util;
public class CountryCapitals {
public static final String[][] pairs = {
// Africa
{"ALGERIA","Algiers"}, {"ANGOLA","Luanda"},
{"BENIN","Porto-Novo"}, {"BOTSWANA","Gaberone"},
{"BURKINA FASO","Ouagadougou"},
{"BURUNDI","Bujumbura"},
{"CAMEROON","Yaounde"}, {"CAPE VERDE","Praia"},
{"CENTRAL AFRICAN REPUBLIC","Bangui"},
{"CHAD","N'djamena"}, {"COMOROS","Moroni"},
{"CONGO","Brazzaville"}, {"DJIBOUTI","Dijibouti"},
{"EGYPT","Cairo"}, {"EQUATORIAL GUINEA","Malabo"},
{"ERITREA","Asmara"}, {"ETHIOPIA","Addis Ababa"},
{"GABON","Libreville"}, {"THE GAMBIA","Banjul"},
{"GHANA","Accra"}, {"GUINEA","Conakry"},
{"GUINEA","-"}, {"BISSAU","Bissau"},
{"COTE D'IVOIR (IVORY COAST)","Yamoussoukro"},
{"KENYA","Nairobi"}, {"LESOTHO","Maseru"},
{"LIBERIA","Monrovia"}, {"LIBYA","Tripoli"},
{"MADAGASCAR","Antananarivo"}, {"MALAWI","Lilongwe"},
{"MALI","Bamako"}, {"MAURITANIA","Nouakchott"},
{"MAURITIUS","Port Louis"}, {"MOROCCO","Rabat"},
{"MOZAMBIQUE","Maputo"}, {"NAMIBIA","Windhoek"},
{"NIGER","Niamey"}, {"NIGERIA","Abuja"},
{"RWANDA","Kigali"},
{"SAO TOME E PRINCIPE","Sao Tome"},
{"SENEGAL","Dakar"}, {"SEYCHELLES","Victoria"},
{"SIERRA LEONE","Freetown"}, {"SOMALIA","Mogadishu"},
{"SOUTH AFRICA","Pretoria/Cape Town"},
{"SUDAN","Khartoum"},
{"SWAZILAND","Mbabane"}, {"TANZANIA","Dodoma"},
{"TOGO","Lome"}, {"TUNISIA","Tunis"},
{"UGANDA","Kampala"},
{"DEMOCRATIC REPUBLIC OF THE CONGO (ZAIRE)",
"Kinshasa"},
{"ZAMBIA","Lusaka"}, {"ZIMBABWE","Harare"},
// Asia
{"AFGHANISTAN","Kabul"}, {"BAHRAIN","Manama"},
{"BANGLADESH","Dhaka"}, {"BHUTAN","Thimphu"},
{"BRUNEI","Bandar Seri Begawan"},
{"CAMBODIA","Phnom Penh"},
{"CHINA","Beijing"}, {"CYPRUS","Nicosia"},
{"INDIA","New Delhi"}, {"INDONESIA","Jakarta"},
{"IRAN","Tehran"}, {"IRAQ","Baghdad"},
{"ISRAEL","Tel Aviv"}, {"JAPAN","Tokyo"},
{"JORDAN","Amman"}, {"KUWAIT","Kuwait City"},
{"LAOS","Vientiane"}, {"LEBANON","Beirut"},
{"MALAYSIA","Kuala Lumpur"}, {"THE MALDIVES","Male"},
{"MONGOLIA","Ulan Bator"},
{"MYANMAR (BURMA)","Rangoon"},
{"NEPAL","Katmandu"}, {"NORTH KOREA","P'yongyang"},
{"OMAN","Muscat"}, {"PAKISTAN","Islamabad"},
{"PHILIPPINES","Manila"}, {"QATAR","Doha"},
{"SAUDI ARABIA","Riyadh"}, {"SINGAPORE","Singapore"},
{"SOUTH KOREA","Seoul"}, {"SRI LANKA","Colombo"},
{"SYRIA","Damascus"},
{"TAIWAN (REPUBLIC OF CHINA)","Taipei"},
{"THAILAND","Bangkok"}, {"TURKEY","Ankara"},
{"UNITED ARAB EMIRATES","Abu Dhabi"},
{"VIETNAM","Hanoi"}, {"YEMEN","Sana'a"},
// Australia and Oceania
{"AUSTRALIA","Canberra"}, {"FIJI","Suva"},
{"KIRIBATI","Bairiki"},
{"MARSHALL ISLANDS","Dalap-Uliga-Darrit"},
{"MICRONESIA","Palikir"}, {"NAURU","Yaren"},
{"NEW ZEALAND","Wellington"}, {"PALAU","Koror"},
{"PAPUA NEW GUINEA","Port Moresby"},
{"SOLOMON ISLANDS","Honaira"}, {"TONGA","Nuku'alofa"},
{"TUVALU","Fongafale"}, {"VANUATU","< Port-Vila"},
{"WESTERN SAMOA","Apia"},
// Eastern Europe and former USSR
{"ARMENIA","Yerevan"}, {"AZERBAIJAN","Baku"},
{"BELARUS (BYELORUSSIA)","Minsk"},
{"GEORGIA","Tbilisi"},
{"KAZAKSTAN","Almaty"}, {"KYRGYZSTAN","Alma-Ata"},
{"MOLDOVA","Chisinau"}, {"RUSSIA","Moscow"},
{"TAJIKISTAN","Dushanbe"}, {"TURKMENISTAN","Ashkabad"},
{"UKRAINE","Kyiv"}, {"UZBEKISTAN","Tashkent"},
// Europe
{"ALBANIA","Tirana"}, {"ANDORRA","Andorra la Vella"},
{"AUSTRIA","Vienna"}, {"BELGIUM","Brussels"},
{"BOSNIA","-"}, {"HERZEGOVINA","Sarajevo"},
{"CROATIA","Zagreb"}, {"CZECH REPUBLIC","Prague"},
{"DENMARK","Copenhagen"}, {"ESTONIA","Tallinn"},
{"FINLAND","Helsinki"}, {"FRANCE","Paris"},
{"GERMANY","Berlin"}, {"GREECE","Athens"},
{"HUNGARY","Budapest"}, {"ICELAND","Reykjavik"},
{"IRELAND","Dublin"}, {"ITALY","Rome"},
{"LATVIA","Riga"}, {"LIECHTENSTEIN","Vaduz"},
{"LITHUANIA","Vilnius"}, {"LUXEMBOURG","Luxembourg"},
{"MACEDONIA","Skopje"}, {"MALTA","Valletta"},
{"MONACO","Monaco"}, {"MONTENEGRO","Podgorica"},
{"THE NETHERLANDS","Amsterdam"}, {"NORWAY","Oslo"},
{"POLAND","Warsaw"}, {"PORTUGAL","Lisbon"},
{"ROMANIA","Bucharest"}, {"SAN MARINO","San Marino"},
{"SERBIA","Belgrade"}, {"SLOVAKIA","Bratislava"},
{"SLOVENIA","Ljujiana"}, {"SPAIN","Madrid"},
{"SWEDEN","Stockholm"}, {"SWITZERLAND","Berne"},
{"UNITED KINGDOM","London"}, {"VATICAN CITY","---"},
// North and Central America
{"ANTIGUA AND BARBUDA","Saint John's"},
{"BAHAMAS","Nassau"},
{"BARBADOS","Bridgetown"}, {"BELIZE","Belmopan"},
{"CANADA","Ottawa"}, {"COSTA RICA","San Jose"},
{"CUBA","Havana"}, {"DOMINICA","Roseau"},
{"DOMINICAN REPUBLIC","Santo Domingo"},
{"EL SALVADOR","San Salvador"},
{"GRENADA","Saint George's"},
{"GUATEMALA","Guatemala City"},
{"HAITI","Port-au-Prince"},
{"HONDURAS","Tegucigalpa"}, {"JAMAICA","Kingston"},
{"MEXICO","Mexico City"}, {"NICARAGUA","Managua"},
{"PANAMA","Panama City"}, {"ST. KITTS","-"},
{"NEVIS","Basseterre"}, {"ST. LUCIA","Castries"},
{"ST. VINCENT AND THE GRENADINES","Kingstown"},
{"UNITED STATES OF AMERICA","Washington, D.C."},
// South America
{"ARGENTINA","Buenos Aires"},
{"BOLIVIA","Sucre (legal)/La Paz(administrative)"},
{"BRAZIL","Brasilia"}, {"CHILE","Santiago"},
{"COLOMBIA","Bogota"}, {"ECUADOR","Quito"},
{"GUYANA","Georgetown"}, {"PARAGUAY","Asuncion"},
{"PERU","Lima"}, {"SURINAME","Paramaribo"},
{"TRINIDAD AND TOBAGO","Port of Spain"},
{"URUGUAY","Montevideo"}, {"VENEZUELA","Caracas"},
};
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
This is simply a two-dimensional array of String data.[56] Heres a simple test using the fill( ) methods and generators: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:FillTest.java
import com.bruceeckel.util.*;
import java.util.*;
public class FillTest {
private static Generator sg =
new Arrays2.RandStringGenerator(7);
public static void main(String[] args) {
List list = new ArrayList();
Collections2.fill(list, sg, 25);
System.out.println(list + "\n");
List list2 = new ArrayList();
Collections2.fill(list2, Collections2.capitals, 25);
System.out.println(list2 + "\n");
Set set = new HashSet();
Collections2.fill(set, sg, 25);
System.out.println(set + "\n");
Map m = new HashMap();
Collections2.fill(m, Collections2.rsp, 25);
System.out.println(m + "\n");
Map m2 = new HashMap();
Collections2.fill(m2, Collections2.geography, 25);
System.out.println(m2);
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
With these tools you can easily test the various containers by filling them with interesting data. Comentários (em inglês)
Container disadvantage:
|
| [en-usa] [Traduzir] [+ Trad.] |
The disadvantage to using the Java containers is that you lose type information when you put an object into a container. This happens because the programmer of that container class had no idea what specific type you wanted to put in the container, and making the container hold only your type would prevent it from being a general-purpose tool. So instead, the container holds references to Object, which is the root of all the classes, so it holds any type. (Of course, this doesnt include primitive types, since they arent real objects, and thus, are not inherited from anything.) This is a great solution, except: Comentários (em inglês)
|
| [en-usa] [Traduzir] [+ Trad.] |
On the up side, Java wont let you misuse the objects that you put into a container. If you throw a dog into a container of cats and then try to treat everything in the container as a cat, youll get a RuntimeException when you pull the dog reference out of the cat container and try to cast it to a cat. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Heres an example using the basic workhorse container, ArrayList. For starters, you can think of ArrayList as an array that automatically expands itself. Using an ArrayList is straightforward: create one, put objects in using add( ), and later get them out with get( ) using an indexjust like you would with an array, but without the square brackets.[57] ArrayList also has a method size( ) to let you know how many elements have been added so you dont inadvertently run off the end and cause an exception. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
First, Cat and Dog classes are created: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:Cat.java
package c11;
public class Cat {
private int catNumber;
public Cat(int i) { catNumber = i; }
public void id() {
System.out.println("Cat #" + catNumber);
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:Dog.java
package c11;
public class Dog {
private int dogNumber;
public Dog(int i) { dogNumber = i; }
public void id() {
System.out.println("Dog #" + dogNumber);
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
Cats and Dogs are placed into the container, then pulled out: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:CatsAndDogs.java
// Simple container example.
// {ThrowsException}
package c11;
import java.util.*;
public class CatsAndDogs {
public static void main(String[] args) {
List cats = new ArrayList();
for(int i = 0; i < 7; i++)
cats.add(new Cat(i));
// Not a problem to add a dog to cats:
cats.add(new Dog(7));
for(int i = 0; i < cats.size(); i++)
((Cat)cats.get(i)).id();
// Dog is detected only at run time
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
The classes Cat and Dog are distinct; they have nothing in common except that they are Objects. (If you dont explicitly say what class youre inheriting from, you automatically inherit from Object.) Since ArrayList holds Objects, you can not only put Cat objects into this container using the ArrayList method add( ), but you can also add Dog objects without complaint at either compile time or run time. When you go to fetch out what you think are Cat objects using the ArrayList method get( ), you get back a reference to an object that you must cast to a Cat. Then you need to surround the entire expression with parentheses to force the evaluation of the cast before calling the id( ) method for Cat; otherwise, youll get a syntax error. Then, at run time, when you try to cast the Dog object to a Cat, youll get an exception. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
This is more than just an annoyance. Its something that can create difficult-to-find bugs. If one part (or several parts) of a program inserts objects into a container, and you discover only in a separate part of the program through an exception that a bad object was placed in the container, then you must find out where the bad insert occurred. Most of the time this isnt a problem, but you should be aware of the possibility. Comentários (em inglês) Sometimes it works anyway |
| [en-usa] [Traduzir] [+ Trad.] |
It turns out that in some cases things seem to work correctly without casting back to your original type. One case is quite special: The String class has some extra help from the compiler to make it work smoothly. Whenever the compiler expects a String object and it hasnt got one, it will automatically call the toString( ) method thats defined in Object and can be overridden by any Java class. This method produces the desired String object, which is then used wherever it is wanted. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Thus, all you need to do to make objects of your class print is to override the toString( ) method, as shown in the following example: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:Mouse.java
// Overriding toString().
public class Mouse {
private int mouseNumber;
public Mouse(int i) { mouseNumber = i; }
// Override Object.toString():
public String toString() {
return "This is Mouse #" + mouseNumber;
}
public int getNumber() { return mouseNumber; }
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:MouseTrap.java
public class MouseTrap {
static void caughtYa(Object m) {
Mouse mouse = (Mouse)m; // Cast from Object
System.out.println("Mouse: " + mouse.getNumber());
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:WorksAnyway.java
// In special cases, things just seem to work correctly.
import com.bruceeckel.simpletest.*;
import java.util.*;
public class WorksAnyway {
private static Test monitor = new Test();
public static void main(String[] args) {
List mice = new ArrayList();
for(int i = 0; i < 3; i++)
mice.add(new Mouse(i));
for(int i = 0; i < mice.size(); i++) {
// No cast necessary, automatic
// call to Object.toString():
System.out.println("Free mouse: " + mice.get(i));
MouseTrap.caughtYa(mice.get(i));
}
monitor.expect(new String[] {
"Free mouse: This is Mouse #0",
"Mouse: 0",
"Free mouse: This is Mouse #1",
"Mouse: 1",
"Free mouse: This is Mouse #2",
"Mouse: 2"
});
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
You can see toString( ) overridden in Mouse. In the second for loop in main( ) you find the statement: |
| [en-usa] [Traduzir] [+ Trad.] |
System.out.println("Free mouse: " + mice.get(i)); |
| [en-usa] [Traduzir] [+ Trad.] |
After the + sign the compiler expects to see a String object. get( ) produces an Object, so to get the desired String, the compiler implicitly calls toString( ). Unfortunately, you can work this kind of magic only with String; it isnt available for any other type. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
A second approach to hiding the cast has been placed inside MouseTrap. The caughtYa( ) method accepts not a Mouse, but an Object, which it then casts to a Mouse. This is quite presumptuous, of course, since by accepting an Object, anything could be passed to the method. However, if the cast is incorrectif you passed the wrong typeyoull get an exception at run time. This is not as good as compile-time checking, but its still robust. Note that in the use of this method: |
| [en-usa] [Traduzir] [+ Trad.] |
MouseTrap.caughtYa(mice.get(i)); |
| [en-usa] [Traduzir] [+ Trad.] |
no cast is necessary. Comentários (em inglês)
Making a type-conscious
ArrayList
|
| [en-usa] [Traduzir] [+ Trad.] |
You might not want to give up on this issue just yet. A more ironclad solution is to create a new class using the ArrayList, such that it will accept only your type and produce only your type: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:MouseList.java
// A type-conscious List.
import java.util.*;
public class MouseList {
private List list = new ArrayList();
public void add(Mouse m) { list.add(m); }
public Mouse get(int index) {
return (Mouse)list.get(index);
}
public int size() { return list.size(); }
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
Heres a test for the new container: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:MouseListTest.java
import com.bruceeckel.simpletest.*;
public class MouseListTest {
private static Test monitor = new Test();
public static void main(String[] args) {
MouseList mice = new MouseList();
for(int i = 0; i < 3; i++)
mice.add(new Mouse(i));
for(int i = 0; i < mice.size(); i++)
MouseTrap.caughtYa(mice.get(i));
monitor.expect(new String[] {
"Mouse: 0",
"Mouse: 1",
"Mouse: 2"
});
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
This is similar to the previous example, except that the new MouseList class has a private member of type ArrayList and methods just like ArrayList. However, it doesnt accept and produce generic Objects, only Mouse objects. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Note that if MouseList had instead been inherited from ArrayList, the add(Mouse) method would simply overload the existing add(Object), and there would still be no restriction on what type of objects could be added, and you wouldnt get the desired results. Using composition, the MouseList simply uses the ArrayList, performing some activities before passing the responsibility for the rest of the operation on to the ArrayList. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Because a MouseList will accept only a Mouse, if you say: |
| [en-usa] [Traduzir] [+ Trad.] |
mice.add(new Pigeon()); |
| [en-usa] [Traduzir] [+ Trad.] |
you will get an error message at compile time. This approach, while more tedious from a coding standpoint, will tell you immediately if youre using a type improperly. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Note that no cast is necessary when using get( ); its always a Mouse. Comentários (em inglês) Parameterized types |
| [en-usa] [Traduzir] [+ Trad.] |
This kind of problem isnt isolated. There are numerous cases in which you need to create new types based on other types, and in which it is useful to have specific type information at compile time. This is the concept of a parameterized type. In C++, this is directly supported by the language using templates. It is likely that Java JDK 1.5 will provide generics, the Java version of parameterized types. Comentários (em inglês) Iterators |
| [en-usa] [Traduzir] [+ Trad.] |
In any container class, you must have a way to put things in and a way to get things out. After all, thats the primary job of a containerto hold things. In the ArrayList, add( ) is the way that you insert objects, and get( ) is one way to get things out. ArrayList is quite flexible; you can select anything at any time, and select multiple elements at once using different indexes. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
If you want to start thinking at a higher level, theres a drawback: You need to know the exact type of the container in order to use it. This might not seem bad at first, but what if you start out using an ArrayList, and later on you discover that because of the features you need in the container you actually need to use a Set instead? Or suppose youd like to write a piece of generic code that doesnt know or care what type of container its working with, so that it could be used on different types of containers without rewriting that code? Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
The concept of an iterator (yet another design pattern) can be used to achieve this abstraction. An iterator is an object whose job is to move through a sequence of objects and select each object in that sequence without the client programmer knowing or caring about the underlying structure of that sequence. In addition, an iterator is usually whats called a light-weight object: one thats cheap to create. For that reason, youll often find seemingly strange constraints for iterators; for example, some iterators can move in only one direction. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
The Java Iterator is an example of an iterator with these kinds of constraints. Theres not much you can do with one except: Comentários (em inglês)
|
| [en-usa] [Traduzir] [+ Trad.] |
Thats all. Its a simple implementation of an iterator, but still powerful (and theres a more sophisticated ListIterator for Lists). To see how it works, lets revisit the CatsAndDogs.java program from earlier in this chapter. In the original version, the method get( ) was used to select each element, but in the following modified version, an Iterator is used: Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:CatsAndDogs2.java
// Simple container with Iterator.
package c11;
import com.bruceeckel.simpletest.*;
import java.util.*;
public class CatsAndDogs2 {
private static Test monitor = new Test();
public static void main(String[] args) {
List cats = new ArrayList();
for(int i = 0; i < 7; i++)
cats.add(new Cat(i));
Iterator e = cats.iterator();
while(e.hasNext())
((Cat)e.next()).id();
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
You can see that the last few lines now use an Iterator to step through the sequence instead of a for loop. With the Iterator, you dont need to worry about the number of elements in the container. Thats taken care of for you by hasNext( ) and next( ). Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
As another example, consider the creation of a general-purpose printing method: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:Printer.java
// Using an Iterator.
import java.util.*;
public class Printer {
static void printAll(Iterator e) {
while(e.hasNext())
System.out.println(e.next());
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
Look closely at printAll( ). Note that theres no information about the type of sequence. All you have is an Iterator, and thats all you need to know about the sequence: that you can get the next object, and that you can know when youre at the end. This idea of taking a container of objects and passing through it to perform an operation on each one is powerful and will be seen throughout this book. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
The example is even more generic, since it implicitly uses the Object.toString( ) method. The println( ) method is overloaded for all the primitive types as well as Object; in each case, a String is automatically produced by calling the appropriate toString( ) method. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Although its unnecessary, you can be more explicit using a cast, which has the effect of calling toString( ): |
| [en-usa] [Traduzir] [+ Trad.] |
System.out.println((String)e.next()); |
| [en-usa] [Traduzir] [+ Trad.] |
In general, however, youll want to do something more than call Object methods, so youll run up against the type-casting issue again. You must assume youve gotten an Iterator to a sequence of the particular type youre interested in, and cast the resulting objects to that type (getting a run-time exception if youre wrong). Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
We can test it by printing Hamsters: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:Hamster.java
public class Hamster {
private int hamsterNumber;
public Hamster(int hamsterNumber) {
this.hamsterNumber = hamsterNumber;
}
public String toString() {
return "This is Hamster #" + hamsterNumber;
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:HamsterMaze.java
// Using an Iterator.
import com.bruceeckel.simpletest.*;
import java.util.*;
public class HamsterMaze {
private static Test monitor = new Test();
public static void main(String[] args) {
List list = new ArrayList();
for(int i = 0; i < 3; i++)
list.add(new Hamster(i));
Printer.printAll(list.iterator());
monitor.expect(new String[] {
"This is Hamster #0",
"This is Hamster #1",
"This is Hamster #2"
});
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
You could write printAll( ) to accept a Collection object instead of an Iterator, but the latter provides better decoupling. Comentários (em inglês) Unintended recursion |
| [en-usa] [Traduzir] [+ Trad.] |
Because (like every other class) the Java standard containers are inherited from Object, they contain a toString( ) method. This has been overridden so that they can produce a String representation of themselves, including the objects they hold. Inside ArrayList, for example, the toString( ) steps through the elements of the ArrayList and calls toString( ) for each one. Suppose youd like to print the address of your class. It seems to make sense to simply refer to this (in particular, C++ programmers are prone to this approach): |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:InfiniteRecursion.java
// Accidental recursion.
// {RunByHand}
import java.util.*;
public class InfiniteRecursion {
public String toString() {
return " InfiniteRecursion address: " + this + "\n";
}
public static void main(String[] args) {
List v = new ArrayList();
for(int i = 0; i < 10; i++)
v.add(new InfiniteRecursion());
System.out.println(v);
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
If you simply create an InfiniteRecursion object and then print it, youll get an endless sequence of exceptions. This is also true if you place the InfiniteRecursion objects in an ArrayList and print that ArrayList as shown here. Whats happening is automatic type conversion for Strings. When you say: |
| [en-usa] [Traduzir] [+ Trad.] |
"InfiniteRecursion address: " + this |
| [en-usa] [Traduzir] [+ Trad.] |
The compiler sees a String followed by a + and something thats not a String, so it tries to convert this to a String. It does this conversion by calling toString( ), which produces a recursive call. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
If you really do want to print the address of the object in this case, the solution is to call the Object toString( ) method, which does just that. So instead of saying this, youd say super.toString( ). Comentários (em inglês) Container taxonomy |
| [en-usa] [Traduzir] [+ Trad.] |
Collections and Maps may be implemented in different ways according to your programming needs. Its helpful to look at a diagram of the Java containers (as of JDK 1.4): |

| [en-usa] [Traduzir] [+ Trad.] |
This diagram can be a bit overwhelming at first, but youll see that there are really only three container componentsMap, List, and Setand only two or three implementations of each one. The containers that you will generally use most of the time have heavy black lines around them. When you see this, the containers are not so daunting. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
The dotted boxes represent interfaces, the dashed boxes represent abstract classes, and the solid boxes are regular (concrete) classes. The dotted-line arrows indicate that a particular class is implementing an interface (or in the case of an abstract class, partially implementing that interface). The solid arrows show that a class can produce objects of the class the arrow is pointing to. For example, any Collection can produce an Iterator and a List can produce a ListIterator (as well as an ordinary Iterator, since List is inherited from Collection). Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
The interfaces that are concerned with holding objects are Collection, List, Set, and Map. Ideally, youll write most of your code to talk to these interfaces, and the only place where youll specify the precise type youre using is at the point of creation. So you can create a List like this: Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
List x = new LinkedList(); |
| [en-usa] [Traduzir] [+ Trad.] |
Of course, you can also decide to make x a LinkedList (instead of a generic List) and carry the precise type information around with x. The beauty (and the intent) of using the interface is that if you decide you want to change your implementation, all you need to do is change it at the point of creation, like this: |
| [en-usa] [Traduzir] [+ Trad.] |
List x = new ArrayList(); |
| [en-usa] [Traduzir] [+ Trad.] |
The rest of your code can remain untouched (some of this genericity can also be achieved with iterators). Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
In the class hierarchy, you can see a number of classes whose names begin with Abstract, and these can seem a bit confusing at first. They are simply tools that partially implement a particular interface. If you were making your own Set, for example, you wouldnt start with the Set interface and implement all the methods; instead, youd inherit from AbstractSet and do the minimal necessary work to make your new class. However, the containers library contains enough functionality to satisfy your needs virtually all the time. So for our purposes, you can ignore any class that begins with Abstract. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Therefore, when you look at the diagram, youre really concerned with only those interfaces at the top of the diagram and the concrete classes (those with solid boxes around them). Youll typically make an object of a concrete class, upcast it to the corresponding interface, and then use the interface throughout the rest of your code. In addition, you do not need to consider the legacy elements when writing new code. Therefore, the diagram can be greatly simplified to look like this: |

| [en-usa] [Traduzir] [+ Trad.] |
Now it only includes the interfaces and classes that you will encounter on a regular basis, and also the elements that we will focus on in this chapter. Note that the WeakHashMap and the JDK 1.4 IdentityHashMap are not included on this diagram, because they are special-purpose tools that you will rarely use. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Heres a simple example that fills a Collection (represented here with an ArrayList) with String objects and then prints each element in the Collection: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:SimpleCollection.java
// A simple example using Java 2 Collections.
import com.bruceeckel.simpletest.*;
import java.util.*;
public class SimpleCollection {
private static Test monitor = new Test();
public static void main(String[] args) {
// Upcast because we just want to
// work with Collection features
Collection c = new ArrayList();
for(int i = 0; i < 10; i++)
c.add(Integer.toString(i));
Iterator it = c.iterator();
while(it.hasNext())
System.out.println(it.next());
monitor.expect(new String[] {
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
});
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
The first line in main( ) creates an ArrayList object and then upcasts it to a Collection. Since this example uses only the Collection methods, any object of a class inherited from Collection would work, but ArrayList is the typical workhorse Collection. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
The add( ) method, as its name suggests, puts a new element in the Collection. However, the documentation carefully states that add( ) ensures that this Container contains the specified element. This is to allow for the meaning of Set, which adds the element only if it isnt already there. With an ArrayList, or any sort of List, add( ) always means put it in, because Lists dont care if there are duplicates. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
All Collections can produce an Iterator via their iterator( ) method. Here, an Iterator is created and used to traverse the Collection, printing each element. Comentários (em inglês) Collection functionality |
| [en-usa] [Traduzir] [+ Trad.] |
The following table shows everything you can do with a Collection (not including the methods that automatically come through with Object), and thus, everything you can do with a Set or a List. (List also has additional functionality.) Maps are not inherited from Collection and will be treated separately.
|
| [en-usa] [Traduzir] [+ Trad.] |
Notice that theres no get( ) method for random-access element selection. Thats because Collection also includes Set, which maintains its own internal ordering (and thus makes random-access lookup meaningless). Thus, if you want to examine the elements of a Collection, you must use an iterator. |
| [en-usa] [Traduzir] [+ Trad.] |
The following example demonstrates all of these methods. Again, these work with anything that implements Collection, but an ArrayList is used as a kind of least-common denominator: Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:Collection1.java
// Things you can do with all Collections.
import com.bruceeckel.simpletest.*;
import java.util.*;
import com.bruceeckel.util.*;
public class Collection1 {
private static Test monitor = new Test();
public static void main(String[] args) {
Collection c = new ArrayList();
Collections2.fill(c, Collections2.countries, 5);
c.add("ten");
c.add("eleven");
System.out.println(c);
// Make an array from the List:
Object[] array = c.toArray();
// Make a String array from the List:
String[] str = (String[])c.toArray(new String[1]);
// Find max and min elements; this means
// different things depending on the way
// the Comparable interface is implemented:
System.out.println("Collections.max(c) = " +
Collections.max(c));
System.out.println("Collections.min(c) = " +
Collections.min(c));
// Add a Collection to another Collection
Collection c2 = new ArrayList();
Collections2.fill(c2, Collections2.countries, 5);
c.addAll(c2);
System.out.println(c);
c.remove(CountryCapitals.pairs[0][0]);
System.out.println(c);
c.remove(CountryCapitals.pairs[1][0]);
System.out.println(c);
// Remove all components that are
// in the argument collection:
c.removeAll(c2);
System.out.println(c);
c.addAll(c2);
System.out.println(c);
// Is an element in this Collection?
String val = CountryCapitals.pairs[3][0];
System.out.println("c.contains(" + val + ") = "
+ c.contains(val));
// Is a Collection in this Collection?
System.out.println(
"c.containsAll(c2) = " + c.containsAll(c2));
Collection c3 = ((List)c).subList(3, 5);
// Keep all the elements that are in both
// c2 and c3 (an intersection of sets):
c2.retainAll(c3);
System.out.println(c);
// Throw away all the elements
// in c2 that also appear in c3:
c2.removeAll(c3);
System.out.println("c.isEmpty() = " + c.isEmpty());
c = new ArrayList();
Collections2.fill(c, Collections2.countries, 5);
System.out.println(c);
c.clear(); // Remove all elements
System.out.println("after c.clear():");
System.out.println(c);
monitor.expect(new String[] {
"[ALGERIA, ANGOLA, BENIN, BOTSWANA, BURKINA FASO, " +
"ten, eleven]",
"Collections.max(c) = ten",
"Collections.min(c) = ALGERIA",
"[ALGERIA, ANGOLA, BENIN, BOTSWANA, BURKINA FASO, " +
"ten, eleven, BURUNDI, CAMEROON, CAPE VERDE, " +
"CENTRAL AFRICAN REPUBLIC, CHAD]",
"[ANGOLA, BENIN, BOTSWANA, BURKINA FASO, ten, " +
"eleven, BURUNDI, CAMEROON, CAPE VERDE, " +
"CENTRAL AFRICAN REPUBLIC, CHAD]",
"[BENIN, BOTSWANA, BURKINA FASO, ten, eleven, " +
"BURUNDI, CAMEROON, CAPE VERDE, " +
"CENTRAL AFRICAN REPUBLIC, CHAD]",
"[BENIN, BOTSWANA, BURKINA FASO, ten, eleven]",
"[BENIN, BOTSWANA, BURKINA FASO, ten, eleven, " +
"BURUNDI, CAMEROON, CAPE VERDE, " +
"CENTRAL AFRICAN REPUBLIC, CHAD]",
"c.contains(BOTSWANA) = true",
"c.containsAll(c2) = true",
"[BENIN, BOTSWANA, BURKINA FASO, ten, eleven, " +
"BURUNDI, CAMEROON, CAPE VERDE, " +
"CENTRAL AFRICAN REPUBLIC, CHAD]",
"c.isEmpty() = false",
"[COMOROS, CONGO, DJIBOUTI, EGYPT, " +
"EQUATORIAL GUINEA]",
"after c.clear():",
"[]"
});
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
ArrayLists are created containing different sets of data and upcast to Collection objects, so its clear that nothing other than the Collection interface is being used. main( ) uses simple exercises to show all of the methods in Collection. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
The following sections describe the various implementations of List, Set, and Map and indicate in each case (with an asterisk) which one should be your default choice. Youll notice that the legacy classes Vector, Stack, and Hashtable are not included, because in all cases there are preferred classes within the Java 2 Containers. Comentários (em inglês)
List
functionality
|
| [en-usa] [Traduzir] [+ Trad.] |
A coleção básica List é muito simples de utilizar, como você pode ver com ArrayList. A maior parte do tempo você estará utilizando tão somente add( ) para inserir objetos, get( ) para remove--los um a um , e iterator( ) para obter um Iterator para a sequencia. Mas existe ainda um punhado de outros métodos que podem ser úteis.Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
In addition, there are actually two types of List: the basic ArrayList, which excels at randomly accessing elements, and the much more powerful LinkedList, which is not designed for fast random access, but has a much more general set of methods.
|
| [en-usa] [Traduzir] [+ Trad.] |
The methods in the following example each cover a different group of activities: things that every list can do (basicTest( )), moving around with an Iterator (iterMotion( )) versus changing things with an Iterator (iterManipulation( )), seeing the effects of List manipulation (testVisual( )), and operations available only to LinkedLists. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:List1.java
// Things you can do with Lists.
import java.util.*;
import com.bruceeckel.util.*;
public class List1 {
public static List fill(List a) {
Collections2.countries.reset();
Collections2.fill(a, Collections2.countries, 10);
return a;
}
private static boolean b;
private static Object o;
private static int i;
private static Iterator it;
private static ListIterator lit;
public static void basicTest(List a) {
a.add(1, "x"); // Add at location 1
a.add("x"); // Add at end
// Add a collection:
a.addAll(fill(new ArrayList()));
// Add a collection starting at location 3:
a.addAll(3, fill(new ArrayList()));
b = a.contains("1"); // Is it in there?
// Is the entire collection in there?
b = a.containsAll(fill(new ArrayList()));
// Lists allow random access, which is cheap
// for ArrayList, expensive for LinkedList:
o = a.get(1); // Get object at location 1
i = a.indexOf("1"); // Tell index of object
b = a.isEmpty(); // Any elements inside?
it = a.iterator(); // Ordinary Iterator
lit = a.listIterator(); // ListIterator
lit = a.listIterator(3); // Start at loc 3
i = a.lastIndexOf("1"); // Last match
a.remove(1); // Remove location 1
a.remove("3"); // Remove this object
a.set(1, "y"); // Set location 1 to "y"
// Keep everything that's in the argument
// (the intersection of the two sets):
a.retainAll(fill(new ArrayList()));
// Remove everything that's in the argument:
a.removeAll(fill(new ArrayList()));
i = a.size(); // How big is it?
a.clear(); // Remove all elements
}
public static void iterMotion(List a) {
ListIterator it = a.listIterator();
b = it.hasNext();
b = it.hasPrevious();
o = it.next();
i = it.nextIndex();
o = it.previous();
i = it.previousIndex();
}
public static void iterManipulation(List a) {
ListIterator it = a.listIterator();
it.add("47");
// Must move to an element after add():
it.next();
// Remove the element that was just produced:
it.remove();
// Must move to an element after remove():
it.next();
// Change the element that was just produced:
it.set("47");
}
public static void testVisual(List a) {
System.out.println(a);
List b = new ArrayList();
fill(b);
System.out.print("b = ");
System.out.println(b);
a.addAll(b);
a.addAll(fill(new ArrayList()));
System.out.println(a);
// Insert, remove, and replace elements
// using a ListIterator:
ListIterator x = a.listIterator(a.size()/2);
x.add("one");
System.out.println(a);
System.out.println(x.next());
x.remove();
System.out.println(x.next());
x.set("47");
System.out.println(a);
// Traverse the list backwards:
x = a.listIterator(a.size());
while(x.hasPrevious())
System.out.print(x.previous() + " ");
System.out.println();
System.out.println("testVisual finished");
}
// There are some things that only LinkedLists can do:
public static void testLinkedList() {
LinkedList ll = new LinkedList();
fill(ll);
System.out.println(ll);
// Treat it like a stack, pushing:
ll.addFirst("one");
ll.addFirst("two");
System.out.println(ll);
// Like "peeking" at the top of a stack:
System.out.println(ll.getFirst());
// Like popping a stack:
System.out.println(ll.removeFirst());
System.out.println(ll.removeFirst());
// Treat it like a queue, pulling elements
// off the tail end:
System.out.println(ll.removeLast());
// With the above operations, it's a dequeue!
System.out.println(ll);
}
public static void main(String[] args) {
// Make and fill a new list each time:
basicTest(fill(new LinkedList()));
basicTest(fill(new ArrayList()));
iterMotion(fill(new LinkedList()));
iterMotion(fill(new ArrayList()));
iterManipulation(fill(new LinkedList()));
iterManipulation(fill(new ArrayList()));
testVisual(fill(new LinkedList()));
testLinkedList();
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
In basicTest( ) and iterMotion( ) the calls are made in order to show the proper syntax, and although the return value is captured, it is not used. In some cases, the return value isnt captured at all. You should look up the full usage of each of these methods in the JDK documentation from java.sun.com before you use them. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Remember that a container is only a storage cabinet to hold objects. If that cabinet solves all of your needs, it doesnt really matter how it is implemented (a basic concept with most types of objects). If youre working in a programming environment that has built-in overhead due to other factors, then the cost difference between an ArrayList and a LinkedList might not matter. You might need only one type of sequence. You can even imagine the perfect container abstraction, which can automatically change its underlying implementation according to the way it is used. Comentários (em inglês) Making a stack from a LinkedList |
| [en-usa] [Traduzir] [+ Trad.] |
A stack is sometimes referred to as a last-in, first-out (LIFO) container. That is, whatever you push on the stack last is the first item you can pop out. Like all of the other containers in Java, what you push and pop are Objects, so you must cast what you pop, unless youre just using Object behavior. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
The LinkedList has methods that directly implement stack functionality, so you can also just use a LinkedList rather than making a stack class. However, a stack class can sometimes tell the story better: Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:StackL.java
// Making a stack from a LinkedList.
import com.bruceeckel.simpletest.*;
import java.util.*;
import com.bruceeckel.util.*;
public class StackL {
private static Test monitor = new Test();
private LinkedList list = new LinkedList();
public void push(Object v) { list.addFirst(v); }
public Object top() { return list.getFirst(); }
public Object pop() { return list.removeFirst(); }
public static void main(String[] args) {
StackL stack = new StackL();
for(int i = 0; i < 10; i++)
stack.push(Collections2.countries.next());
System.out.println(stack.top());
System.out.println(stack.top());
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
monitor.expect(new String[] {
"CHAD",
"CHAD",
"CHAD",
"CENTRAL AFRICAN REPUBLIC",
"CAPE VERDE"
});
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
If you want only stack behavior, inheritance is inappropriate here because it would produce a class with all the rest of the LinkedList methods (youll see later that this very mistake was made by the Java 1.0 library designers with Stack). Comentários (em inglês) Making a queue from a LinkedList |
| [en-usa] [Traduzir] [+ Trad.] |
A queue is a first-in, first-out (FIFO) container. That is, you put things in at one end and pull them out at the other. So the order in which you put them in will be the same order that they come out. LinkedList has methods to support queue behavior, so these can be used in a Queue class: Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:Queue.java
// Making a queue from a LinkedList.
import com.bruceeckel.simpletest.*;
import java.util.*;
public class Queue {
private static Test monitor = new Test();
private LinkedList list = new LinkedList();
public void put(Object v) { list.addFirst(v); }
public Object get() { return list.removeLast(); }
public boolean isEmpty() { return list.isEmpty(); }
public static void main(String[] args) {
Queue queue = new Queue();
for(int i = 0; i < 10; i++)
queue.put(Integer.toString(i));
while(!queue.isEmpty())
System.out.println(queue.get());
monitor.expect(new String[] {
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9"
});
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
You can also easily create a deque (double-ended queue) from a LinkedList. This is like a queue, but you can add and remove elements from either end. Comentários (em inglês)
Set
functionality
|
| [en-usa] [Traduzir] [+ Trad.] |
Set has exactly the same interface as Collection, so there isnt any extra functionality like there is with the two different Lists. Instead, the Set is exactly a Collectionit just has different behavior. (This is the ideal use of inheritance and polymorphism: to express different behavior.) A Set refuses to hold more than one instance of each object value (what constitutes the value of an object is more complex, as you shall see).
|
| [en-usa] [Traduzir] [+ Trad.] |
The following example does not show everything you can do with a Set, since the interface is the same as Collection, and so was exercised in the previous example. Instead, this demonstrates the behavior that makes a Set unique: Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:Set1.java
// Things you can do with Sets.
import com.bruceeckel.simpletest.*;
import java.util.*;
public class Set1 {
private static Test monitor = new Test();
static void fill(Set s) {
s.addAll(Arrays.asList(
"one two three four five six seven".split(" ")));
}
public static void test(Set s) {
// Strip qualifiers from class name:
System.out.println(
s.getClass().getName().replaceAll("\\w+\\.", ""));
fill(s); fill(s); fill(s);
System.out.println(s); // No duplicates!
// Add another set to this one:
s.addAll(s);
s.add("one");
s.add("one");
s.add("one");
System.out.println(s);
// Look something up:
System.out.println("s.contains(\"one\"): " +
s.contains("one"));
}
public static void main(String[] args) {
test(new HashSet());
test(new TreeSet());
test(new LinkedHashSet());
monitor.expect(new String[] {
"HashSet",
"[one, two, five, four, three, seven, six]",
"[one, two, five, four, three, seven, six]",
"s.contains(\"one\"): true",
"TreeSet",
"[five, four, one, seven, six, three, two]",
"[five, four, one, seven, six, three, two]",
"s.contains(\"one\"): true",
"LinkedHashSet",
"[one, two, three, four, five, six, seven]",
"[one, two, three, four, five, six, seven]",
"s.contains(\"one\"): true"
});
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
Duplicate values are added to the Set, but when it is printed, youll see that the Set has accepted only one instance of each value. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
When you run this program, youll notice that the order maintained by the HashSet is different from TreeSet and LinkedHashSet, since each has a different way of storing elements so they can be located later. (TreeSet keeps elements sorted into a red-black tree data structure, whereas HashSet uses a hashing function, which is designed specifically for rapid lookups. LinkedHashSet uses hashing internally for lookup speed, but appears to maintain elements in insertion order using a linked list.) When creating your own types, be aware that a Set needs a way to maintain a storage order, which means that you must implement the Comparable interface and define the compareTo( ) method. Heres an example: Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:Set2.java
// Putting your own type in a Set.
import com.bruceeckel.simpletest.*;
import java.util.*;
public class Set2 {
private static Test monitor = new Test();
public static Set fill(Set a, int size) {
for(int i = 0; i < size; i++)
a.add(new MyType(i));
return a;
}
public static void test(Set a) {
fill(a, 10);
fill(a, 10); // Try to add duplicates
fill(a, 10);
a.addAll(fill(new TreeSet(), 10));
System.out.println(a);
}
public static void main(String[] args) {
test(new HashSet());
test(new TreeSet());
test(new LinkedHashSet());
monitor.expect(new String[] {
"[2 , 4 , 9 , 8 , 6 , 1 , 3 , 7 , 5 , 0 ]",
"[9 , 8 , 7 , 6 , 5 , 4 , 3 , 2 , 1 , 0 ]",
"[0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ]"
});
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
The form for the definitions for equals( ) and hashCode( ) will be described later in this chapter. You must define an equals( ) in both cases, but the hashCode( ) is absolutely necessary only if the class will be placed in a HashSet (which is likely, since that should generally be your first choice as a Set implementation). However, as a programming style, you should always override hashCode( ) when you override equals( ). This process will be fully detailed later in this chapter. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
In the compareTo( ), note that I did not use the simple and obvious form return i-i2. Although this is a common programming error, it would only work properly if i and i2 were unsigned ints (if Java had an unsigned keyword, which it does not). It breaks for Javas signed int, which is not big enough to represent the difference of two signed ints. If i is a large positive integer and j is a large negative integer, i-j will overflow and return a negative value, which will not work. Comentários (em inglês) SortedSet |
| [en-usa] [Traduzir] [+ Trad.] |
If you have a SortedSet (of which TreeSet is the only one available), the elements are guaranteed to be in sorted order, which allows additional functionality to be provided with these methods in the SortedSet interface: Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Comparator comparator( ): Produces the Comparator used for this Set, or null for natural ordering. |
| [en-usa] [Traduzir] [+ Trad.] |
Object first( ): Produces the lowest element. |
| [en-usa] [Traduzir] [+ Trad.] |
Object last( ): Produces the highest element. |
| [en-usa] [Traduzir] [+ Trad.] |
SortedSet subSet(fromElement, toElement): Produces a view of this Set with elements from fromElement, inclusive, to toElement, exclusive. |
| [en-usa] [Traduzir] [+ Trad.] |
SortedSet headSet(toElement): Produces a view of this Set with elements less than toElement. |
| [en-usa] [Traduzir] [+ Trad.] |
SortedSet tailSet(fromElement): Produces a view of this Set with elements greater than or equal to fromElement. |
| [en-usa] [Traduzir] [+ Trad.] |
Heres a simple demonstration: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:SortedSetDemo.java
// What you can do with a TreeSet.
import com.bruceeckel.simpletest.*;
import java.util.*;
public class SortedSetDemo {
private static Test monitor = new Test();
public static void main(String[] args) {
SortedSet sortedSet = new TreeSet(Arrays.asList(
"one two three four five six seven eight".split(" ")));
System.out.println(sortedSet);
Object
low = sortedSet.first(),
high = sortedSet.last();
System.out.println(low);
System.out.println(high);
Iterator it = sortedSet.iterator();
for(int i = 0; i <= 6; i++) {
if(i == 3) low = it.next();
if(i == 6) high = it.next();
else it.next();
}
System.out.println(low);
System.out.println(high);
System.out.println(sortedSet.subSet(low, high));
System.out.println(sortedSet.headSet(high));
System.out.println(sortedSet.tailSet(low));
monitor.expect(new String[] {
"[eight, five, four, one, seven, six, three, two]",
"eight",
"two",
"one",
"two",
"[one, seven, six, three]",
"[eight, five, four, one, seven, six, three]",
"[one, seven, six, three, two]"
});
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
Note that SortedSet means sorted according to the comparison function of the object, not insertion order.
Map
functionality
|
| [en-usa] [Traduzir] [+ Trad.] |
An ArrayList allows you to select from a sequence of objects using a number, so in a sense it associates numbers to objects. But what if youd like to select from a sequence of objects using some other criterion? A stack is an example. Its selection criterion is the last thing pushed on the stack. A powerful twist on this idea of selecting from a sequence is termed a map, a dictionary, or an associative array (you saw a simple example of this in AssociativeArray.java in the previous chapter). Conceptually, it seems like an ArrayList, but instead of looking up objects using a number, you look them up using another object! This is a key technique in programming. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
The concept shows up in Java as the Map interface. The put(Object key, Object value) method adds a value (the thing you want) and associates it with a key (the thing you look it up with). get(Object key) produces the value given the corresponding key. You can also test a Map to see if it contains a key or a value with containsKey( ) and containsValue( ). Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
The standard Java library contains different types of Maps: HashMap, TreeMap, LinkedHashMap, WeakHashMap, and IdentityHashMap. The all have the same basic Map interface, but they differ in behaviors including efficiency, order in which the pairs are held and presented, how long the objects are held by the map, and how key equality is determined. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
A big issue with maps is performance. If you look at what must be done for a get( ), it seems pretty slow to search through (for example) an ArrayList for the key. This is where HashMap speeds things up. Instead of a slow search for the key, it uses a special value called a hash code. The hash code is a way to take some information in the object in question and turn it into a relatively unique int for that object. All Java objects can produce a hash code, and hashCode( ) is a method in the root class Object. A HashMap takes the hashCode( ) of the object and uses it to quickly hunt for the key. This results in a dramatic performance improvement.[58] Comentários (em inglês)
|
| [en-usa] [Traduzir] [+ Trad.] |
Hashing is the most commonly used way to store elements in a map. Sometimes youll need to know the details of how hashing works, so well look at that a little later. |
| [en-usa] [Traduzir] [+ Trad.] |
The following example uses the Collections2.fill( ) method and the test data sets that were previously defined: Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:Map1.java
// Things you can do with Maps.
import java.util.*;
import com.bruceeckel.util.*;
public class Map1 {
private static Collections2.StringPairGenerator geo =
Collections2.geography;
private static Collections2.RandStringPairGenerator
rsp = Collections2.rsp;
// Producing a Set of the keys:
public static void printKeys(Map map) {
System.out.print("Size = " + map.size() + ", ");
System.out.print("Keys: ");
System.out.println(map.keySet());
}
public static void test(Map map) {
// Strip qualifiers from class name:
System.out.println(
map.getClass().getName().replaceAll("\\w+\\.", ""));
Collections2.fill(map, geo, 25);
// Map has 'Set' behavior for keys:
Collections2.fill(map, geo.reset(), 25);
printKeys(map);
// Producing a Collection of the values:
System.out.print("Values: ");
System.out.println(map.values());
System.out.println(map);
String key = CountryCapitals.pairs[4][0];
String value = CountryCapitals.pairs[4][1];
System.out.println("map.containsKey(\"" + key +
"\"): " + map.containsKey(key));
System.out.println("map.get(\"" + key + "\"): "
+ map.get(key));
System.out.println("map.containsValue(\""
+ value + "\"): " + map.containsValue(value));
Map map2 = new TreeMap();
Collections2.fill(map2, rsp, 25);
map.putAll(map2);
printKeys(map);
key = map.keySet().iterator().next().toString();
System.out.println("First key in map: " + key);
map.remove(key);
printKeys(map);
map.clear();
System.out.println("map.isEmpty(): " + map.isEmpty());
Collections2.fill(map, geo.reset(), 25);
// Operations on the Set change the Map:
map.keySet().removeAll(map.keySet());
System.out.println("map.isEmpty(): " + map.isEmpty());
}
public static void main(String[] args) {
test(new HashMap());
test(new TreeMap());
test(new LinkedHashMap());
test(new IdentityHashMap());
test(new WeakHashMap());
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
The printKeys( ) and printValues( ) methods are not only useful utilities, they also demonstrate how to produce Collection views of a Map. The keySet( ) method produces a Set backed by the keys in the Map. Similar treatment is given to values( ), which produces a Collection containing all the values in the Map. (Note that keys must be unique, but values may contain duplicates.) Since these Collections are backed by the Map, any changes in a Collection will be reflected in the associated Map. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
The rest of the program provides simple examples of each Map operation and tests each type of Map. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
As an example of the use of a HashMap, consider a program to check the randomness of Javas Random class. Ideally, it would produce a perfect distribution of random numbers, but to test this you need to generate a bunch of random numbers and count the ones that fall in the various ranges. A HashMap is perfect for this, since it associates objects with objects (in this case, the value object contains the number produced by Math.random( ) along with the number of times that number appears): |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:Statistics.java
// Simple demonstration of HashMap.
import java.util.*;
class Counter {
int i = 1;
public String toString() { return Integer.toString(i); }
}
public class Statistics {
private static Random rand = new Random();
public static void main(String[] args) {
Map hm = new HashMap();
for(int i = 0; i < 10000; i++) {
// Produce a number between 0 and 20:
Integer r = new Integer(rand.nextInt(20));
if(hm.containsKey(r))
((Counter)hm.get(r)).i++;
else
hm.put(r, new Counter());
}
System.out.println(hm);
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
In main( ), each time a random number is generated it is wrapped inside an Integer object so that reference can be used with the HashMap. (You cant use a primitive with a containeronly an object reference.) The containsKey( ) method checks to see if this key is already in the container (that is, has the number been found already?). If so, the get( ) method produces the associated value for the key, which in this case is a Counter object. The value i inside the counter is incremented to indicate that one more of this particular random number has been found. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
If the key has not been found yet, the method put( ) will place a new key-value pair into the HashMap. Since Counter automatically initializes its variable i to one when its created, it indicates the first occurrence of this particular random number. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
To display the HashMap, it is simply printed. The HashMap toString( ) method moves through all the key-value pairs and calls the toString( ) for each one. The Integer.toString( ) is predefined, and you can see the toString( ) for Counter. The output from one run (with some line breaks added) is: |
| [en-usa] [Traduzir] [+ Trad.] |
{15=529, 4=488, 19=518, 8=487, 11=501, 16=487, 18=507, 3=524, 7=474, 12=485, 17=493, 2=490, 13=540, 9=453, 6=512, 1=466, 14=522, 10=471, 5=522, 0=531} |
| [en-usa] [Traduzir] [+ Trad.] |
You might wonder at the necessity of the class Counter, which seems like it doesnt even have the functionality of the wrapper class Integer. Why not use int or Integer? Well, you cant use an int because all of the containers can hold only Object references. After youve seen containers, the wrapper classes might begin to make a little more sense to you, since you cant put any of the primitive types in containers. However, the only thing you can do with the Java wrappers is initialize them to a particular value and read that value. That is, theres no way to change a value once a wrapper object has been created. This makes the Integer wrapper immediately useless to solve the problem, so were forced to create a new class that does satisfy the need. Comentários (em inglês) SortedMap |
| [en-usa] [Traduzir] [+ Trad.] |
If you have a SortedMap (of which TreeMap is the only one available), the keys are guaranteed to be in sorted order, which allows additional functionality to be provided with these methods in the SortedMap interface: Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Comparator comparator( ): Produces the comparator used for this Map, or null for natural ordering. |
| [en-usa] [Traduzir] [+ Trad.] |
Object firstKey( ): Produces the lowest key. |
| [en-usa] [Traduzir] [+ Trad.] |
Object lastKey( ): Produces the highest key. |
| [en-usa] [Traduzir] [+ Trad.] |
SortedMap subMap(fromKey, toKey): Produces a view of this Map with keys from fromKey, inclusive, to toKey, exclusive. |
| [en-usa] [Traduzir] [+ Trad.] |
SortedMap headMap(toKey): Produces a view of this Map with keys less than toKey. |
| [en-usa] [Traduzir] [+ Trad.] |
SortedMap tailMap(fromKey): Produces a view of this Map with keys greater than or equal to fromKey. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Heres an example thats similar to SortedSetDemo.java and shows this additional behavior of TreeMaps: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:SimplePairGenerator.java
import com.bruceeckel.util.*;
//import java.util.*;
public class SimplePairGenerator implements MapGenerator {
public Pair[] items = {
new Pair("one", "A"), new Pair("two", "B"),
new Pair("three", "C"), new Pair("four", "D"),
new Pair("five", "E"), new Pair("six", "F"),
new Pair("seven", "G"), new Pair("eight", "H"),
new Pair("nine", "I"), new Pair("ten", "J")
};
private int index = -1;
public Pair next() {
index = (index + 1) % items.length;
return items[index];
}
public static SimplePairGenerator gen =
new SimplePairGenerator();
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:SortedMapDemo.java
// What you can do with a TreeMap.
import com.bruceeckel.simpletest.*;
import com.bruceeckel.util.*;
import java.util.*;
public class SortedMapDemo {
private static Test monitor = new Test();
public static void main(String[] args) {
TreeMap sortedMap = new TreeMap();
Collections2.fill(
sortedMap, SimplePairGenerator.gen, 10);
System.out.println(sortedMap);
Object
low = sortedMap.firstKey(),
high = sortedMap.lastKey();
System.out.println(low);
System.out.println(high);
Iterator it = sortedMap.keySet().iterator();
for(int i = 0; i <= 6; i++) {
if(i == 3) low = it.next();
if(i == 6) high = it.next();
else it.next();
}
System.out.println(low);
System.out.println(high);
System.out.println(sortedMap.subMap(low, high));
System.out.println(sortedMap.headMap(high));
System.out.println(sortedMap.tailMap(low));
monitor.expect(new String[] {
"{eight=H, five=E, four=D, nine=I, one=A, seven=G," +
" six=F, ten=J, three=C, two=B}",
"eight",
"two",
"nine",
"ten",
"{nine=I, one=A, seven=G, six=F}",
"{eight=H, five=E, four=D, nine=I, " +
"one=A, seven=G, six=F}",
"{nine=I, one=A, seven=G, six=F, " +
"ten=J, three=C, two=B}"
});
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
Here, the pairs are stored by key-sorted order. Because there is a sense of order in the TreeMap, the concept of location makes sense, so you can have first and last elements and submaps. Comentários (em inglês) LinkedHashMap |
| [en-usa] [Traduzir] [+ Trad.] |
The LinkedHashMap hashes everything for speed, but also produces the pairs in insertion order during a traversal (println( ) iterates through the map, so you see the results of traversal). In addition, a LinkedHashMap can be configured in the constructor to use a least-recently-used (LRU) algorithm based on accesses, so elements that havent been accessed (and thus are candidates for removal) appear at the front of the list. This allows easy creation of programs that do periodic cleanup in order to save space. Heres a simple example showing both features: Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:LinkedHashMapDemo.java
// What you can do with a LinkedHashMap.
import com.bruceeckel.simpletest.*;
import com.bruceeckel.util.*;
import java.util.*;
public class LinkedHashMapDemo {
private static Test monitor = new Test();
public static void main(String[] args) {
LinkedHashMap linkedMap = new LinkedHashMap();
Collections2.fill(
linkedMap, SimplePairGenerator.gen, 10);
System.out.println(linkedMap);
// Least-recently used order:
linkedMap = new LinkedHashMap(16, 0.75f, true);
Collections2.fill(
linkedMap, SimplePairGenerator.gen, 10);
System.out.println(linkedMap);
for(int i = 0; i < 7; i++) // Cause accesses:
linkedMap.get(SimplePairGenerator.gen.items[i].key);
System.out.println(linkedMap);
linkedMap.get(SimplePairGenerator.gen.items[0].key);
System.out.println(linkedMap);
monitor.expect(new String[] {
"{one=A, two=B, three=C, four=D, five=E, " +
"six=F, seven=G, eight=H, nine=I, ten=J}",
"{one=A, two=B, three=C, four=D, five=E, " +
"six=F, seven=G, eight=H, nine=I, ten=J}",
"{eight=H, nine=I, ten=J, one=A, two=B, " +
"three=C, four=D, five=E, six=F, seven=G}",
"{eight=H, nine=I, ten=J, two=B, three=C, " +
"four=D, five=E, six=F, seven=G, one=A}"
});
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
You can see from the output that the pairs are indeed traversed in insertion order, even for the LRU version. However, after the first seven items (only) are accessed, the last three items move to the front of the list. Then, when one is accessed again, it moves to the back of the list. Comentários (em inglês)
Hashing and hash
codes
|
| [en-usa] [Traduzir] [+ Trad.] |
In Statistics.java, a standard library class (Integer) was used as a key for the HashMap. It worked because it has all the necessary wiring to make it behave correctly as a key. But a common pitfall occurs with HashMaps when you create your own classes to be used as keys. For example, consider a weather predicting system that matches Groundhog objects to Prediction objects. It seems fairly straightforwardyou create the two classes, and use Groundhog as the key and Prediction as the value: Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:Groundhog.java
// Looks plausible, but doesn't work as a HashMap key.
public class Groundhog {
protected int number;
public Groundhog(int n) { number = n; }
public String toString() {
return "Groundhog #" + number;
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:Prediction.java
// Predicting the weather with groundhogs.
public class Prediction {
private boolean shadow = Math.random() > 0.5;
public String toString() {
if(shadow)
return "Six more weeks of Winter!";
else
return "Early Spring!";
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:SpringDetector.java
// What will the weather be?
import com.bruceeckel.simpletest.*;
import java.util.*;
import java.lang.reflect.*;
public class SpringDetector {
private static Test monitor = new Test();
// Uses a Groundhog or class derived from Groundhog:
public static void
detectSpring(Class groundHogClass) throws Exception {
Constructor ghog = groundHogClass.getConstructor(
new Class[] {int.class});
Map map = new HashMap();
for(int i = 0; i < 10; i++)
map.put(ghog.newInstance(
new Object[]{ new Integer(i) }), new Prediction());
System.out.println("map = " + map + "\n");
Groundhog gh = (Groundhog)
ghog.newInstance(new Object[]{ new Integer(3) });
System.out.println("Looking up prediction for " + gh);
if(map.containsKey(gh))
System.out.println((Prediction)map.get(gh));
else
System.out.println("Key not found: " + gh);
}
public static void main(String[] args) throws Exception {
detectSpring(Groundhog.class);
monitor.expect(new String[] {
"%% map = \\{(Groundhog #\\d=" +
"(Early Spring!|Six more weeks of Winter!)" +
"(, )?){10}\\}",
"",
"Looking up prediction for Groundhog #3",
"Key not found: Groundhog #3"
});
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
Each Groundhog is given an identity number, so you can look up a Prediction in the HashMap by saying, Give me the Prediction associated with Groundhog #3. The Prediction class contains a boolean that is initialized using Math.random( ) and a toString( ) that interprets the result for you. The detectSpring( ) method is created using reflection to instantiate and use the Class Groundhog or any derived class. This will come in handy when we inherit a new type of Groundhog to solve the problem demonstrated here. A HashMap is filled with Groundhogs and their associated Predictions. The HashMap is printed so that you can see it has been filled. Then a Groundhog with an identity number of 3 is used as a key to look up the prediction for Groundhog #3 (which you can see must be in the Map). Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
It seems simple enough, but it doesnt work. The problem is that Groundhog is inherited from the common root class Object (which is what happens if you dont specify a base class, thus all classes are ultimately inherited from Object). It is Objects hashCode( ) method that is used to generate the hash code for each object, and by default it just uses the address of its object. Thus, the first instance of Groundhog(3) does not produce a hash code equal to the hash code for the second instance of Groundhog(3) that we tried to use as a lookup. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
You might think that all you need to do is write an appropriate override for hashCode( ). But it still wont work until youve done one more thing: override the equals( ) that is also part of Object. equals( ) is used by the HashMap when trying to determine if your key is equal to any of the keys in the table. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
A proper equals( ) must satisfy the following five conditions: Comentários (em inglês)
|
| [en-usa] [Traduzir] [+ Trad.] |
Again, the default Object.equals( ) simply compares object addresses, so one Groundhog(3) is not equal to another Groundhog(3). Thus, to use your own classes as keys in a HashMap, you must override both hashCode( ) and equals( ), as shown in the following solution to the groundhog problem: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:Groundhog2.java
// A class that's used as a key in a HashMap
// must override hashCode() and equals().
public class Groundhog2 extends Groundhog {
public Groundhog2(int n) { super(n); }
public int hashCode() { return number; }
public boolean equals(Object o) {
return (o instanceof Groundhog2)
&& (number == ((Groundhog2)o).number);
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:SpringDetector2.java
// A working key.
import com.bruceeckel.simpletest.*;
import java.util.*;
public class SpringDetector2 {
private static Test monitor = new Test();
public static void main(String[] args) throws Exception {
SpringDetector.detectSpring(Groundhog2.class);
monitor.expect(new String[] {
"%% map = \\{(Groundhog #\\d=" +
"(Early Spring!|Six more weeks of Winter!)" +
"(, )?){10}\\}",
"",
"Looking up prediction for Groundhog #3",
"%% Early Spring!|Six more weeks of Winter!"
});
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
Groundhog2.hashCode( ) returns the groundhog number as a hash value. In this example, the programmer is responsible for ensuring that no two groundhogs exist with the same ID number. The hashCode( ) is not required to return a unique identifier (something youll understand better later in this chapter), but the equals( ) method must be able to strictly determine whether two objects are equivalent. Here, equals( ) is based on the groundhog number, so if two Groundhog2 objects exist as keys in the HashMap with the same groundhog number, it will fail. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Even though it appears that the equals( ) method is only checking to see whether the argument is an instance of Groundhog2 (using the instanceof keyword, which was explained in Chapter 10), the instanceof actually quietly does a second sanity check to see if the object is null, since instanceof produces false if the left-hand argument is null. Assuming its the correct type and not null, the comparison is based on the actual ghNumbers. You can see from the output that the behavior is now correct. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
When creating your own class to use in a HashSet, you must pay attention to the same issues as when it is used as a key in a HashMap. Comentários (em inglês) Understanding hashCode( ) |
| [en-usa] [Traduzir] [+ Trad.] |
The preceding example is only a start toward solving the problem correctly. It shows that if you do not override hashCode( ) and equals( ) for your key, the hashed data structure (HashSet, HashMap, LinkedHashSet, or LinkedHashMap) will not be able to deal with your key properly. However, to get a good solution for the problem you need to understand whats going on inside the hashed data structure. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
First, consider the motivation behind hashing: you want to look up an object using another object. But you can accomplish this with a TreeSet or TreeMap, too. Its also possible to implement your own Map. To do so, the Map.entrySet( ) method must be supplied to produce a set of Map.Entry objects. MPair will be defined as the new type of Map.Entry. In order for it to be placed in a TreeSet, it must implement equals( ) and be Comparable: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:MPair.java
// A new type of Map.Entry.
import java.util.*;
public class MPair implements Map.Entry, Comparable {
private Object key, value;
public MPair(Object k, Object v) {
key = k;
value = v;
}
public Object getKey() { return key; }
public Object getValue() { return value; }
public Object setValue(Object v) {
Object result = value;
value = v;
return result;
}
public boolean equals(Object o) {
return key.equals(((MPair)o).key);
}
public int compareTo(Object rv) {
return ((Comparable)key).compareTo(((MPair)rv).key);
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
Notice that the comparisons are only interested in the keys, so duplicate values are perfectly acceptable. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
The following example implements a Map using a pair of ArrayLists: Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:SlowMap.java
// A Map implemented with ArrayLists.
import com.bruceeckel.simpletest.*;
import java.util.*;
import com.bruceeckel.util.*;
public class SlowMap extends AbstractMap {
private static Test monitor = new Test();
private List
keys = new ArrayList(),
values = new ArrayList();
public Object put(Object key, Object value) {
Object result = get(key);
if(!keys.contains(key)) {
keys.add(key);
values.add(value);
} else
values.set(keys.indexOf(key), value);
return result;
}
public Object get(Object key) {
if(!keys.contains(key))
return null;
return values.get(keys.indexOf(key));
}
public Set entrySet() {
Set entries = new HashSet();
Iterator
ki = keys.iterator(),
vi = values.iterator();
while(ki.hasNext())
entries.add(new MPair(ki.next(), vi.next()));
return entries;
}
public String toString() {
StringBuffer s = new StringBuffer("{");
Iterator
ki = keys.iterator(),
vi = values.iterator();
while(ki.hasNext()) {
s.append(ki.next() + "=" + vi.next());
if(ki.hasNext()) s.append(", ");
}
s.append("}");
return s.toString();
}
public static void main(String[] args) {
SlowMap m = new SlowMap();
Collections2.fill(m, Collections2.geography, 15);
System.out.println(m);
monitor.expect(new String[] {
"{ALGERIA=Algiers, ANGOLA=Luanda, BENIN=Porto-Novo,"+
" BOTSWANA=Gaberone, BURKINA FASO=Ouagadougou, " +
"BURUNDI=Bujumbura, CAMEROON=Yaounde, " +
"CAPE VERDE=Praia, CENTRAL AFRICAN REPUBLIC=Bangui,"+
" CHAD=N'djamena, COMOROS=Moroni, " +
"CONGO=Brazzaville, DJIBOUTI=Dijibouti, " +
"EGYPT=Cairo, EQUATORIAL GUINEA=Malabo}"
});
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
| [en-usa] [Traduzir] [+ Trad.] |
This shows that its not that hard to produce a new type of Map. But as the name suggests, a SlowMap isnt very fast, so you probably wouldnt use it if you had an alternative available. The problem is in the lookup of the key; there is no order, so a simple linear search is used, which is the slowest way to look something up. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
The whole point of hashing is speed: Hashing allows the lookup to happen quickly. Since the bottleneck is in the speed of the key lookup, one of the solutions to the problem could be to keep the keys sorted and then use Collections.binarySearch( ) to perform the lookup (an exercise at the end of this chapter will walk you through this process). Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Hashing goes further by saying that all you want to do is to store the key somewhere so that it can be quickly found. As youve seen in this chapter, the fastest structure in which to store a group of elements is an array, so that will be used for representing the key information (note carefully that I said key information, and not the key itself). Also seen in this chapter was the fact that an array, once allocated, cannot be resized, so we have a problem: We want to be able to store any number of values in the Map, but if the number of keys is fixed by the array size, how can this be? Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
The answer is that the array will not hold the keys. From the key object, a number will be derived that will index into the array. This number is the hash code, produced by the hashCode( ) method (in computer science parlance, this is the hash function) defined in Object and presumably overridden by your class. To solve the problem of the fixed-size array, more than one key may produce the same index. That is, there may be collisions. Because of this, it doesnt matter how big the array is; each key object will land somewhere in that array. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
So the process of looking up a value starts by computing the hash code and using it to index into the array. If you could guarantee that there were no collisions (which could be possible if you have a fixed number of values) then youd have a perfect hashing function, but thats a special case. In all other cases, collisions are handled by external chaining: The array points not directly to a value, but instead to a list of values. These values are searched in a linear fashion using the equals( ) method. Of course, this aspect of the search is much slower, but if the hash function is good, there will only be a few values in each slot. So instead of searching through the entire list, you quickly jump to a slot where you have to compare a few entries to find the value. This is much faster, which is why the HashMap is so quick. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Knowing the basics of hashing, its possible to implement a simple hashed Map: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:SimpleHashMap.java
// A demonstration hashed Map.
import java.util.*;
import com.bruceeckel.util.*;
public class SimpleHashMap extends AbstractMap {
// Choose a prime number for the hash table
// size, to achieve a uniform distribution:
private static final int SZ = 997;
private LinkedList[] bucket = new LinkedList[SZ];
public Object put(Object key, Object value) {
Object result = null;
int index = key.hashCode() % SZ;
if(index < 0) index = -index;
if(bucket[index] == null)
bucket[index] = new LinkedList();
LinkedList pairs = bucket[index];
MPair pair = new MPair(key, value);
ListIterator it = pairs.listIterator();
boolean found = false;
while(it.hasNext()) {
Object iPair = it.next();
if(iPair.equals(pair)) {
result = ((MPair)iPair).getValue();
it.set(pair); // Replace old with new
found = true;
break;
}
}
if(!found)
bucket[index].add(pair);
return result;
}
public Object get(Object key) {
int index = key.hashCode() % SZ;
if(index < 0) index = -index;
if(bucket[index] == null) return null;
LinkedList pairs = bucket[index];
MPair match = new MPair(key, null);
ListIterator it = pairs.listIterator();
while(it.hasNext()) {
Object iPair = it.next();
if(iPair.equals(match))
return ((MPair)iPair).getValue();
}
return null;
}
public Set entrySet() {
Set entries = new HashSet();
for(int i = 0; i < bucket.length; i++) {
if(bucket[i] == null) continue;
Iterator it = bucket[i].iterator();
while(it.hasNext())
entries.add(it.next());
}
return entries;
}
public static void main(String[] args) {
SimpleHashMap m = new SimpleHashMap();
Collections2.fill(m, Collections2.geography, 25);
System.out.println(m);
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
Because the slots in a hash table are often referred to as buckets, the array that represents the actual table is called bucket. To promote even distribution, the number of buckets is typically a prime number.[59] Notice that it is an array of LinkedList, which automatically provides for collisions; each new item is simply added to the end of the list. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
The return value of put( ) is null or, if the key was already in the list, the old value associated with that key. The return value is result, which is initialized to null, but if a key is discovered in the list, then result is assigned to that key. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
For both put( ) and get( ), the first thing that happens is that the hashCode( ) is called for the key, and the result is forced to a positive number. Then it is forced to fit into the bucket array using the modulus operator and the size of the array. If that location is null, it means there are no elements that hash to that location, so a new LinkedList is created to hold the object that just did. However, the normal process is to look through the list to see if there are duplicates, and if there are, the old value is put into result and the new value replaces the old. The found flag keeps track of whether an old key-value pair was found and, if not, the new pair is appended to the end of the list. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
In get( ), youll see very similar code as that contained in put( ), but simpler. The index is calculated into the bucket array, and if a LinkedList exists, it is searched for a match. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
entrySet( ) must find and traverse all the lists, adding them to the result Set. Once this method has been created, the Map can be tested by filling it with values and then printing them. Comentários (em inglês) HashMap performance factors |
| [en-usa] [Traduzir] [+ Trad.] |
To understand the issues, some terminology is necessary: |
| [en-usa] [Traduzir] [+ Trad.] |
| [en-usa] [Traduzir] [+ Trad.] |
Initial capacity: The number of buckets when the table is created. HashMap and HashSet have constructors that allow you to specify the initial capacity. |
| [en-usa] [Traduzir] [+ Trad.] |
| [en-usa] [Traduzir] [+ Trad.] |
Load factor: size/capacity. A load factor of 0 is an empty table, 0.5 is a half-full table, etc. A lightly loaded table will have few collisions and so is optimal for insertions and lookups (but will slow down the process of traversing with an iterator). HashMap and HashSet have constructors that allow you to specify the load factor, which means that when this load factor is reached, the container will automatically increase the capacity (the number of buckets) by roughly doubling it and will redistribute the existing objects into the new set of buckets (this is called rehashing). Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
The default load factor used by HashMap is 0.75 (it doesnt rehash until the table is ¾ full). This seems to be a good trade-off between time and space costs. A higher load factor decreases the space required by the table but increases the lookup cost, which is important because lookup is what you do most of the time (including both get( ) and put( )). Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
If you know that youll be storing many entries in a HashMap, creating it with an appropriately large initial capacity will prevent the overhead of automatic rehashing.[60] Comentários (em inglês) Overriding hashCode( ) |
| [en-usa] [Traduzir] [+ Trad.] |
Now that you understand whats involved in the function of the HashMap, the issues involved in writing a hashCode( ) will make more sense. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
First of all, you dont have control of the creation of the actual value thats used to index into the array of buckets. That is dependent on the capacity of the particular HashMap object, and that capacity changes depending on how full the container is, and what the load factor is. The value produced by your hashCode( ) will be further processed in order to create the bucket index (in SimpleHashMap, the calculation is just a modulo by the size of the bucket array). Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
The most important factor in creating a hashCode( ) is that, regardless of when hashCode( ) is called, it produces the same value for a particular object every time it is called. If you end up with an object that produces one hashCode( ) value when it is put( ) into a HashMap and another during a get( ), you wont be able to retrieve the objects. So if your hashCode( ) depends on mutable data in the object, the user must be made aware that changing the data will effectively produce a different key by generating a different hashCode( ). Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
In addition, you will probably not want to generate a hashCode( ) that is based on unique object informationin particular, the value of this makes a bad hashCode( ) because then you cant generate a new identical key to the one used to put( ) the original key-value pair. This was the problem that occurred in SpringDetector.java, because the default implementation of hashCode( ) does use the object address. So youll want to use information in the object that identifies the object in a meaningful way. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
One example can be seen in the String class. Strings have the special characteristic that if a program has several String objects that contain identical character sequences, then those String objects all map to the same memory (the mechanism for this is described in Appendix A). So it makes sense that the hashCode( ) produced by two separate instances of new String(hello) should be identical. You can see this in the following program: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:StringHashCode.java
import com.bruceeckel.simpletest.*;
public class StringHashCode {
private static Test monitor = new Test();
public static void main(String[] args) {
System.out.println("Hello".hashCode());
System.out.println("Hello".hashCode());
monitor.expect(new String[] {
"69609650",
"69609650"
});
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
The hashCode( ) for String is clearly based on the contents of the String. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
So for a hashCode( ) to be effective, it must be fast and it must be meaningful; that is, it must generate a value based on the contents of the object. Remember that this value doesnt have to be uniqueyou should lean toward speed rather than uniquenessbut between hashCode( ) and equals( ), the identity of the object must be completely resolved. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Because the hashCode( ) is further processed before the bucket index is produced, the range of values is not important; it just needs to generate an int. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Theres one other factor: A good hashCode( ) should result in an even distribution of values. If the values tend to cluster, then the HashMap or HashSet will be more heavily loaded in some areas and will not be as fast as it could be with an evenly distributed hashing function. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
In Effective Java (Addison-Wesley 2001), Joshua Bloch gives a basic recipe for generating a decent hashCode( ):
|
| [en-usa] [Traduzir] [+ Trad.] |
Heres an example that follows these guidelines: |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:CountedString.java
// Creating a good hashCode().
import com.bruceeckel.simpletest.*;
import java.util.*;
public class CountedString {
private static Test monitor = new Test();
private static List created = new ArrayList();
private String s;
private int id = 0;
public CountedString(String str) {
s = str;
created.add(s);
Iterator it = created.iterator();
// Id is the total number of instances
// of this string in use by CountedString:
while(it.hasNext())
if(it.next().equals(s))
id++;
}
public String toString() {
return "String: " + s + " id: " + id +
" hashCode(): " + hashCode();
}
public int hashCode() {
// Very simple approach:
// return s.hashCode() * id;
// Using Joshua Bloch's recipe:
int result = 17;
result = 37*result + s.hashCode();
result = 37*result + id;
return result;
}
public boolean equals(Object o) {
return (o instanceof CountedString)
&& s.equals(((CountedString)o).s)
&& id == ((CountedString)o).id;
}
public static void main(String[] args) {
Map map = new HashMap();
CountedString[] cs = new CountedString[10];
for(int i = 0; i < cs.length; i++) {
cs[i] = new CountedString("hi");
map.put(cs[i], new Integer(i));
}
System.out.println(map);
for(int i = 0; i < cs.length; i++) {
System.out.println("Looking up " + cs[i]);
System.out.println(map.get(cs[i]));
}
monitor.expect(new String[] {
"{String: hi id: 4 hashCode(): 146450=3," +
" String: hi id: 10 hashCode(): 146456=9," +
" String: hi id: 6 hashCode(): 146452=5," +
" String: hi id: 1 hashCode(): 146447=0," +
" String: hi id: 9 hashCode(): 146455=8," +
" String: hi id: 8 hashCode(): 146454=7," +
" String: hi id: 3 hashCode(): 146449=2," +
" String: hi id: 5 hashCode(): 146451=4," +
" String: hi id: 7 hashCode(): 146453=6," +
" String: hi id: 2 hashCode(): 146448=1}",
"Looking up String: hi id: 1 hashCode(): 146447",
"0",
"Looking up String: hi id: 2 hashCode(): 146448",
"1",
"Looking up String: hi id: 3 hashCode(): 146449",
"2",
"Looking up String: hi id: 4 hashCode(): 146450",
"3",
"Looking up String: hi id: 5 hashCode(): 146451",
"4",
"Looking up String: hi id: 6 hashCode(): 146452",
"5",
"Looking up String: hi id: 7 hashCode(): 146453",
"6",
"Looking up String: hi id: 8 hashCode(): 146454",
"7",
"Looking up String: hi id: 9 hashCode(): 146455",
"8",
"Looking up String: hi id: 10 hashCode(): 146456",
"9"
});
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
CountedString includes a String and an id that represents the number of CountedString objects that contain an identical String. The counting is accomplished in the constructor by iterating through the static ArrayList where all the Strings are stored. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Both hashCode( ) and equals( ) produce results based on both fields; if they were just based on the String alone or the id alone, there would be duplicate matches for distinct values. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
In main( ), a bunch of CountedString objects are created, using the same String to show that the duplicates create unique values because of the count id. The HashMap is displayed so that you can see how it is stored internally (no discernible orders), and then each key is looked up individually to demonstrate that the lookup mechanism is working properly. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Writing a proper hashCode( ) and equals( ) for a new class can be tricky. You can find tools to help you do this in Apaches Jakarta Commons project at jakarta.apache.org/commons, under lang (this project also has many other potentially useful libraries, and appears to be the Java communitys answer to the C++ communitys www.boost.org). Holding references |
| [en-usa] [Traduzir] [+ Trad.] |
The java.lang.ref library contains a set of classes that allow greater flexibility in garbage collection. These classes are especially useful when you have large objects that may cause memory exhaustion. There are three classes inherited from the abstract class Reference: SoftReference, WeakReference, and PhantomReference. Each of these provides a different level of indirection for the garbage collector if the object in question is only reachable through one of these Reference objects. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
If an object is reachable, it means that somewhere in your program the object can be found. This could mean that you have an ordinary reference on the stack that goes right to the object, but you might also have a reference to an object that has a reference to the object in question; there could be many intermediate links. If an object is reachable, the garbage collector cannot release it because its still in use by your program. If an object isnt reachable, theres no way for your program to use it, so its safe to garbage collect that object. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
You use Reference objects when you want to continue to hold onto a reference to that object; you want to be able to reach that object, but you also want to allow the garbage collector to release that object. Thus, you have a way to go on using the object, but if memory exhaustion is imminent, you allow that object to be released. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
You accomplish this by using a Reference object as an intermediary between you and the ordinary reference, and there must be no ordinary references to the object (ones that are not wrapped inside Reference objects). If the garbage collector discovers that an object is reachable through an ordinary reference, it will not release that object. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
In the order of SoftReference, WeakReference, and PhantomReference, each one is weaker than the last and corresponds to a different level of reachability. Soft references are for implementing memory-sensitive caches. Weak references are for implementing canonicalizing mappingswhere instances of objects can be simultaneously used in multiple places in a program, to save storagethat do not prevent their keys (or values) from being reclaimed. Phantom references are for scheduling premortem cleanup actions in a more flexible way than is possible with the Java finalization mechanism. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
With SoftReferences and WeakReferences, you have a choice about whether to place them on a ReferenceQueue (the device used for premortem cleanup actions), but a PhantomReference can only be built on a ReferenceQueue. Heres a simple demonstration: Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:References.java
// Demonstrates Reference objects
import java.lang.ref.*;
class VeryBig {
private static final int SZ = 10000;
private double[] d = new double[SZ];
private String ident;
public VeryBig(String id) { ident = id; }
public String toString() { return ident; }
public void finalize() {
System.out.println("Finalizing " + ident);
}
}
public class References {
private static ReferenceQueue rq = new ReferenceQueue();
public static void checkQueue() {
Object inq = rq.poll();
if(inq != null)
System.out.println("In queue: " +
(VeryBig)((Reference)inq).get());
}
public static void main(String[] args) {
int size = 10;
// Or, choose size via the command line:
if(args.length > 0)
size = Integer.parseInt(args[0]);
SoftReference[] sa = new SoftReference[size];
for(int i = 0; i < sa.length; i++) {
sa[i] = new SoftReference(
new VeryBig("Soft " + i), rq);
System.out.println("Just created: " +
(VeryBig)sa[i].get());
checkQueue();
}
WeakReference[] wa = new WeakReference[size];
for(int i = 0; i < wa.length; i++) {
wa[i] = new WeakReference(
new VeryBig("Weak " + i), rq);
System.out.println("Just created: " +
(VeryBig)wa[i].get());
checkQueue();
}
SoftReference s =
new SoftReference(new VeryBig("Soft"));
WeakReference w =
new WeakReference(new VeryBig("Weak"));
System.gc();
PhantomReference[] pa = new PhantomReference[size];
for(int i = 0; i < pa.length; i++) {
pa[i] = new PhantomReference(
new VeryBig("Phantom " + i), rq);
System.out.println("Just created: " +
(VeryBig)pa[i].get());
checkQueue();
}
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
When you run this program (youll want to pipe the output through a more utility so that you can view the output in pages), youll see that the objects are garbage collected, even though you still have access to them through the Reference object (to get the actual object reference, you use get( )). Youll also see that the ReferenceQueue always produces a Reference containing a null object. To make use of this, you can inherit from the particular Reference class youre interested in and add more useful methods to the new type of Reference. Comentários (em inglês) The WeakHashMap |
| [en-usa] [Traduzir] [+ Trad.] |
The containers library has a special Map to hold weak references: the WeakHashMap. This class is designed to make the creation of canonicalized mappings easier. In such a mapping, you are saving storage by making only one instance of a particular value. When the program needs that value, it looks up the existing object in the mapping and uses that (rather than creating one from scratch). The mapping may make the values as part of its initialization, but its more likely that the values are made on demand. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Since this is a storage-saving technique, its very convenient that the WeakHashMap allows the garbage collector to automatically clean up the keys and values. You dont have to do anything special to the keys and values you want to place in the WeakHashMap; these are automatically wrapped in WeakReferences by the map. The trigger to allow cleanup is if the key is no longer in use, as demonstrated here: Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:CanonicalMapping.java
// Demonstrates WeakHashMap.
import java.util.*;
import java.lang.ref.*;
class Key {
private String ident;
public Key(String id) { ident = id; }
public String toString() { return ident; }
public int hashCode() { return ident.hashCode(); }
public boolean equals(Object r) {
return (r instanceof Key)
&& ident.equals(((Key)r).ident);
}
public void finalize() {
System.out.println("Finalizing Key "+ ident);
}
}
class Value {
private String ident;
public Value(String id) { ident = id; }
public String toString() { return ident; }
public void finalize() {
System.out.println("Finalizing Value " + ident);
}
}
public class CanonicalMapping {
public static void main(String[] args) {
int size = 1000;
// Or, choose size via the command line:
if(args.length > 0)
size = Integer.parseInt(args[0]);
Key[] keys = new Key[size];
WeakHashMap map = new WeakHashMap();
for(int i = 0; i < size; i++) {
Key k = new Key(Integer.toString(i));
Value v = new Value(Integer.toString(i));
if(i % 3 == 0)
keys[i] = k; // Save as "real" references
map.put(k, v);
}
System.gc();
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
The Key class must have a hashCode( ) and an equals( ) since it is being used as a key in a hashed data structure, as described previously in this chapter. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
When you run the program, youll see that the garbage collector will skip every third key, because an ordinary reference to that key has also been placed in the keys array, and thus those objects cannot be garbage collected. Comentários (em inglês) Iterators revisited |
| [en-usa] [Traduzir] [+ Trad.] |
We can now demonstrate the true power of the Iterator: the ability to separate the operation of traversing a sequence from the underlying structure of that sequence. The class PrintData (defined earlier in the chapter) uses an Iterator to move through a sequence and call the toString( ) method for every object. In the following example, two different types of containers are createdan ArrayList and a HashMapand they are each filled with, respectively, Mouse and Hamster objects. (These classes are defined earlier in this chapter.) Because an Iterator hides the structure of the underlying container, Printer.printAll( ) doesnt know or care what kind of container the Iterator comes from: Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:Iterators2.java
// Revisiting Iterators.
import com.bruceeckel.simpletest.*;
import java.util.*;
public class Iterators2 {
private static Test monitor = new Test();
public static void main(String[] args) {
List list = new ArrayList();
for(int i = 0; i < 5; i++)
list.add(new Mouse(i));
Map m = new HashMap();
for(int i = 0; i < 5; i++)
m.put(new Integer(i), new Hamster(i));
System.out.println("List");
Printer.printAll(list.iterator());
System.out.println("Map");
Printer.printAll(m.entrySet().iterator());
monitor.expect(new String[] {
"List",
"This is Mouse #0",
"This is Mouse #1",
"This is Mouse #2",
"This is Mouse #3",
"This is Mouse #4",
"Map",
"4=This is Hamster #4",
"3=This is Hamster #3",
"2=This is Hamster #2",
"1=This is Hamster #1",
"0=This is Hamster #0"
}, Test.IGNORE_ORDER);
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
For the HashMap, the entrySet( ) method produces a Set of Map.entry objects, which contain both the key and the value for each entry, so you see both of them printed. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
Note that PrintData.print( ) takes advantage of the fact that the objects in these containers are of class Object so the call toString( ) by System.out.println( ) is automatic. Its more likely that in your problem, you must make the assumption that your Iterator is walking through a container of some specific type. For example, you might assume that everything in the container is a Shape with a draw( ) method. Then you must downcast from the Object that Iterator.next( ) returns to produce a Shape. Comentários (em inglês) Choosing an implementation |
| [en-usa] [Traduzir] [+ Trad.] |
By now you should understand that there are really only three container components: Map, List, and Set, but more than one implementation of each interface. If you need to use the functionality offered by a particular interface, how do you decide which implementation to use? Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
To understand the answer, you must be aware that each different implementation has its own features, strengths, and weaknesses. For example, you can see in the diagram that the feature of Hashtable, Vector, and Stack is that they are legacy classes, so that old code doesnt break. On the other hand, its best if you dont use those for new code. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
The distinction between the other containers often comes down to what they are backed by; that is, the data structures that physically implement your desired interface. This means that, for example, ArrayList and LinkedList implement the List interface, so the basic operations are the same regardless of which one you use. However, ArrayList is backed by an array, and LinkedList is implemented in the usual way for a doubly linked list, as individual objects each containing data along with references to the previous and next elements in the list. Because of this, if you want to do many insertions and removals in the middle of a list, a LinkedList is the appropriate choice. (LinkedList also has additional functionality that is established in AbstractSequentialList.) If not, an ArrayList is typically faster. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
As another example, a Set can be implemented as either a TreeSet, a HashSet, or a LinkedHashSet. Each of these have different behaviors: HashSet is for typical use and provides raw speed on lookup, LinkedHashSet keeps pairs in insertion order, and TreeSet is backed by TreeMap and is designed to produce a constantly sorted set. The idea is that you can choose the implementation based on the behavior you need. Most of the time, the HashSet is all thats necessary and should be your default choice of Set. Comentários (em inglês) Choosing between Lists |
| [en-usa] [Traduzir] [+ Trad.] |
The most convincing way to see the differences between the implementations of List is with a performance test. The following code establishes an inner base class to use as a test framework, then creates an array of anonymous inner classes, one for each different test. Each of these inner classes is called by the test( ) method. This approach allows you to easily add and remove new kinds of tests. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:ListPerformance.java
// Demonstrates performance differences in Lists.
// {Args: 500}
import java.util.*;
import com.bruceeckel.util.*;
public class ListPerformance {
private static int reps = 10000;
private static int quantity = reps / 10;
private abstract static class Tester {
private String name;
Tester(String name) { this.name = name; }
abstract void test(List a);
}
private static Tester[] tests = {
new Tester("get") {
void test(List a) {
for(int i = 0; i < reps; i++) {
for(int j = 0; j < quantity; j++)
a.get(j);
}
}
},
new Tester("iteration") {
void test(List a) {
for(int i = 0; i < reps; i++) {
Iterator it = a.iterator();
while(it.hasNext())
it.next();
}
}
},
new Tester("insert") {
void test(List a) {
int half = a.size()/2;
String s = "test";
ListIterator it = a.listIterator(half);
for(int i = 0; i < reps * 10; i++)
it.add(s);
}
},
new Tester("remove") {
void test(List a) {
ListIterator it = a.listIterator(3);
while(it.hasNext()) {
it.next();
it.remove();
}
}
},
};
public static void test(List a) {
// Strip qualifiers from class name:
System.out.println("Testing " +
a.getClass().getName().replaceAll("\\w+\\.", ""));
for(int i = 0; i < tests.length; i++) {
Collections2.fill(a, Collections2.countries.reset(),
quantity);
System.out.print(tests[i].name);
long t1 = System.currentTimeMillis();
tests[i].test(a);
long t2 = System.currentTimeMillis();
System.out.println(": " + (t2 - t1));
}
}
public static void testArrayAsList(int reps) {
System.out.println("Testing array as List");
// Can only do first two tests on an array:
for(int i = 0; i < 2; i++) {
String[] sa = new String[quantity];
Arrays2.fill(sa, Collections2.countries.reset());
List a = Arrays.asList(sa);
System.out.print(tests[i].name);
long t1 = System.currentTimeMillis();
tests[i].test(a);
long t2 = System.currentTimeMillis();
System.out.println(": " + (t2 - t1));
}
}
public static void main(String[] args) {
// Choose a different number of
// repetitions via the command line:
if(args.length > 0)
reps = Integer.parseInt(args[0]);
System.out.println(reps + " repetitions");
testArrayAsList(reps);
test(new ArrayList());
test(new LinkedList());
test(new Vector());
}
} ///:~ |
| [en-usa] [Traduzir] [+ Trad.] |
To provide a base class for the specific tests, the inner class Tester is abstract. It contains a String to be printed when the test starts and an abstract method test( ) that does the work. All the different types of tests are collected in one place, the array tests, which is initialized with different anonymous inner classes that inherit from Tester. To add or remove tests, simply add or remove an inner class definition from the array, and everything else happens automatically. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
To compare array access to container access (primarily against ArrayList), a special test is created for arrays by wrapping one as a List using Arrays.asList( ). Note that only the first two tests can be performed in this case, because you cannot insert or remove elements from an array. Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
The List thats handed to test( ) is first filled with elements, then each test in the tests array is timed. The results will vary from machine to machine; they are intended to give only an order of magnitude comparison between the performance of the different containers. Here is a summary of one run: Comentários (em inglês)
|
| [en-usa] [Traduzir] [+ Trad.] |
As expected, arrays are faster than any container for random-access lookups and iteration. You can see that random accesses (get( )) are cheap for ArrayLists and expensive for LinkedLists. (Oddly, iteration is faster for a LinkedList than an ArrayList, which is a bit counterintuitive.) On the other hand, insertions and removals from the middle of a list are dramatically cheaper for a LinkedList than for an ArrayListespecially removals. Vector is generally not as fast as ArrayList, and it should be avoided; its only in the library for legacy code support (the only reason it works in this program is because it was adapted to be a List in Java 2). The best approach is probably to choose an ArrayList as your default and to change to a LinkedList if you discover performance problems due to many insertions and removals from the middle of the list. And of course, if you are working with a fixed-sized group of elements, use an array. Comentários (em inglês) Choosing between Sets |
| [en-usa] [Traduzir] [+ Trad.] |
You can choose a TreeSet, a HashSet, or a LinkedHashSet depending on the behavior you desire. The following test program gives an indication of the performance trade-off between the implementations: Comentários (em inglês) |
| [en-usa] [Traduzir] [+ Trad.] |
//: c11:SetPerformance.java
// {Args: 500}
import java.util.*;
import com.bruceeckel.util.*;
public class SetPerformance {
private static int reps = 50000;
private abstract static class Tester {
String name;
Tester(String name) { this.name = name; }
abstract void test(Set s, int size);
}
private static Tester[] tests = {
new Tester("add") {
void test(Set s, int size) {
for(int i = 0; i < reps; i++) {
s.clear();
Collections2.fill(s,
Collections2.countries.reset(),size);
}
}
},
new Tester("contains") {
void test(Set s, int size) {
for(int i = 0; i < reps; i++)
for(int j = 0; j < size; j++)
s.contains(Integer.toString(j));
}
},
new Tester("iteration") {
void test(Set s, int size) {
for(int i = 0; i < reps * 10; i++) {
Iterator it = s.iterator();
while(it.hasNext())
it.next();
}
}
},
};
public static void test(Set s, int size) {
// Strip qualifiers from class name:
System.out.println("Testing " +
s.getClass().getName().replaceAll("\\w+\\.", |