Perguntas da entrevista sobre coleções Java

Perguntas da entrevista sobre coleções Java

1. Introdução

Coleções Java é um tópico frequentemente abordado em entrevistas técnicas para desenvolvedores Java. Este artigo analisa algumas questões importantes que são feitas com mais frequência e que podem ser difíceis de corrigir.

2. Questões

Q1. Descreva a hierarquia de tipos de coleções. Quais são as interfaces principais e quais são as diferenças entre elas?

A interfaceIterable representa qualquer coleção que pode ser iterada usando o loopfor-each. A interfaceCollection herda deIterable e adiciona métodos genéricos para verificar se um elemento está em uma coleção, adicionando e removendo elementos da coleção, determinando seu tamanho etc.

As interfacesList,Set eQueue herdam da interfaceCollection.

List é uma coleção ordenada e seus elementos podem ser acessados ​​por seu índice na lista.

Set é uma coleção não ordenada com elementos distintos, semelhante à noção matemática de conjunto.

Queue é uma coleção com métodos adicionais para adicionar, remover e examinar elementos, útil para manter elementos antes do processamento.

A interfaceMap também faz parte da estrutura de coleção, mas não estendeCollection. Isso ocorre por design, para enfatizar a diferença entre coleções e mapeamentos difíceis de reunir sob uma abstração comum. A interfaceMap representa uma estrutura de dados de valor-chave com chaves exclusivas e não mais do que um valor para cada chave.

 

Q2. Descrever várias implementações da interface do mapa e suas diferenças de caso de uso.

Uma das implementações mais freqüentemente usadas da interfaceMap éHashMap. É uma estrutura de dados de mapa hash típica que permite acessar elementos em tempo constante, ou O (1), masdoes not preserve order and is not thread-safe.

Para preservar a ordem de inserção de elementos, você pode usar a classeLinkedHashMap, que estendeHashMape adicionalmente vincula os elementos em uma lista vinculada, com sobrecarga previsível.

A classeTreeMap armazena seus elementos em uma estrutura de árvore vermelho-preto, que permite acessar os elementos em tempo logarítmico, ou O (log (n)). É mais lento queHashMap na maioria dos casos, mas permite manter os elementos em ordem de acordo com algunsComparator.

OConcurrentHashMap é uma implementação thread-safe de um hash map. Ele fornece simultaneidade total de recuperações (já que a operaçãoget não envolve bloqueio) e alta simultaneidade esperada de atualizações.

A classeHashtable está em Java desde a versão 1.0. Não está obsoleto, mas é considerado principalmente obsoleto. É um mapa hash seguro para thread, mas ao contrário deConcurrentHashMap, todos os seus métodos são simplesmentesynchronized, o que significa que todas as operações neste mapa bloqueiam, até mesmo a recuperação de valores independentes.

 

Q3. Explique a diferença entre Linkedlist e Arraylist.

ArrayList é uma implementação da interfaceList baseada em uma matriz. ArrayList trata internamente o redimensionamento desta matriz quando os elementos são adicionados ou removidos. Você pode acessar seus elementos em tempo constante pelo respectivo índice na matriz. No entanto, a inserção ou remoção de um elemento infere a alteração de todos os elementos subsequentes que podem ser lentos se a matriz for enorme e o elemento inserido ou removido estiver próximo do início da lista.

LinkedList é uma lista duplamente vinculada: elementos únicos são colocados em objetosNode que têm referências aNode anterior e seguinte. Esta implementação pode parecer mais eficiente do queArrayList se você tiver muitas inserções ou exclusões em diferentes partes da lista, especialmente se a lista for grande.

Na maioria dos casos, entretanto,ArrayList superaLinkedList. Mesmo os elementos mudando emArrayList, embora sendo uma operação O (n), são implementados como uma chamadaSystem.arraycopy() muito rápida. Ele pode até mesmo aparecer mais rápido do que a inserção O (1) deLinkedList, que requer a instanciação de um objetoNode e a atualização de várias referências sob o capô. LinkedList também pode ter uma grande sobrecarga de memória devido à criação de vários objetos pequenosNode.

 

Q4. Qual é a diferença entre Hashset e Treeset?

