Fragen zum Vorstellungsgespräch für Java-Sammlungen

Dieser Artikel ist Teil einer Reihe: • Interviewfragen zu Java-Sammlungen (aktueller Artikel) • Interviewfragen zu Java Type System

• Fragen zum Java Concurrency-Interview (+ Antworten)

• Fragen zum Java-Klassenstruktur- und Initialisierungsinterview

• Fragen zum Java 8-Interview (+ Antworten)

• Speicherverwaltung in Java Interview Fragen (+ Antworten)

• Fragen zum Java Generics-Interview (+ Antworten)

• Fragen zum Java Flow Control-Interview (+ Antworten)

• Fragen zum Interview mit Java-Ausnahmen (+ Antworten)

• Fragen zum Interview mit Java Annotations (+ Antworten)

• Fragen zum Top Spring Framework-Interview

1. Einleitung

Java-Sammlungen sind ein Thema, das häufig in technischen Interviews für Java-Entwickler angesprochen wird. Dieser Artikel behandelt einige wichtige Fragen, die am häufigsten gestellt werden und möglicherweise schwierig zu beantworten sind.

2. Fragen

Q1. Beschreiben der Hierarchie der Sammlungstypen. Was sind die Hauptschnittstellen und was sind die Unterschiede zwischen ihnen?

Die Iterable- Schnittstelle repräsentiert jede Sammlung, die mit der for-each- Schleife iteriert werden kann . Die Collection- Schnittstelle erbt von Iterable und fügt generische Methoden hinzu, um zu überprüfen, ob sich ein Element in einer Sammlung befindet, Elemente zur Sammlung hinzuzufügen und daraus zu entfernen, ihre Größe zu bestimmen usw.

Die Schnittstellen List , Set und Queue erben von der Collection- Schnittstelle.

List ist eine geordnete Sammlung, auf deren Elemente über ihren Index in der Liste zugegriffen werden kann.

Set ist eine ungeordnete Sammlung mit unterschiedlichen Elementen, ähnlich dem mathematischen Begriff eines Sets.

Queue ist eine Sammlung mit zusätzlichen Methoden zum Hinzufügen, Entfernen und Untersuchen von Elementen, die zum Halten von Elementen vor der Verarbeitung nützlich sind.

Die Kartenschnittstelle ist ebenfalls Teil des Sammlungsframeworks, erweitert jedoch nicht die Sammlung . Dies ist beabsichtigt, um den Unterschied zwischen Sammlungen und Zuordnungen hervorzuheben, die unter einer gemeinsamen Abstraktion schwer zu erfassen sind. Die Map- Schnittstelle repräsentiert eine Schlüsselwertdatenstruktur mit eindeutigen Schlüsseln und nicht mehr als einem Wert für jeden Schlüssel.

Q2. Beschreiben verschiedener Implementierungen der Kartenschnittstelle und ihrer Anwendungsfallunterschiede.

Eine der am häufigsten verwendeten Implementierungen der Map- Schnittstelle ist die HashMap . Es handelt sich um eine typische Hash-Map-Datenstruktur, die den Zugriff auf Elemente in konstanter Zeit oder O (1) ermöglicht, jedoch die Reihenfolge nicht beibehält und nicht threadsicher ist .

Um die Einfügereihenfolge von Elementen beizubehalten , können Sie die LinkedHashMap- Klasse verwenden, die die HashMap erweitert und die Elemente zusätzlich mit vorhersehbarem Aufwand in eine verknüpfte Liste einbindet .

Die TreeMap- Klasse speichert ihre Elemente in einer rot-schwarzen Baumstruktur, die den Zugriff auf Elemente in logarithmischer Zeit oder O (log (n)) ermöglicht. Es ist in den meisten Fällen langsamer als die HashMap , ermöglicht es jedoch, die Elemente gemäß einigen Komparatoren in der richtigen Reihenfolge zu halten .

Die ConcurrentHashMap ist eine thread-sichere Implementierung einer Hash-Map. Es bietet die vollständige Parallelität von Abrufen (da der get- Vorgang kein Sperren beinhaltet) und eine hohe erwartete Parallelität von Updates.

Die Hashtable- Klasse befindet sich seit Version 1.0 in Java. Es ist nicht veraltet, wird aber meist als veraltet angesehen. Es ist eine thread-sichere Hash-Map, aber im Gegensatz zu ConcurrentHashMap werden alle Methoden einfach synchronisiert , was bedeutet, dass alle Operationen auf diesem Map-Block sogar das Abrufen unabhängiger Werte blockieren.

