
Coleções
84. Conte-nos sobre iteradores e como eles são usados
Coleções é um tópico favorito em qualquer entrevista com desenvolvedor Java. Ao responder perguntas sobre a hierarquia da coleção, os candidatos costumam dizer que ela começa com a interface da Coleção . Mas não é assim. Existe outra interface um nível acima: Iterable . Esta interface consiste no método iterator() , que permite acessar o objeto Iterator da coleção atual. E o que exatamente é esse objeto Iterator ? O objeto Iterator fornece a capacidade de percorrer uma coleção e iterar sobre seus elementos, e o usuário não precisa conhecer os detalhes específicos de implementação da coleção. Ou seja, é uma espécie de ponteiro para os elementos da coleção, como se estivesse espiando um deles. O iterador possui métodos como:-
hasNext() — retorna verdadeiro se a iteração tiver outro elemento (este método permite saber quando você chegou ao final da coleção);
-
next() — retorna o próximo item da iteração. Se não houver, uma NoSuchElementException será lançada. Isso significa que antes de usar este método, é melhor usar o método hasNext() para garantir que exista um próximo elemento;
-
remove() — remove da coleção o último elemento recebido usando o método next() . Se next() nunca tiver sido chamado, então chamar remove() fará com que uma IllegalStateException seja lançada;
-
forEachRemaining(<Consumer>) — executa a ação passada em cada elemento da coleção (este método apareceu em Java 8).
List<String> list = new ArrayList<>();
list.add("Hello ");
list.add("World, ");
list.add("It's ");
list.add("Amigo!");
Iterator iterator = list.iterator();
while(iterator.hasNext()) {
iterator.next();
iterator.remove();
}
System.out.println(list.size());
O console exibirá o seguinte:
iterator.forEachRemaining(x -> System.out.print(x));
Mas uma vez feito isso, o iterador se torna inadequado para uso posterior: ele percorreu toda a lista e um iterador comum não possui métodos para iterar de trás para frente. E isso é uma boa continuação para uma discussão sobre LinkedList , especificamente, seu método listIterator() , que retorna um tipo aprimorado de iterador: ListIterator . Além dos métodos de um iterador regular (padrão), esse tipo possui o seguinte:
-
add(<Element>) — adiciona um novo elemento à lista;
-
hasPrevious() — retorna verdadeiro se houver um elemento localizado antes do próximo elemento (se houver um elemento anterior);
-
nextIndex() — retorna o índice do próximo elemento;
-
previous() — retorna o elemento anterior (aquele antes do próximo elemento);
-
previousIndex retorna o índice do elemento anterior.
-
set(<Element>) — substitui o último elemento retornado por next() ou previous() .

85. Qual hierarquia de coleção existe no Java Collection Framework?
Existem duas hierarquias de coleção em Java. A primeira hierarquia é a hierarquia de Coleções, que possui a seguinte estrutura:
-
Conjunto é uma interface que descreve um conjunto, uma estrutura de dados que contém elementos únicos não ordenados (não repetidos). Esta interface possui algumas implementações padrão: TreeSet , HashSet e LinkedHashSet .
-
Lista é uma interface que descreve uma estrutura de dados que armazena uma sequência ordenada de objetos. Os objetos em uma lista podem ser inseridos e removidos pelo seu índice na lista (como um array, mas com redimensionamento dinâmico). Essa interface também possui algumas implementações padrão: ArrayList , Vector ( obsoleto e não usado de fato ) e LinkedList .
-
Fila é uma interface que descreve uma estrutura de dados que armazena itens em uma fila First In First Out (FIFO) . Essa interface possui as seguintes implementações padrão: LinkedList (isso mesmo, ela também implementa Queue ) e PriotityQueue .


86. Qual é a estrutura interna de um ArrayList?
Um ArrayList é como um array, mas pode se expandir dinamicamente. O que isso significa? Nos bastidores, ArrayList usa um array comum, ou seja, ele armazena seus elementos em um array interno cujo tamanho padrão é 10 células. Quando o array interno estiver cheio, um novo array será criado. O tamanho da nova matriz é determinado por esta fórmula:<size of the current array> * 3 / 2 + 1
Portanto, se o tamanho do nosso array for 10, o tamanho do novo será: 10 * 3/2 + 1 = 16. Em seguida, todos os valores do array original (antigo) serão copiados para ele usando o built-in Método System.arraycopy() e o array original é excluído. Resumindo, é assim que ArrayList implementa o redimensionamento dinâmico. Vamos considerar os métodos ArrayList mais populares : 1. add(<Element>) — adiciona um elemento ao final do array (na última célula vazia), após verificar primeiro se há uma célula disponível no array. Caso contrário, um novo array é criado e os elementos são copiados para ele. A complexidade de tempo desta operação é O(1). Existe um método add(<Index>, <Elelement>) semelhante . Adiciona um elemento não ao final da lista (array), mas à célula específica indicada pelo índice que entrou como argumento. Neste caso, a complexidade do tempo irá variar dependendo de onde você adiciona:
- se a adição estiver próxima do início da lista, então a complexidade de tempo será próxima de O(N), pois todos os elementos localizados à direita do novo deverão ser movidos uma célula para a direita;
- se o elemento for inserido no meio, então será O(N/2), pois só precisamos deslocar metade dos itens da lista uma célula para a direita.
87. Qual é a estrutura interna de um LinkedList?
Um ArrayList contém elementos no array interno, mas um LinkedList os armazena em uma lista duplamente vinculada. Isso significa que cada elemento contém um link para o elemento anterior e para o próximo elemento. O primeiro elemento não está vinculado a um elemento anterior (afinal, é o primeiro). Também é considerado o topo da lista, e o objeto LinkedList possui uma referência direta a ele. Da mesma forma, o último elemento não possui o próximo elemento, pois é o final da lista. O objeto LinkedList também faz referência a ele diretamente. Isso significa que a complexidade de tempo de acesso ao início ou ao fim de uma lista é O(1).

- se estiver próximo do início ou do final, a operação se aproximará de O(1), pois não será realmente necessário iterar sobre os elementos;
- se estiver próximo do meio, então teremos O(N/2), pois o método irá pesquisar desde o início e o final ao mesmo tempo até que o elemento desejado seja encontrado.

88. Qual é a estrutura interna de um HashMap?
Esta pode ser uma das perguntas de entrevista mais populares para candidatos a desenvolvedores Java. Um HashMap funciona com pares chave-valor . Como eles são armazenados dentro do próprio HashMap ? Um HashMap possui uma matriz interna de nós:Node<K,V>[] table
Por padrão, o tamanho do array é 16 e dobra cada vez que é preenchido com elementos (ou seja, quando LOAD_FACTOR é atingido ; ele especifica o limite de quão cheio o array pode ficar - por padrão, é 0,75 ) . Cada um dos nós armazena um hash da chave, a chave, um valor e uma referência ao próximo elemento: 
- A célula está vazia — neste caso, o novo valor do Node está armazenado nela.
- A célula não está vazia — neste caso, os valores das chaves são comparados. Se forem iguais, o novo valor do Nó substitui o antigo; se não for igual, então o próximo é acessado e sua chave é comparada... E assim por diante, até que o novo valor substitua algum valor antigo ou cheguemos ao final da lista vinculada individualmente e então armazenemos o novo valor lá como o último elemento.

GO TO FULL VERSION