Ambas as classesHashSeteTreeSet implementam a interfaceSet e representam conjuntos de elementos distintos. Além disso,TreeSet implementa a interfaceNavigableSet. Essa interface define métodos que tiram proveito da ordem dos elementos.

HashSet é baseado internamente emHashMap, eTreeSet é apoiado por uma instânciaTreeMap, que define suas propriedades:HashSet não mantém elementos em nenhum particular ordem. A iteração sobre os elementos em aHashSet os produz em ordem aleatória. TreeSet, por outro lado, produz elementos em ordem de acordo com algunsComparator predefinidos.

 

Q5. Como o Hashmap é implementado em Java? Como sua implementação usa código de hash e métodos iguais de objetos? Qual é a complexidade de tempo para colocar e obter um elemento dessa estrutura?

A classeHashMap representa uma estrutura de dados de mapa hash típica com certas opções de design.

OHashMap é apoiado por uma matriz redimensionável que tem um tamanho de potência de dois. Quando o elemento é adicionado aHashMap, primeiro seuhashCode é calculado (um valorint). Então, um certo número de bits mais baixos desse valor é usado como um índice de matriz. Esse índice aponta diretamente para a célula da matriz (chamada de bucket) onde esse par de valores-chave deve ser colocado. O acesso a um elemento por seu índice em uma matriz é uma operação O (1) muito rápida, que é o principal recurso de uma estrutura de mapa de hash.

Entretanto, umhashCode não é único e, mesmo parahashCodes diferentes, podemos receber a mesma posição do array. Isso é chamado de colisão. Há mais de uma maneira de resolver colisões nas estruturas de dados do mapa de hash. EmHashMap do Java, cada intervalo realmente não se refere a um único objeto, mas a uma árvore vermelha e preta de todos os objetos que pousaram neste intervalo (antes do Java 8, esta era uma lista vinculada).

Então, quandoHashMap determinou o balde para uma chave, ele tem que atravessar esta árvore para colocar o par de valor-chave em seu lugar. Se um par com essa chave já existir no balde, ele será substituído por um novo.

Para recuperar o objeto por sua chave, oHashMap novamente tem que calcular ohashCode para a chave, encontrar o intervalo correspondente, percorrer a árvore, chamarequals nas chaves na árvore e encontrar o correspondente.

HashMap tem complexidade O (1), ou complexidade de tempo constante, de colocar e obter os elementos. Obviamente, muitas colisões podem degradar o desempenho para a complexidade do tempo O (log (n)) no pior dos casos, quando todos os elementos chegam a um único bucket. Isso geralmente é resolvido fornecendo uma boa função de hash com uma distribuição uniforme.

Quando o array internoHashMap é preenchido (mais sobre isso na próxima pergunta), ele é redimensionado automaticamente para ficar com o dobro do tamanho. Esta operação infere novo hashing (reconstrução de estruturas de dados internas), o que é caro, portanto, você deve planejar o tamanho de seuHashMap com antecedência.

 

Q6. Qual é o objetivo dos parâmetros de capacidade inicial e fator de carga de um Hashmap? Quais são seus valores padrão?

O argumentoinitialCapacity do construtorHashMap afeta o tamanho da estrutura de dados interna deHashMap, mas raciocinar sobre o tamanho real de um mapa é um pouco complicado. A estrutura de dados interna deHashMap é uma matriz com tamanho de potência de dois. Portanto, o valor do argumentoinitialCapacity é aumentado para a próxima potência de dois (por exemplo, se você definir para 10, o tamanho real do array interno será 16).

O fator de carga de aHashMap é a proporção da contagem do elemento dividido pela contagem do balde (ou seja, tamanho interno da matriz). Por exemplo, se umHashMap de 16 baldes contém 12 elementos, seu fator de carga é 12/16 = 0,75. Um alto fator de carga significa muitas colisões, o que, por sua vez, significa que o mapa deve ser redimensionado para a próxima potência de duas. Portanto, o argumentoloadFactor é um valor máximo do fator de carga de um mapa. Quando o mapa atinge esse fator de carga, ele redimensiona sua matriz interna para o próximo valor de potência de dois.

OinitialCapacity é 16 por padrão, e o loadFactor é 0,75 por padrão, então você poderia colocar 12 elementos em umHashMap instanciado com o construtor padrão, e ele não seria redimensionado. O mesmo vale paraHashSet, que é apoiado por uma instânciaHashMap internamente.