Q3. Erklären Sie den Unterschied zwischen Linkedlist und Arraylist.

ArrayList ist eine Implementierung der List- Schnittstelle, die auf einem Array basiert. ArrayList behandelt intern die Größenänderung dieses Arrays, wenn die Elemente hinzugefügt oder entfernt werden. Sie können in konstanter Zeit über den Index im Array auf die Elemente zugreifen. Das Einfügen oder Entfernen eines Elements führt jedoch dazu, dass alle nachfolgenden Elemente verschoben werden. Dies kann langsam sein, wenn das Array sehr groß ist und sich das eingefügte oder entfernte Element am Anfang der Liste befindet.

LinkedList ist eine doppelt verknüpfte Liste: Einzelelemente werden in setzen Knoten Objektedie Verweise auf vorherige und nächste haben Knoten . Diese Implementierung erscheint möglicherweise effizienter als ArrayList, wenn Sie viele Einfügungen oder Löschungen in verschiedenen Teilen der Liste haben, insbesondere wenn die Liste groß ist.

In den meisten Fällen übertrifft ArrayList jedoch LinkedList . Sogar Elemente, die in ArrayList verschoben werden , sind zwar eine O (n) -Operation , werden jedoch als sehr schneller System.arraycopy () -Aufruf implementiert . Es kann sogar erscheinen schneller als die LinkedList ‚s O (1) Einfügung , die eine erfordert Instanziieren Knoten Objekt und Aktualisierung mehr Referenzen unter der Haube. LinkedList kann auch einen großen Speicher - Overhead haben aufgrund einer Schaffung von mehreren kleinen Knoten Objekte.

Q4. Was ist der Unterschied zwischen Hashset und Treeset?

Sowohl die HashSet- als auch die TreeSet- Klasse implementieren die Set- Schnittstelle und repräsentieren Sätze unterschiedlicher Elemente. Darüber hinaus implementiert TreeSet die NavigableSet- Schnittstelle. Diese Schnittstelle definiert Methoden, die die Reihenfolge der Elemente nutzen.

HashSet basiert intern auf einer HashMap , und TreeSet wird von einer TreeMap- Instanz unterstützt, die ihre Eigenschaften definiert: HashSet speichert Elemente nicht in einer bestimmten Reihenfolge. Die Iteration über die Elemente in einem HashSet erzeugt sie in einer gemischten Reihenfolge. TreeSet hingegen erzeugt Elemente in der Reihenfolge eines vordefinierten Komparators .

Q5. Wie wird Hashmap in Java implementiert? Wie verwendet die Implementierung Hashcode und entspricht den Methoden von Objekten? Was ist die zeitliche Komplexität des Platzierens und Erhaltens eines Elements aus einer solchen Struktur?

Die HashMap- Klasse repräsentiert eine typische Hash-Map-Datenstruktur mit bestimmten Entwurfsoptionen.

Die HashMap wird von einem anpassbaren Array mit einer Zweierpotenz unterstützt. Wenn das Element zu einer HashMap hinzugefügt wird , wird zuerst sein Hashcode berechnet (ein int- Wert). Dann wird eine bestimmte Anzahl niedrigerer Bits dieses Wertes als Array-Index verwendet. Dieser Index zeigt direkt auf die Zelle des Arrays (als Bucket bezeichnet), in der dieses Schlüssel-Wert-Paar platziert werden soll. Der Zugriff auf ein Element über seinen Index in einem Array ist eine sehr schnelle O (1) -Operation, die das Hauptmerkmal einer Hash-Map-Struktur ist.

A hashCode is not unique, however, and even for different hashCodes, we may receive the same array position. This is called a collision. There is more than one way of resolving collisions in the hash map data structures. In Java's HashMap, each bucket actually refers not to a single object, but to a red-black tree of all objects that landed in this bucket (prior to Java 8, this was a linked list).

So when the HashMap has determined the bucket for a key, it has to traverse this tree to put the key-value pair in its place. If a pair with such key already exists in the bucket, it is replaced with a new one.

To retrieve the object by its key, the HashMap again has to calculate the hashCode for the key, find the corresponding bucket, traverse the tree, call equals on keys in the tree and find the matching one.

HashMap has O(1) complexity, or constant-time complexity, of putting and getting the elements. Of course, lots of collisions could degrade the performance to O(log(n)) time complexity in the worst case, when all elements land in a single bucket. This is usually solved by providing a good hash function with a uniform distribution.