Conseqüentemente, não é trivial chegar ainitialCapacity que satisfaça suas necessidades. É por isso que a biblioteca Guava tem métodosMaps.newHashMapWithExpectedSize()eSets.newHashSetWithExpectedSize() que permitem a você construir umHashMap ou umHashSet que pode conter o número esperado de elementos sem redimensionar.

===

Q7. Descrever coleções especiais para enums. Quais são os benefícios de sua implementação em comparação com as cobranças regulares?

EnumSeteEnumMap são implementações especiais das interfacesSeteMap correspondentemente. Você deve sempre usar essas implementações ao lidar com enums, porque elas são muito eficientes.

UmEnumSet é apenas um vetor de bits com “uns” nas posições correspondentes aos valores ordinais de enums presentes no conjunto. Para verificar se um valor de enum está no conjunto, a implementação simplesmente precisa verificar se o bit correspondente no vetor é "um", o que é uma operação muito fácil. Da mesma forma, umEnumMap é uma matriz acessada com o valor ordinal de enum como um índice. No caso deEnumMap, não há necessidade de calcular códigos hash ou resolver colisões.

 

Q8. Qual é a diferença entre iteradores fail-fast e fail-safe?

Os iteradores para coleções diferentes são rápidos ou seguros contra falhas, dependendo de como eles reagem a modificações simultâneas. A modificação simultânea não é apenas uma modificação da coleção de outro encadeamento, mas também uma modificação do mesmo encadeamento, mas usando outro iterador ou modificando a coleção diretamente.

Os iteradoresFail-fast (aqueles retornados porHashMap,ArrayList e outras coleções não thread-safe) iteram sobre a estrutura de dados interna da coleção e lançamConcurrentModificationException assim que eles detectam uma modificação simultânea.

Os iteradoresFail-safe (retornados por coleções thread-safe comoConcurrentHashMap,CopyOnWriteArrayList) criam uma cópia da estrutura sobre a qual iteram. Eles garantem segurança contra modificações simultâneas. Suas desvantagens incluem consumo excessivo de memória e iteração sobre dados possivelmente desatualizados, caso a coleção tenha sido modificada.

 

Q9. Como você pode usar interfaces comparáveis ​​e comparadoras para classificar coleções?

A interfaceComparable é uma interface para objetos que podem ser comparados de acordo com alguma ordem. Seu único método écompareTo, que opera em dois valores: o próprio objeto e o objeto de argumento do mesmo tipo. Por exemplo,Integer,Long e outros tipos numéricos implementam esta interface. String também implementa essa interface, e seu métodocompareTo compara strings em ordem lexicográfica.

A interfaceComparable permite ordenar listas de objetos correspondentes com o métodoCollections.sort()e manter a ordem de iteração em coleções que implementamSortedSeteSortedMap. Se seus objetos podem ser classificados usando alguma lógica, eles devem implementar a interfaceComparable.

A interfaceComparable geralmente é implementada usando a ordem natural dos elementos. Por exemplo, todos os númerosInteger são ordenados dos valores menores aos maiores. Mas às vezes você pode implementar outro tipo de ordem, por exemplo, para classificar os números em ordem decrescente. A interfaceComparator pode ajudar aqui.

A classe dos objetos que você deseja classificar não precisa implementar essa interface. Você simplesmente cria uma classe de implementação e define o métodocompare que recebe dois objetos e decide como ordená-los. Você pode então usar a instância dessa classe para substituir a ordem natural do métodoCollections.sort() ou instânciasSortedSeteSortedMap.

Como a interfaceComparator é uma interface funcional, você pode substituí-la por uma expressão lambda, como no exemplo a seguir. Ele mostra a ordenação de uma lista usando uma ordenação natural (interfaceIntegerComparable) e usando um iterador personalizado (interfaceComparator<Integer>).

List list1 = Arrays.asList(5, 2, 3, 4, 1);
Collections.sort(list1);
assertEquals(new Integer(1), list1.get(0));

List list1 = Arrays.asList(5, 2, 3, 4, 1);
Collections.sort(list1, (a, b) -> b - a);
assertEquals(new Integer(5), list1.get(0));