When the HashMap internal array is filled (more on that in the next question), it is automatically resized to be twice as large. This operation infers rehashing (rebuilding of internal data structures), which is costly, so you should plan the size of your HashMap beforehand.

Q6. What Is the Purpose of the Initial Capacity and Load Factor Parameters of a Hashmap? What Are Their Default Values?

The initialCapacity argument of the HashMap constructor affects the size of the internal data structure of the HashMap, but reasoning about the actual size of a map is a bit tricky. The HashMap‘s internal data structure is an array with the power-of-two size. So the initialCapacity argument value is increased to the next power-of-two (for instance, if you set it to 10, the actual size of the internal array will be 16).

The load factor of a HashMap is the ratio of the element count divided by the bucket count (i.e. internal array size). For instance, if a 16-bucket HashMap contains 12 elements, its load factor is 12/16 = 0.75. A high load factor means a lot of collisions, which in turn means that the map should be resized to the next power of two. So the loadFactor argument is a maximum value of the load factor of a map. When the map achieves this load factor, it resizes its internal array to the next power-of-two value.

The initialCapacity is 16 by default, and the loadFactor is 0.75 by default, so you could put 12 elements in a HashMap that was instantiated with the default constructor, and it would not resize. The same goes for the HashSet, which is backed by a HashMap instance internally.

Consequently, it is not trivial to come up with initialCapacity that satisfies your needs. This is why the Guava library has Maps.newHashMapWithExpectedSize() and Sets.newHashSetWithExpectedSize() methods that allow you to build a HashMap or a HashSet that can hold the expected number of elements without resizing.

Q7. Describe Special Collections for Enums. What Are the Benefits of Their Implementation Compared to Regular Collections?

EnumSet and EnumMap are special implementations of Set and Map interfaces correspondingly. You should always use these implementations when you're dealing with enums because they are very efficient.

An EnumSet is just a bit vector with “ones” in the positions corresponding to ordinal values of enums present in the set. To check if an enum value is in the set, the implementation simply has to check if the corresponding bit in the vector is a “one”, which is a very easy operation. Similarly, an EnumMap is an array accessed with enum's ordinal value as an index. In the case of EnumMap, there is no need to calculate hash codes or resolve collisions.

Q8. What Is the Difference Between Fail-Fast and Fail-Safe Iterators?

Iterators for different collections are either fail-fast or fail-safe, depending on how they react to concurrent modifications. The concurrent modification is not only a modification of collection from another thread but also modification from the same thread but using another iterator or modifying the collection directly.

Fail-fast iterators (those returned by HashMap, ArrayList, and other non-thread-safe collections) iterate over the collection's internal data structure, and they throw ConcurrentModificationException as soon as they detect a concurrent modification.

Fail-safe iterators (returned by thread-safe collections such as ConcurrentHashMap, CopyOnWriteArrayList) create a copy of the structure they iterate upon. They guarantee safety from concurrent modifications. Their drawbacks include excessive memory consumption and iteration over possibly out-of-date data in case the collection was modified.

Q9. How Can You Use Comparable and Comparator Interfaces to Sort Collections?

The Comparable interface is an interface for objects that can be compared according to some order. Its single method is compareTo, which operates on two values: the object itself and the argument object of the same type. For instance, Integer, Long, and other numeric types implement this interface. String also implements this interface, and its compareTo method compares strings in lexicographical order.

The Comparable interface allows to sort lists of corresponding objects with the Collections.sort() method and uphold the iteration order in collections that implement SortedSet and SortedMap. If your objects can be sorted using some logic, they should implement the Comparable interface.

The Comparable interface usually is implemented using natural ordering of the elements. For instance, all Integer numbers are ordered from lesser to greater values. But sometimes you may want to implement another kind of ordering, for instance, to sort the numbers in descending order. The Comparator interface can help here.

The class of the objects you want to sort does not need to implement this interface. You simply create an implementing class and define the compare method which receives two objects and decides how to order them. You may then use the instance of this class to override the natural ordering of the Collections.sort() method or SortedSet and SortedMap instances.

As the Comparator interface is a functional interface, you may replace it with a lambda expression, as in the following example. It shows ordering a list using a natural ordering (Integer‘s Comparable interface) and using a custom iterator (Comparator interface).

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));
Weiter » Fragen zum Java Type System-Interview