Java - Collections
Java - Collections
4
How to remove duplicates from ArrayList in Java?...................................................... 4
How to reverse ArrayList in Java?................................................................................4
What is the difference between an array and an ArrayList in Java?............................ 4
How to synchronise ArrayList in Java?........................................................................ 4
When to use ArrayList and LinkedList in Java?........................................................... 6
What is the difference between ArrayList and HashSet in Java?.................................6
How to loop over ArrayList in Java?............................................................................. 6
What is the difference between Vector and ArrayList in Java?.................................... 6
How to sort an ArrayList in Java?.................................................................................6
What is the difference between HashMap and ArrayList in Java?............................... 7
How to convert ArrayList to String in Java?................................................................. 7
How to get a sublist from ArrayList in Java?................................................................ 7
What is the difference between the length() of the array and the size() of ArrayList in
Java?............................................................................................................................ 7
What is CopyOnWriteArrayList in Java?...................................................................... 7
How to remove objects from ArrayList in Java?........................................................... 8
How to make ArrayList read-only in Java?................................................................... 8
How to sort an ArrayList in descending order in Java?................................................ 8
HashMap................................................................................................................................. 9
How does the put() method of HashMap works in Java?.............................................9
What is the requirement for an object to be used as a key or value in HashMap?.... 10
Can you store a null key in Java HashMap?.............................................................. 11
Can you store a null value inside HashMap in Java?................................................. 11
How does HashMap handle collisions in Java?..........................................................11
Which data structure does HashMap represent?....................................................... 12
Can you store the duplicate value in Java HashMap?............................................... 12
Is HashMap thread-safe in Java?...............................................................................12
What will happen if you use HashMap in a multithreaded Java application?.............12
What are the different ways to iterate over HashMap in Java?.................................. 12
How do you remove a mapping while iterating over HashMap in Java?....................13
In which order are mappings stored in HashMap?.....................................................13
Can you sort HashMap in Java?................................................................................ 14
What is the load factor in HashMap?......................................................................... 14
How does resize happen in HashMap?......................................................................15
How many entries can you store in HashMap? What is the maximum limit?.............15
What is the difference between the capacity and size of HashMap in Java?.............15
What is the time complexity for adding, retrieving, and removing elements from a
HashMap?.................................................................................................................. 15
How does HashMap determine equality between keys?............................................15
What is the purpose of the hashCode() method in HashMap?.................................. 15
1
How can you ensure that objects used as keys in a HashMap have correct hashcode
and equals implementations?.....................................................................................16
Can you use mutable objects as keys in a HashMap?...............................................16
What happens if you insert a duplicate key into a HashMap?....................................16
How do you iterate through the entries of a HashMap?............................................. 16
What's the difference between HashMap and ConcurrentHashMap?........................16
How can you make a HashMap thread-safe?............................................................ 16
You can make a HashMap thread-safe by using Collections.synchronizedMap() to
create a synchronised wrapper around the HashMap. Alternatively, you can use the
ConcurrentHashMap class, which is designed for concurrent access.
When would you consider using a TreeMap instead of a HashMap?........................ 16
How can you make a deep copy of a HashMap?.......................................................16
Explain how the initial capacity and load factor of a HashMap affect its performance...
16
How does HashMap's performance degrade as the number of collisions increases?...
17
Can a custom object be used as a key in a HashMap without implementing
hashCode() and equals()?..........................................................................................17
What happens if you modify a key object after it's been added to a HashMap?........ 17
Is there a way to get the exact position of an element in a HashMap?...................... 17
How can you make sure that a key's hash code doesn't change after insertion into a
HashMap?.................................................................................................................. 17
Can you guarantee the order of elements when iterating through a HashMap?........17
How would you handle a scenario where a large number of keys have the same hash
code?..........................................................................................................................17
Can you explain the internal data structure of a HashMap?...................................... 18
What is the purpose of the transient modifier in the HashMap class?....................... 18
How does the hashCode() function affect the performance of a HashMap?..............18
How does the Java 8 update improve performance of HashMap?............................ 18
ConcurrentHashMap............................................................................................................ 18
What is ConcurrentHashMap in Java?.......................................................................18
How does ConcurrentHashMap achieve thread safety?............................................ 18
Can multiple threads read from ConcurrentHashMap same time?............................ 18
Can one thread read and the other write on ConcurrentHashMap at the same time?...
19
How does ConcurrentHashMap work internally?....................................................... 19
How do you atomically update a value in ConcurrentHashMap?...............................19
How do you remove a mapping while iterating over ConcurrentHashMap?.............. 19
Does the Iterator of ConcurrentHashMap fail-safe or fail-fast?.................................. 19
What will happen if you add a new mapping in ConcurrentHashMap while one thread
is iterating over it?...................................................................................................... 19
Can you pass an object of ConcurrentHahsMap when a Map is expected?..............20
Equals and HashCode......................................................................................................... 20
When you are writing the equals() method, which other method or methods do you
need to override?....................................................................................................... 20
Can two objects which are not equal have the same hashCode?..............................20
2
How does the get() method of HashMap work if two keys have the same hashCode?.
20
Where have you written equals() and hashCode in your project?..............................21
Suppose your Class has an Id field; should you include it in equals()? Why?........... 21
What happens if equals() is not consistent with the compareTo() method?............... 22
What happens if you compare an object to a null using equals()?.............................22
What is the difference in using instanceof and getClass() method for checking type
inside equals?............................................................................................................ 22
How do you avoid NullPointerException, while comparing two Strings in Java?....... 22
What is the difference between the "==" and the equals() method in Java?.............. 22
What is the purpose of the equals() method in Java?................................................ 22
Why is it important to override the equals() method?.................................................23
What is the contract between equals() and hashCode() methods?........................... 23
What is the default implementation of the hashCode() method in Java?................... 23
Why is it important to override the hashCode() method when you override the
equals() method?....................................................................................................... 23
What factors should you consider when choosing fields for calculating hash codes?...
23
How can you ensure that the hash code calculation remains consistent with the
equals() method?....................................................................................................... 23
What happens if you don't maintain the contract between equals() and hashCode()?..
23
How can you handle null values in equals() and hashCode() implementations?....... 23
Can hash codes be negative?....................................................................................24
What's the impact of frequently changing an object's state on its hash code?.......... 24
How do you ensure that an immutable class's equals() and hashCode() methods are
correctly implemented?.............................................................................................. 24
How do you handle inheritance while implementing equals() and hashCode()?........24
How would you implement the equals() and hashCode() methods for a class that
contains a collection as one of its fields?................................................................... 24
Can you provide an example of a scenario where a circular reference between two
objects causes problems with equals() and hashCode()?..........................................24
What's the danger of using mutable fields in the equals() and hashCode() methods?..
24
How would you handle the situation where two objects have circular references to
each other in their fields' comparison logic within the equals() method?................... 25
In a distributed system, how would you ensure that objects with the same content but
located on different machines have consistent hash codes?..................................... 25
Can the hashCode() method ever return the same value for two different objects? If
so, under what circumstances?.................................................................................. 25
How can you handle situations where the fields used for comparison in the equals()
method are sensitive to certain business rules that may change over time?............. 25
If you override the equals() method to consider only a subset of fields for equality, do
you need to include all fields in the hashCode() method?..........................................25
How would you implement a caching mechanism for hash codes to improve
performance?............................................................................................................. 25
How would you design a system to update an object's hash code automatically when
3
its mutable fields change?..........................................................................................26
How would you approach testing the correctness of your equals() and hashCode()
implementations?....................................................................................................... 26
In a multi-threaded environment, how would you ensure thread safety while
calculating hash codes for your objects?................................................................... 26
COLLECTION FRAMEWORK...............................................................................................26
How does HashMap work in Java?............................................................................ 26
What is the difference between poll() and remove() method of Queue interface?..... 27
What is the difference between fail-fast and fail-safe Iterators?.................................28
How do you remove an entry from a Collection? and subsequently what is the
difference between the remove() method of Collection and remove() method of
Iterator, which one you will use while removing elements during iteration?............... 29
What is the difference between Synchronized Collection and Concurrent Collection?..
30
What is the difference between Iterator and Enumeration?....................................... 30
How does HashSet is implemented in Java, How does it use Hashing?................... 31
What do you need to do to use a custom object as a key in Collection classes like
Map or Set?................................................................................................................ 32
The difference between HashMap and Hashtable?................................................... 33
When do you use ConcurrentHashMap in Java?.......................................................33
What is the difference between Set and List in Java?................................................33
How do you Sort objects in the collection?.................................................................34
What is the difference between Vector and ArrayList?...............................................35
What is the difference between HashMap and HashSet?.......................................... 36
What is NavigableMap in Java? What is a benefit over Map?................................... 36
Which one you will prefer between Array and ArrayList for Storing objects and why?..
37
Can we replace Hashtable with ConcurrentHashMap?..............................................38
What is CopyOnWriteArrayList, how is it different from ArrayList and Vector?.......... 38
Why ListIterator has added() method but Iterator doesn’t or Why to add() method is
declared in ListIterator and not in Iterator...................................................................38
When does ConcurrentModificationException occur on iteration?.............................38
Difference between Set, List and Map Collection classes?........................................39
What is BlockingQueue, how is it different from other collection classes?.................39
How does LinkedList is implemented in Java, is it a Singly or Doubly linked list?..... 40
How do you iterate over Synchronized HashMap, do you need to lock iteration and
why?........................................................................................................................... 40
What is Deque? When do you use it?........................................................................ 41
What is the Java Collection Framework?................................................................... 42
What are the core interfaces of the Java Collection Framework?..............................42
Differentiate between ArrayList and LinkedList.......................................................... 42
Explain the HashMap and HashTable differences......................................................42
What is the difference between HashSet and LinkedHashSet?................................. 42
Describe the TreeMap class.......................................................................................42
How does the Comparable interface relate to the Java Collection Framework?........42
Explain the purpose of the Comparator interface.......................................................42
4
What is the difference between fail-fast and fail-safe iterators?................................. 43
How does the Collections class provide utility methods for collections?....................43
What is the purpose of the java.util.concurrent package?..........................................43
Describe the WeakHashMap class.............................................................................43
What is the significance of the hashCode and equals methods in the context of
collections?.................................................................................................................43
Explain the identityHashCode method....................................................................... 43
How can you synchronize a non-thread-safe collection?........................................... 43
Describe the purpose of the EnumSet class.............................................................. 43
Explain the difference between HashSet and LinkedHashSet in terms of
performance............................................................................................................... 43
How does a ConcurrentHashMap handle concurrent read and write operations
efficiently?...................................................................................................................44
Can you achieve a concurrent sorted collection using the Java Collection
Framework? If yes, how?........................................................................................... 44
Explain the scenarios in which using CopyOnWriteArrayList might be beneficial...... 44
How does the WeakHashMap differ from other map implementations in terms of
memory management?.............................................................................................. 44
Describe a use case where you would prefer to use a NavigableMap over a
SortedMap.................................................................................................................. 44
How can you ensure thread safety while iterating over a collection without using
synchronization?........................................................................................................ 44
Can you implement a custom HashMap with a fixed size that removes the least
recently used element when the limit is reached?......................................................44
Explain the term "fail-safe" in the context of iterators and provide an example......... 45
Describe the difference between the addAll method of ArrayList and LinkedList...... 45
Can you implement a custom HashMap that guarantees insertion order, like
LinkedHashMap, without using the built-in LinkedHashMap class?...........................45
Explain how using a custom Comparator can lead to unexpected behaviour in a
TreeSet.......................................................................................................................45
Can you compare the memory usage of HashSet and TreeSet for storing the same
set of elements?......................................................................................................... 45
How can you perform a binary search on an element within an unsorted ArrayList? 45
Explain the internal structure of a LinkedHashMap and how it maintains insertion
order........................................................................................................................... 45
Describe a situation where using a WeakHashMap could lead to unexpected
behaviour.................................................................................................................... 45
STREAM AND FUNCTIONAL PROGRAMMING..................................................................46
What is the difference between Collection and Stream?............................................46
What does the map() function do? Why do you use it?..............................................46
What does the filter() method do? When do you use it?............................................ 47
What does the flatmap() function do? Why do you need it?.......................................48
What is the difference between flatMap() and map() functions?................................ 49
What is the difference between intermediate and terminal operations on Stream?... 49
What does the peek() method do? When should you use it?.....................................49
What do you mean by saying Stream is lazy?........................................................... 50
What is a functional interface in Java 8?....................................................................50
5
What is the difference between a normal and functional interface in Java?.............. 51
What is the difference between the findFirst() and findAny() method?.......................51
What is a Predicate interface?................................................................................... 52
What are Supplier and Consumer Functional interfaces?..........................................53
Can you convert an array to Stream? How?.............................................................. 54
What is the parallel Stream? How can you get a parallel stream from a List?........... 54
ArrayList
How to remove duplicates from ArrayList in Java?
To remove duplicates from an ArrayList in Java:
Choose the method that best suits your requirements and Java version. The Set-based approaches
are generally more efficient for larger lists.
Arrays use square brackets [] for syntax, while ArrayLists use the ArrayList class from java.util
package.
6
How to synchronise ArrayList in Java?
To synchronise an ArrayList in Java and make it thread-safe:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
// Example usage:
synchronizedList.add(1);
synchronizedList.add(2);
synchronizedList.add(3);
}
}
import java.util.ArrayList;
import java.util.List;
synchronized (arrayList) {
// Perform operations on the arrayList within the synchronized block
arrayList.add(1);
arrayList.add(2);
arrayList.add(3);
// ...
7
}
}
}
The first approach provides a convenient way to achieve synchronisation with less boilerplate code,
while the second approach gives you more control over synchronisation if you need to perform
multiple operations within a synchronised block.
Choose ArrayList when duplicates and order preservation are important. Choose HashSet when you
need unique elements and order is not significant.
● Use a traditional for loop with the size() method and get() method.
● Use an enhanced for-each loop to directly iterate over the elements of the ArrayList.
● Use an Iterator (Java 5 and later) obtained from the iterator() method to loop through the
ArrayList.
Given these differences, in modern Java development, ArrayList is typically preferred over Vector
unless you specifically require synchronisation for multi-threaded scenarios. If thread-safety is
needed, consider using Collections.synchronizedList() with ArrayList instead of Vector to get the best
of both worlds - thread-safety with better performance in single-threaded environments.
8
How to sort an ArrayList in Java?
● For a list of simple data types (e.g., Integer, String):
● Use Collections.sort() method to sort in ascending order.
● Example: Collections.sort(arrayList);
● For a list of custom objects with a natural ordering (implementing Comparable interface):
● Implement the Comparable interface in the custom class.
● Override the compareTo() method to define the sorting criteria.
● Use Collections.sort() method to sort the ArrayList.
● Example: Collections.sort(customObjectList);
Remember that the Collections.sort() method modifies the original ArrayList to sort it in-place. If you
need to preserve the original order, consider creating a copy of the ArrayList and sorting the copy.
Choose HashMap when you need to associate elements with unique keys for fast lookup, and choose
ArrayList when you need a simple ordered collection of elements.
What is the difference between the length() of the array and the size() of
ArrayList in Java?
● length is a final attribute of arrays, used to get the number of elements (fixed size).
● size() is a method of ArrayList, used to get the number of elements (dynamic size that can
change as elements are added or removed).
9
explicit synchronisation. Whenever the list is modified, a new copy of the underlying array is
created, ensuring that read operations can proceed without any data inconsistency during
modifications. It is best suited for scenarios with infrequent writes but frequent reads.
● Alternatively, you can use third-party libraries like Apache Commons Collections to achieve
the same result.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
● Alternatively, with Java 8 or later, use a lambda expression as the custom comparator
10
import java.util.ArrayList;
import java.util.Collections;
HashMap
How does the put() method of HashMap works in Java?
● Hashing: The method calculates the hash code of the given key to determine the storage
location.
● Indexing: The hash code is converted to an index within the array of buckets using modulo
operation.
● Collision Handling: If multiple keys have the same index, their values are stored in a linked
list (or tree) at that index.
● Adding Key-Value Pair: The new key-value pair is added to the appropriate bucket, either
creating a new entry or adding to an existing chain.
● Load Factor Check: If necessary, the map may be resized based on load factor to maintain
efficiency.
● Time Complexity: Average-case time complexity is O(1), but can degrade to O(n) in worst
case due to collisions.
● Replacement: If the key already exists, the old value is replaced with the new value.
Remember that a HashMap provides fast key-based access to values, but maintaining a
well-distributed set of keys and managing collisions are important for efficient performance.
Detailed
In Java, the put() method of the HashMap class is used to insert a key-value pair into the map. Here's
how the put() method works:
● Hashing: When you call the put(key, value) method, the hash code of the key is computed
using the hashCode() method of the key object. This hash code is used to determine the
bucket in which the key-value pair will be stored. HashMap maintains an array of buckets, and
each bucket can contain multiple key-value pairs.
● Index Calculation: The hash code is then transformed into an index within the range of the
array using the modulo operation (hash code % array length). This index indicates the bucket
where the key-value pair will be stored.
● Collision Handling: Hash collisions can occur when two different keys have the same hash
code or when their hash codes map to the same index in the array of buckets. To handle
collisions, HashMap uses a mechanism called separate chaining. Each bucket in the array
can be a linked list or a tree (in Java 8+), which holds all key-value pairs that have the same
index (hash collision).
● Adding Key-Value Pair: The new key-value pair is then added to the bucket. If the bucket is
initially empty, a new node (or entry) is created and inserted into the bucket. If there are
11
existing entries in the bucket due to hash collisions, the new entry is added to the front of the
linked list (or inserted into the tree).
● Size and Load Factor: After adding the key-value pair, the size of the HashMap is updated,
and if the load factor (current size / capacity) exceeds a certain threshold, the HashMap may
be resized to maintain efficient performance. Resizing involves creating a new larger array
and rehashing the existing key-value pairs into the new array.
● Time Complexity: The average-case time complexity of the put() method is O(1), assuming a
good distribution of hash codes and a well-maintained load factor. However, in the worst case,
when there are many hash collisions, the time complexity can degrade to O(n), where n is the
number of key-value pairs in the map.
Remember that keys in a HashMap must be unique. If you put a new value with a key that already
exists in the map, the old value associated with that key will be replaced with the new value.
● Immutable: Keys must be immutable, meaning their state cannot change after being added
to the map. This is because the hash code of a key is used to determine its storage location in
the map, and if the key's hash code changes, it could be stored in the wrong location.
● Consistent Hash Code: The hashCode() method of the key object must return the same
value for the duration of its presence in the map. This ensures that the key can be accurately
located within the map's buckets.
● Equals and HashCode Contracts: The key's equals() method must be properly implemented
to define the equality of key objects, and it must be consistent with the hashCode() method. If
two keys are considered equal, their hash codes should also be equal.
@Override
public int hashCode() {
return id; // Consistent hash code
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
12
if (obj == null || getClass() != obj.getClass()) return false;
Key otherKey = (Key) obj;
return id == otherKey.id; // Equality based on id
}
}
The HashMap doesn't call hashCode() on the null key because it will throw a NullPointerException.
Hence when a user calls the get() method with null, then the value of the first index is returned.
It's recommended to use null keys sparingly and ensure that they don't cause unexpected behaviour
in your application.
● Chaining: To handle collisions, a linked list (or a balanced tree in Java 8+) is used at each
bucket. Each node in the linked list (or tree) represents a key-value pair. New key-value pairs
with the same index are added to the front of the linked list (or tree) at that index.
● Searching for a Key: When you look up a value associated with a key, the HashMap
searches for the bucket corresponding to the calculated index. Then, it traverses the linked
list (or tree) within that bucket to find the correct key-value pair.
13
The average-case time complexity for search, insertion, and deletion in a well-distributed HashMap is
O(1), in the worst case, when many keys have the same hash code and collide in a single bucket, the
performance can degrade to O(n), where n is the number of key-value pairs in the map.
● Array of Buckets: The array serves as the primary storage structure. It is divided into
multiple "buckets," and each bucket can store one or more key-value pairs. The array is
allocated with an initial size, which can be dynamically resized as needed.
● Chaining: Within each bucket, HashMap uses linked lists (or trees) to handle hash collisions.
If multiple keys produce the same hash code and map to the same bucket, they are stored in
a linked list (or tree) within that bucket.
● In Java 7 and earlier versions, HashMap uses singly-linked lists for chaining in case of hash
collisions.
● In Java 8 and later versions, if a bucket's linked list becomes too long (due to many
collisions), the HashMap converts the linked list into a balanced binary search tree to maintain
O(log n) performance for key operations.
14
How do you remove a mapping while iterating over HashMap in Java?
In Java, you cannot remove a mapping directly using the remove() method of a HashMap while
iterating over it using an enhanced for loop or the enhanced Iterator. Doing so will result in a
ConcurrentModificationException because it detects that the map structure has changed during
iteration.
However, there are ways to safely remove mappings while iterating:
● Using Iterator:
○ You can use the iterator obtained from the entrySet() and explicitly call the iterator's
remove() method. This is the proper way to remove a mapping while iterating.
map = map.entrySet()
.stream()
.filter(entry -> /* some condition */)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
Using the Iterator is often the preferred choice for efficiency and control. Using the Iterator to remove
elements from a HashMap while iterating is preferred because it prevents exceptions, provides
efficient removal, offers controlled iteration, ensures consistency, and follows a widely accepted
practice in Java programming.
Since Java 8, the HashMap class itself doesn't provide ordering guarantees. If you need a HashMap
with predictable ordering, you might consider using LinkedHashMap instead.
However, starting from Java 9, a small optimization was introduced where the HashMap class tries to
maintain a partial insertion order, especially for small maps, to improve cache performance. But this
15
order is not the same as the insertion order, and it's not guaranteed to be consistent across different
runs or different JVM implementations.
If you need a map with a specific ordering, consider using the LinkedHashMap class, which maintains
a predictable insertion order.
import java.util.*;
16
How does resize happen in HashMap?
In a HashMap, resizing occurs when the number of entries surpasses a threshold based on the load
factor. During resizing:
Resizing maintains efficient performance by reducing collisions and ensuring a balanced distribution
of keys. However, it comes with a time and memory cost due to rehashing and redistribution.
How many entries can you store in HashMap? What is the maximum
limit?
The number of entries a HashMap can store is limited by the available memory of your system.
There's no strict maximum limit defined by Java, but practical constraints like memory availability and
performance considerations should guide your choices.
How can you ensure that objects used as keys in a HashMap have
correct hashcode and equals implementations?
To ensure correctness when using objects as keys, you should override the hashCode() and equals()
methods. Make sure that if two objects are equal according to equals(), their hash codes should also
be the same.
17
Can you use mutable objects as keys in a HashMap?
Using mutable objects as keys in a HashMap can lead to unexpected behaviour. If the object's
hashcode changes after being used as a key, it may not be retrievable from the HashMap. It's
recommended to use immutable objects or objects with stable hash codes as keys.
Explain how the initial capacity and load factor of a HashMap affect its
performance.
The initial capacity determines the size of the internal array used for storing elements. A larger initial
capacity reduces the frequency of resizing operations but consumes more memory. The load factor
determines when the HashMap should be resized to maintain performance. A lower load factor
reduces collisions but increases memory consumption. Balancing these two factors optimally is
important for HashMap's performance.
18
the same hash code, the HashMap essentially degrades to a linked list with O(n) performance for
many operations.
What happens if you modify a key object after it's been added to a
HashMap?
If you modify a key object after it's been added to a HashMap, its hash code might change, leading to
unpredictable behaviour. The HashMap might not be able to find the key and associated value
correctly, as it calculates the hash code at the time of insertion.
How can you make sure that a key's hash code doesn't change after
insertion into a HashMap?
To ensure that a key's hash code remains consistent after insertion, use only immutable objects as
keys. Changing a mutable object's state after insertion can lead to problems with key retrieval.
How would you handle a scenario where a large number of keys have
the same hash code?
If a large number of keys have the same hash code, the performance of the HashMap would degrade
significantly due to long linked lists. In such cases, you might consider using a different data structure,
like a balanced tree (e.g., TreeMap) or using a custom hash function that distributes keys more
evenly.
19
What is the purpose of the transient modifier in the HashMap class?
The transient modifier is used on fields in the HashMap class to indicate that they should not be
serialised. This is because during serialisation, the actual array contents of the HashMap are not
serialised, only the key-value pairs are.
ConcurrentHashMap
What is ConcurrentHashMap in Java?
ConcurrentHashMap is a thread-safe version of a hash map in Java. It allows multiple threads to read
and modify the map concurrently without causing conflicts. It uses techniques like segmentation and
lock striping to achieve high concurrency. This makes it useful for scenarios where multiple threads
need to work with a shared map without explicitly managing synchronisation.
20
Can one thread read and the other write on ConcurrentHashMap at
the same time?
Yes, in a ConcurrentHashMap, one thread can perform read operations while another thread
simultaneously performs write operations. The design of ConcurrentHashMap allows for concurrent
reading and writing without requiring explicit synchronisation between threads.
21
Can you pass an object of ConcurrentHahsMap when a Map is
expected?
Yes, you can pass a ConcurrentHashMap to a method or parameter that expects a Map object.
ConcurrentHashMap implements the Map interface, so it provides the necessary methods and
behaviour required by the Map interface. Just be aware that ConcurrentHashMap has its own
concurrency and thread-safety characteristics, which might affect its behaviour in concurrent
scenarios.
Can two objects which are not equal have the same hashCode?
Yes, two objects that are not equal can have the same hash code due to hash collisions. However,
objects that are equal should have the same hash code as part of the hash code contract for
hash-based collections.
How does the get() method of HashMap work if two keys have the same
hashCode?
When two keys in a HashMap have the same hash code, the get() method still works correctly, but it
involves an additional step called "equals comparison" to distinguish between these keys. Here's how
it works:
● Hash Code: The HashMap uses the hash code of the key to determine the initial bucket
(index) in which the corresponding value might be stored. Each bucket contains a linked list
(or a balanced tree in later versions) of key-value pairs that have the same hash code.
● Equals Comparison: Once the initial bucket is determined, the get() method goes through
the linked list (or the tree) associated with that bucket. It compares the provided key with the
keys in the list using the equals() method.
● Matching Key: If the equals() comparison finds a key that matches the provided key, the
corresponding value is returned. If multiple keys have the same hash code and one of them
matches, the correct value will be returned.
● No Matching Key: If the equals() comparison doesn't find a matching key in the linked list (or
the tree), then the get() method will return null, indicating that the requested key is not present
in the map.
22
It's important to note that hash collisions (when two keys have the same hash code) are common in
hash-based data structures like HashMap. The equals() comparison ensures that the correct
key-value pair is retrieved even in the presence of hash collisions.
Keep in mind that the efficiency of get() depends on how well-distributed the hash codes are and how
efficient the equals() method implementation is. In well-designed classes, hash codes are chosen to
minimise collisions and the equals() method accurately compares objects for equality.
In summary, when two keys have the same hash code in a HashMap, the get() method uses the hash
code to narrow down the search, and then uses the equals() method to identify the correct key-value
pair.
● Uniqueness of Id: If the Id field is guaranteed to be unique for each object and is a primary
identifier for object instances, including it in the equals() method can make sense. This is
often the case with database-generated or globally unique identifiers.
● Business Logic: If two objects with the same Id are considered equal regardless of other
attributes, including the Id field in the equals() method might be appropriate. This can be
especially relevant in scenarios where you want to compare instances based on their
business-level identity.
● Value-based Equality: If your class models a value type where equality is determined solely
by the values of the attributes, and the Id field is not relevant to this comparison, you might
choose to exclude it from the equals() method.
● Collection Membership: If objects are part of collections like HashSet or HashMap,
modifying an object's Id field after it's added to the collection can lead to unexpected
behaviour, as the object might not be found in the collection anymore.
● Consistency with hashCode(): If you include the Id field in the equals() method, ensure that
the hashCode() method is also consistent with it. Objects that are equal should have the
same hash code.
Remember that when you override the equals() method, you need to follow the contract specified in
the Java documentation. Specifically, the equals() method should be reflexive, symmetric, transitive,
consistent, and should return false for null inputs.
In summary, including the Id field in the equals() method depends on how you want to define object
equality for your class. If the Id field plays a significant role in determining equality, including it can be
appropriate. However, if equality is based solely on other attributes, excluding it might be a better
choice.
23
What happens if equals() is not consistent with the compareTo()
method?
If equals() is not consistent with the compareTo() method:
● If the equals() method handles comparisons with null, it will return false or true based on your
implementation.
● If the equals() method does not handle comparisons with null, it might throw a
NullPointerException if not properly implemented to account for null comparisons.
What is the difference between the "==" and the equals() method in
Java?
● == compares object references for reference equality.
● equals() compares object content for content equality. It's a method that can be overridden for
custom comparison logic.
24
Why is it important to override the equals() method?
Overriding the equals() method allows you to define how two objects of your class are considered
equal based on their content, rather than their memory references.
What factors should you consider when choosing fields for calculating
hash codes?
Choose immutable fields that are used in the equals() method for comparison. These fields should be
those that uniquely define the object's state.
How can you ensure that the hash code calculation remains consistent
with the equals() method?
The hash code calculation should be based on the same fields that are used in the equals() method
for comparison. Any change that affects equality should also be reflected in the hash code calculation.
What happens if you don't maintain the contract between equals() and
hashCode()?
It can lead to incorrect behaviour in hash-based collections, such as objects being lost or duplicated,
as well as inconsistent behaviour during lookups and removals.
25
What's the impact of frequently changing an object's state on its hash
code?
If an object's state changes while it's stored in a hash-based collection, it can lead to issues where the
object becomes "lost" in the collection or can't be retrieved due to a change in hash code.
How would you implement the equals() and hashCode() methods for a
class that contains a collection as one of its fields?
When dealing with collections in the equals() method, you would need to iterate through each element
of the collection and compare them individually. In the hashCode() method, you could calculate the
hash code based on the collection's hash code and the hash codes of its elements.
How would you handle the situation where two objects have circular
references to each other in their fields' comparison logic within the
equals() method?
To handle this situation, you might implement a custom comparison logic that takes circular
references into account and avoids infinite recursion. For example, you could use an identity-based
comparison for the fields involved in the circular reference.
26
In a distributed system, how would you ensure that objects with the
same content but located on different machines have consistent hash
codes?
You can use a distributed hash code algorithm, such as consistent hashing, which ensures that the
hash codes are consistent across machines, even if the objects are distributed on different nodes.
Can the hashCode() method ever return the same value for two different
objects? If so, under what circumstances?
Yes, hash codes can collide, meaning different objects can have the same hash code. This is known
as a hash collision. It can occur due to the limited range of integer hash codes and the nature of the
hashing algorithm used.
How can you handle situations where the fields used for comparison in
the equals() method are sensitive to certain business rules that may
change over time?
If the fields' comparison logic depends on changing business rules, you might consider encapsulating
the comparison logic within separate strategy classes. This way, you can switch between different
comparison strategies as the business rules change.
If you override the equals() method to consider only a subset of fields for
equality, do you need to include all fields in the hashCode() method?
It's recommended to include all fields used in the equals() method in the hashCode() method.
However, if you exclude certain fields, be aware that hash-based collections might not work correctly,
and you'll need to carefully consider the implications.
How would you approach testing the correctness of your equals() and
hashCode() implementations?
You should design test cases that cover different scenarios, including equal and unequal objects, null
values, edge cases, and situations where fields change after the object is added to a collection.
Additionally, you can use tools like libraries for property-based testing to generate a wide range of test
cases automatically.
27
In a multi-threaded environment, how would you ensure thread safety
while calculating hash codes for your objects?
You could synchronise access to the fields used for hash code calculation or use thread-safe data
structures, such as AtomicInteger or AtomicReference, to store the hash code. Alternatively, if the
fields are immutable, you don't need additional synchronisation.
COLLECTION FRAMEWORK
How does HashMap work in Java?
A HashMap in Java is a data structure that implements the Map interface, allowing you to store
key-value pairs. It provides fast and efficient access to values based on their associated keys. The
core mechanism behind how a HashMap works involves hashing and bucketing.
Here's how a HashMap works:
● Hashing:
○ When you put a key-value pair into a HashMap, the key's hashCode() method is
called to compute a hash code. The hash code is an integer that represents the key
and is used to index into an array.
● Bucketing:
○ The hash code is used to determine the index or bucket where the key-value pair will
be stored in the underlying array. This array of buckets is called the "hash table."
○ Multiple key-value pairs with the same hash code might be stored in the same bucket,
forming a linked list or a balanced tree (in Java 8+).
● Collisions:
○ Collisions occur when different keys produce the same hash code and need to be
stored in the same bucket. To handle collisions, the HashMap uses a linked list (or a
balanced tree) to store multiple entries in the same bucket.
● Searching:
○ When you want to retrieve a value based on a key, the hashCode() of the key is
computed again to determine the bucket.
○ The linked list (or tree) in the corresponding bucket is traversed to find the key-value
pair with the matching key.
● Resizing:
○ As the number of entries in the HashMap increases, the load factor (ratio of entries to
buckets) increases as well. When the load factor exceeds a certain threshold, the
HashMap is resized (rehashed), and the number of buckets is increased to maintain
efficient performance.
● Performance:
○ On average, the time complexity for put and get operations is O(1) when collisions
are minimised. However, in the worst case, when there are many collisions and the
hash table becomes degenerate, the time complexity can degrade to O(n), where n is
the number of entries.
28
Bucket 2: [Key3 -> Value3]
In this example, three buckets hold the key-value pairs, and collisions are resolved using linked lists
within each bucket.
Keep in mind that HashMap is not thread-safe for concurrent access by default. If you need
thread-safe behaviour, consider using ConcurrentHashMap or adding synchronisation mechanisms
when accessing the HashMap from multiple threads.
● poll() Method:
○ The poll() method retrieves and removes the head of the queue. If the queue is
empty, it returns null.
○ It's a non-throwing method, meaning it doesn't throw an exception if the queue is
empty.
○ It's commonly used in scenarios where you want to remove the first element if
present, but if the queue is empty, you get a null result instead of an exception.
● remove() Method:
○ The remove() method retrieves and removes the head of the queue. If the queue is
empty, it throws a NoSuchElementException.
○ It's a throwing method, which means it throws an exception when the queue is empty.
○ It's suitable when you expect the queue to contain elements and you want to remove
and retrieve the head element. If the queue is empty, throwing an exception might
indicate a logical error in your code.
29
System.out.println("Removed Element: " + removedElement); // Output: 2
In this example, poll() and remove() methods are used to retrieve and remove elements from the
queue. The key difference is in their behaviour when the queue is empty: poll() returns null, while
remove() throws an exception. Choose the appropriate method based on your expected use case and
whether you want to handle exceptions or check for null results.
And here's how you can use the remove() method of an iterator to remove an element during iteration:
30
List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
The key difference between the remove() methods of Collection and Iterator is their behaviour and
use cases during iteration:
When removing elements during iteration, it's recommended to use the remove() method of the
iterator (Iterator.remove()) to avoid potential concurrent modification exceptions and ensure safe
removal. This method is specifically designed to allow removal while iterating without disrupting the
iteration process.
31
○ Concurrent collections are part of the java.util.concurrent package and are specifically
designed to handle concurrent access more efficiently. They provide better
performance in multi-threaded environments compared to synchronized collections.
○ Concurrent collections use internal mechanisms such as locks, atomic operations,
and non-blocking algorithms to manage concurrent access without requiring external
synchronization.
○ Examples of concurrent collections include ConcurrentHashMap,
ConcurrentLinkedQueue, CopyOnWriteArrayList, etc.
○ These collections offer higher concurrency and scalability compared to synchronized
collections.
In modern applications, it's generally recommended to use Iterator over Enumeration due to its more
advanced features, support for removing elements during iteration, and its compatibility with a wider
range of collection types.
● Hashing Mechanism:
○ When you add an element to a HashSet, the hash code of the element is computed
using the hashCode() method of the element's object.
○ The hash code is an integer value that's used to determine the index or "bucket" in
the hash table where the element will be stored.
● Hash Table Structure:
○ Internally, a HashSet maintains a HashMap where the keys are the elements you add
to the set, and the values are dummy objects (usually a constant, such as PRESENT)
to save memory.
○ The HashMap handles the complexity of managing buckets and collisions.
● Adding Elements:
32
○ When you add an element to a HashSet, the element's hash code is used to compute
the bucket index where the element should be stored.
○ If the bucket is empty, the element is added directly.
○ If the bucket is not empty (collision occurs), the element is added to the linked list (or
balanced tree in Java 8+) of elements in that bucket.
● Checking for Element Existence:
○ When you check whether an element exists in a HashSet, its hash code is computed,
and the hash table is searched in the corresponding bucket.
○ If the element is found in the bucket, its equality is verified using the equals() method
to ensure it's not a collision-induced match.
● Performance:
○ On average, HashSet provides O(1) time complexity for basic operations like add(),
contains(), and remove().
○ However, if there are many hash collisions (elements with the same hashcode but
different values), the performance can degrade to O(n) in the worst case.
● Resizing and Rehashing:
○ As elements are added and removed, the load factor (ratio of elements to buckets) is
monitored.
○ If the load factor exceeds a threshold, the HashSet is resized and elements are
rehashed to new buckets to maintain efficient performance.
In summary, HashSet uses hashing to distribute elements efficiently across buckets in a hash table
structure. It leverages the underlying HashMap implementation for managing collisions and ensuring
that elements are unique within the set.
● Equals Method:
○ You need to override the equals() method in your custom object's class to define how
two instances of the object should be compared for equality.
○ The equals() method should compare the content of the custom object, not just their
references.
● Hashcode Method:
○ You need to override the hashCode() method in your custom object's class to provide
a consistent hash code for each instance of the object that's considered equal based
on the equals() method.
○ The hash code should be calculated based on the same attributes used for the
equals() comparison.
class CustomKey {
private String keyData;
33
this.keyData = keyData;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CustomKey customKey = (CustomKey) o;
return Objects.equals(keyData, customKey.keyData);
}
@Override
public int hashCode() {
return Objects.hash(keyData);
}
}
In this example, the CustomKey class overrides the equals() and hashCode() methods to allow
instances of CustomKey to be used as keys in a HashMap while ensuring proper equality comparison
and hash code generation.
By following these practices, you can use your custom object as a key in Map or Set collections,
allowing efficient storage, retrieval, and proper handling of duplicate keys.
34
In most cases, if you are working in a multi-threaded environment and require thread-safe access to
key-value pairs, consider using ConcurrentHashMap instead of Hashtable, as ConcurrentHashMap
provides better performance and fine-grained concurrency control.
● A Set is a collection that does not allow duplicate elements. Each element in a set is unique.
● The order of elements in a set is not guaranteed. The elements are not stored in a specific
order.
● The primary implementations of Set are HashSet, LinkedHashSet, and TreeSet.
● HashSet: Uses hash-based storage, providing constant-time complexity for basic operations.
● LinkedHashSet: Maintains insertion order using a linked list, offering slightly slower
performance than HashSet.
● TreeSet: Maintains elements in a sorted order using a red-black tree, providing log-time
complexity for basic operations.
List:
● A List is a collection that allows duplicate elements and maintains the order of insertion.
● The order of elements in a list is guaranteed and is the same as the order in which elements
were added.
● The primary implementations of List are ArrayList, LinkedList, and Vector.
● ArrayList: Uses a dynamic array to store elements, offering efficient random access and
linear-time complexity for insertions and deletions in the middle.
● LinkedList: Uses a doubly-linked list, providing efficient insertions and deletions at both ends
of the list but slower random access.
● Vector: Similar to ArrayList, but synchronized, making it thread-safe for concurrent access.
In summary:
● Use a Set when you want to store a collection of unique elements and don't care about their
order.
● Use a List when you want to maintain the order of insertion and allow duplicate elements.
Choose the implementation based on the required performance characteristics.
Choose between Set and List based on your specific use case and the characteristics of the elements
you're dealing with.
35
● Implement Comparable Interface:
○ If the objects you want to sort are instances of your custom class, you need to
implement the Comparable interface in that class. This interface requires you to
override the compareTo() method, which defines the natural ordering of objects.
● Override compareTo() Method:
○ In the compareTo() method, you define the comparison logic for your objects. This
method should return a negative integer if the current object is smaller, 0 if they are
equal, and a positive integer if the current object is larger than the other object being
compared.
● Use Sorting Algorithm:
○ After implementing Comparable and compareTo(), you can use various sorting
algorithms provided by the Collections class to sort your collection of objects.
@Override
public int compareTo(Person otherPerson) {
// Compare based on age
return this.age - otherPerson.age;
}
@Override
public String toString() {
return name + " (" + age + " years old)";
}
}
System.out.println("Before sorting:");
System.out.println(people);
Collections.sort(people);
36
System.out.println("\nAfter sorting:");
System.out.println(people);
}
}
In this example, the Person class implements the Comparable interface and overrides the
compareTo() method to compare objects based on their ages. The Collections.sort() method is used
to sort the list of Person objects based on their natural ordering (age in this case).
Remember that implementing Comparable allows you to define a single natural ordering for your
objects. If you need to sort objects in multiple ways, you might consider using a custom Comparator
instead.
37
What is NavigableMap in Java? What is a benefit over Map?
NavigableMap is an interface in the Java Collections Framework that extends the SortedMap
interface. It represents a map (key-value pairs) that is sorted according to the natural ordering of its
keys or a custom comparator. The NavigableMap interface provides additional methods for navigating
and manipulating the map in a sorted manner.
The primary benefit of NavigableMap over the basic Map interface is that it offers enhanced
navigational and searching capabilities, making it easier to work with sorted maps:
Key Features of NavigableMap:
● Navigation Methods:
○ lowerKey(key): Returns the greatest key strictly less than the given key.
○ floorKey(key): Returns the greatest key less than or equal to the given key.
○ ceilingKey(key): Returns the smallest key greater than or equal to the given key.
○ higherKey(key): Returns the smallest key strictly greater than the given key.
● Submap Views:
○ subMap(fromKey, toKey): Returns a view of the map that is a sub-map of the original
map.
○ headMap(toKey): Returns a view of the portion of the map that is less than the
specified key.
○ tailMap(fromKey): Returns a view of the portion of the map that is greater than or
equal to the specified key.
● Descending Views:
○ descendingMap(): Returns a reverse order view of the map.
By using a NavigableMap, you can perform operations that are tailored to sorted data without needing
to manually sort the map or implement custom search algorithms. This makes the code more
readable, efficient, and less error-prone.
One common implementation of NavigableMap is the TreeMap, which is a sorted map based on a
red-black tree. You can use NavigableMap when you need to work with sorted data and require
advanced search and navigation capabilities beyond what the basic Map interface provides.
Which one you will prefer between Array and ArrayList for Storing
objects and why?
The choice between using an array and an ArrayList for storing objects depends on the specific
requirements of your application. Both options have their own advantages and trade-offs. Here's a
comparison to help you decide:
Array:
● Arrays are a fundamental data structure in Java and provide direct access to elements using
an index.
● Arrays have a fixed size once created. You need to know the size beforehand or reallocate
and copy elements to resize.
● Memory consumption can be slightly more efficient than ArrayList because it doesn't have the
overhead of dynamic resizing and an underlying array.
● Arrays are less flexible when it comes to adding or removing elements, as you need to
manually manage these operations.
● No built-in methods for common operations like resizing, adding, removing, or searching.
ArrayList:
38
● ArrayList is part of the Java Collections Framework and provides dynamic resizing of its
underlying array as needed.
● ArrayList is more flexible in terms of adding and removing elements, as you can use methods
like add(), remove(), and set().
● ArrayList provides convenient methods for common operations like searching (contains(),
indexOf()), resizing (ensureCapacity()), and iterating (forEach()).
● ArrayList automatically handles resizing and copying of elements when needed, so you don't
need to worry about managing the array's size.
● Memory overhead due to dynamic resizing and additional methods.
Considerations:
● If you know the fixed size of the collection and memory efficiency is a concern, an array might
be preferable.
● If you need dynamic resizing, easy addition/removal of elements, and built-in methods for
common operations, ArrayList is a better choice.
In most cases, ArrayList is more convenient and flexible for storing objects because it provides
dynamic resizing and a set of useful methods. However, if you have specific memory constraints or
know the exact size of the collection in advance, using an array might be more suitable.
Key Differences:
39
Why ListIterator has added() method but Iterator doesn’t or Why to add()
method is declared in ListIterator and not in Iterator.
The add() method is declared in the ListIterator interface but not in the basic Iterator interface
because adding elements at specific positions is more relevant and common for lists. The ListIterator
interface is specialised for lists and provides more capabilities, including the ability to traverse in both
directions and add elements at the current position. The basic Iterator interface focuses on read-only
traversal, where adding elements is less common and might lead to confusion or unexpected
behaviour.
40
● Blocking Behaviour:
○ When a thread tries to remove an element from an empty BlockingQueue, it will be
blocked until an element becomes available.
○ When a thread tries to add an element to a full BlockingQueue, it will be blocked until
space becomes available.
● Synchronisation:
○ BlockingQueue implementations handle synchronisation internally, ensuring
thread-safe operations without the need for external synchronisation mechanisms.
● Producer-Consumer Scenarios:
○ BlockingQueue is particularly useful in producer-consumer scenarios, where multiple
threads are producing items for processing and other threads are consuming them.
● Wait and Notify:
○ Blocking operations utilise the wait() and notify() mechanisms internally to efficiently
manage thread blocking and unblocking.
● Built-in Methods:
○ BlockingQueue provides methods for adding, removing, and inspecting elements, as
well as special methods that allow timed waits and interruptible operations.
● Various Implementations:
○ Java provides various implementations of BlockingQueue, such as
ArrayBlockingQueue, LinkedBlockingQueue, and PriorityBlockingQueue, each with
different characteristics.
● Unlike other collection classes, which may provide methods to manually block or synchronise,
BlockingQueue offers built-in mechanisms for blocking and synchronisation.
● BlockingQueue is designed for scenarios where coordination between producer and
consumer threads is required, ensuring efficient use of system resources and reducing
busy-waiting.
● Other collection classes like ArrayList, LinkedList, or HashMap are not optimised for
concurrent access and require external synchronisation to ensure thread safety.
41
● Synchronise the Whole Iteration:
You can synchronise the iteration process itself to ensure that no concurrent modifications
occur during the iteration. To do this, you should acquire a lock on the synchronised HashMap
while iterating. This will prevent other threads from modifying the map while you're iterating.
// ...
synchronized (synchronizedMap) {
for (Map.Entry<KeyType, ValueType> entry : synchronizedMap.entrySet()) {
// Process the entry
}
}
● Use Iterator:
It's recommended to use an iterator while iterating over a collection, as it provides additional
benefits like fail-safe behaviour (no ConcurrentModificationException) and better control over
modifications.
// ...
synchronized (synchronizedMap) {
Iterator<Map.Entry<KeyType, ValueType>> iterator =
synchronizedMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<KeyType, ValueType> entry = iterator.next();
// Process the entry
}
}
By synchronising the iteration process, you ensure that the map is not modified by other threads
during your iteration, avoiding potential issues. Keep in mind that this approach might reduce the
benefits of parallelism if multiple threads need to access the map concurrently. If you require both
iteration and concurrent modifications, consider using a more advanced concurrent collection like
ConcurrentHashMap or external synchronisation mechanisms.
42
● Two Ends: Unlike a regular queue, which only supports adding at one end and removing
from the other, a Deque allows operations on both ends (front and back).
● Methods: Some common methods provided by the Deque interface include addFirst(),
addLast(), removeFirst(), removeLast(), getFirst(), and getLast(), among others.
● Implementations: Java provides several implementations of the Deque interface, such as
ArrayDeque (a resizable array-based implementation) and LinkedList (a doubly linked
list-based implementation).
● Double-Ended Queues: When you need a queue-like data structure that supports efficient
addition and removal of elements from both ends. Examples include tasks involving
breadth-first traversal, sliding windows, and maintaining a history of recent operations.
● Queue Simulation: For simulating scenarios where elements are added and removed from
both ends, such as simulating processes or tasks.
● Efficient Stacks: A Deque can also be used as a stack by using only one end for pushing
and popping elements. This can provide better performance compared to using the Stack
class.
● Implementing Algorithms: Algorithms involving bidirectional traversal or data manipulation
can benefit from the flexibility of a Deque.
In summary, a Deque is a versatile data structure that allows efficient addition and removal of
elements from both ends. It's particularly useful when you need to work with double-ended queues,
simulate queue-based scenarios, implement efficient stacks, or solve algorithms requiring bidirectional
traversal.
43
Describe the TreeMap class.
TreeMap is an implementation of the SortedMap interface, using a Red-Black tree structure to store
key-value pairs in sorted order.
How does the Collections class provide utility methods for collections?
The Collections class provides static methods like sort, binarySearch, reverse, etc., for common
operations on collections that aren't directly supported by the collection interfaces.
44
Describe the purpose of the EnumSet class.
EnumSet is a specialized implementation of the Set interface designed for use with enum types,
offering efficient and type-safe storage of enum constants.
Can you achieve a concurrent sorted collection using the Java Collection
Framework? If yes, how?
Yes, you can use the ConcurrentSkipListSet and ConcurrentSkipListMap classes to achieve
concurrent sorted collections. They're implemented as skip lists, providing efficient concurrent access
and sorted order.
How can you ensure thread safety while iterating over a collection
without using synchronization?
You can use a CopyOnWriteArrayList to iterate without synchronization, ensuring that modifications
won't affect ongoing iterations. However, it's not efficient for frequent modifications.
45
Can you implement a custom HashMap with a fixed size that removes
the least recently used element when the limit is reached?
Yes, you can implement this using a combination of a LinkedHashMap (to maintain insertion order)
and overriding its removeEldestEntry method to remove the least recently used element when the
size exceeds the limit.
Can you compare the memory usage of HashSet and TreeSet for storing
the same set of elements?
HashSet generally uses less memory than TreeSet because it only needs to store elements, while
TreeSet requires additional memory for maintaining the binary search tree structure.
46
Describe a situation where using a WeakHashMap could lead to
unexpected behaviour.
If you use WeakHashMap to store keys in a cache-like scenario and don't maintain strong references
to the values, they might be unexpectedly removed by the garbage collector, leading to cache misses.
What does the map() function do? Why do you use it?
The map() function is a common operation used in programming, especially in functional
programming and when working with collections or sequences of data. It is used to transform
each element in a collection or sequence into a new element by applying a specified function
to each element. The result is a new collection or sequence with the transformed elements.
In the context of Java streams (or similar constructs in other languages), the map() function
is often used to transform the elements of a stream. Here's how it works:
For example, in Java, if you have a stream of integers and you want to create a new stream
with each integer doubled, you can use the map() function as follows:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> doubledNumbers = numbers.stream()
.map(n -> n * 2)
.collect(Collectors.toList());
In this code, the map() function takes a lambda function that doubles each integer in the
original stream, and the result is collected into a new list. The doubledNumbers list will
contain [2, 4, 6, 8, 10].
Common use cases for map() include:
● Data Transformation: Converting data from one form to another. For example,
transforming strings to uppercase, converting objects to specific properties, or
changing data types.
47
● Projection: Selecting specific attributes or properties from complex objects, creating a
new stream with just those attributes.
● Calculations: Applying mathematical operations or other calculations to elements in a
collection.
● Value Extraction: Extracting values from a collection of objects, such as extracting
names from a list of people.
map() is a fundamental tool for working with data in a more functional and declarative style,
making code more concise and readable. It is especially powerful when combined with other
stream operations like filter(), reduce(), and collect(), enabling complex data processing
tasks to be expressed in a clear and concise manner.
What does the filter() method do? When do you use it?
The filter() method is a common operation in programming, particularly in functional
programming and when working with collections or sequences of data. It is used to select
elements from a collection or sequence that satisfy a specified condition. The result is a new
collection or sequence containing only the elements that meet the condition.
In the context of Java streams (or similar constructs in other languages), the filter() function
is used to filter elements of a stream based on a given predicate. Here's how it works:
1. Input: You have a stream of data with various elements.
2. Operation: You provide a predicate function that defines the condition that an element
must meet to be included in the result. The predicate function is applied to each element in
the stream.
3. Output: The result is a new stream with only the elements that satisfy the condition
specified by the predicate.
For example, in Java, if you have a stream of integers and you want to create a new stream
containing only the even numbers, you can use the filter() function as follows:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
In this code, the filter() function takes a lambda function that checks if each integer in the
original stream is even, and the result is collected into a new list. The evenNumbers list will
contain [2, 4, 6, 8].
48
● Data Reduction: Reducing the size of a dataset by excluding elements that are not
relevant to a particular task.
filter() is an essential tool for working with data in a functional and declarative style. It allows
you to express filtering conditions in a clear and concise manner, making your code more
readable and maintainable. It is often used in combination with other stream operations like
map(), reduce(), and collect() to perform complex data processing tasks efficiently.
What does the flatmap() function do? Why do you need it?
The flatMap() function is a commonly used operation in functional programming and when
working with collections or sequences of data. It is used to transform the elements of a
nested collection or structure and flatten them into a single, new collection. The primary use
case for flatMap() is to handle situations where you have a collection of collections (or a
nested structure) and want to transform and flatten it into a single collection.
In the context of Java streams (or similar constructs in other languages), the flatMap()
function is used to apply a mapping function to each element of a stream, where the
mapping function returns another stream. The results from these inner streams are then
combined into a single stream, effectively flattening the nested structure.
For example, in Java, suppose you have a list of lists of integers, and you want to flatten
them into a single stream of integers:
List<List<Integer>> nestedLists = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5, 6),
Arrays.asList(7, 8, 9)
);
In this code, the flatMap() function takes each inner list (an element of the outer list) and
flattens them into a single stream of integers. The flatList will contain [1, 2, 3, 4, 5, 6, 7, 8, 9].
49
● Handling Nested Data: When you have collections within collections, such as lists
within a list, and you want to work with the inner elements as if they were part of a
single collection.
● Flattening Maps: When you have a map of keys to collections, and you want to work
with the elements within those collections as a single collection.
● Handling Optional Values: When you have optional values (e.g., Optional<T>) and
you want to extract the values while handling the possibility of absence (using
flatMap(Optional::stream)).
● Data Transformation: When you need to transform data within nested structures and
maintain a flat structure for further processing.
flatMap() is a powerful tool for working with complex data structures and simplifying the
processing of nested collections. It's particularly useful when you want to apply a
transformation to elements within nested structures and then work with the flattened result in
a single collection or stream.
What does the peek() method do? When should you use it?
The peek() method is an intermediate operation in Java Streams that is used to perform a
side effect on the elements of the stream without changing the stream's content. It is often
used for debugging or for observing the elements as they pass through the stream pipeline.
The primary purpose of peek() is to inspect the elements in the stream without modifying
them.
Here's how peek() works:
1. Input: You have a stream with a sequence of elements.
2. Operation: You provide a consumer function that is applied to each element in the stream.
This function can perform actions like printing the element, logging, or updating some
external state.
3. Output: The original stream is returned, and the elements are unchanged. The primary
purpose of peek() is not to transform elements but to observe them.
Example:
50
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> result = numbers.stream()
.map(n -> n * 2)
.peek(n -> System.out.println("Processing: " + n))
.collect(Collectors.toList());
In this code, peek() is used to log each element before it's collected into the result list. The
result will contain [2, 4, 6, 8, 10], and the log messages will show the elements being
processed.
It's important to note that while peek() is a powerful tool for debugging and monitoring, it
should be used with caution in production code because it introduces side effects. If you use
peek() for logging or debugging, it's a good practice to ensure that it doesn't affect the
correctness or performance of your program.
In summary, peek() is an intermediate operation in Java Streams that allows you to observe
and log the elements as they pass through the stream pipeline without modifying them. It is
particularly useful for debugging, logging, and monitoring the behaviour of your stream
operations.
51
1. Single Abstract Method: A functional interface must declare one and only one abstract
(unimplemented) method. This method represents the abstract behaviour to be implemented
by the interface's functional instances.
2. Default Methods: Functional interfaces can have default methods, which are methods with
default implementations provided in the interface. These methods are not counted as
abstract methods.
3. Marker Annotations: Java provides the @FunctionalInterface annotation to explicitly mark
an interface as a functional interface. While it's not strictly necessary to use this annotation, it
helps prevent accidental violation of the single abstract method rule.
4. Lambda Expressions: Functional interfaces are designed to work seamlessly with lambda
expressions. Lambda expressions allow you to concisely create instances of functional
interfaces by specifying the behaviour of the single abstract method.
With a functional interface defined, you can use it to create instances using lambda
expressions:
MyFunction addition = (a, b) -> a + b;
MyFunction subtraction = (a, b) -> a - b;
Functional interfaces are at the core of Java's support for functional programming, allowing
you to use lambda expressions and method references to write more concise and
expressive code, especially when working with functional-style operations on collections and
streams. They are used extensively in Java's Stream API and other libraries to represent
behaviour to be applied to data.
52
● A functional interface in Java declares exactly one abstract method and can also
have default and static methods. It is intended for use with lambda expressions and
method references to express single units of behaviour concisely, making it a core
concept in Java's support for functional programming. The @FunctionalInterface
annotation is often used to mark an interface as a functional interface.
Example:
Optional<Integer> firstEven = numbers.stream()
.filter(n -> n % 2 == 0)
.findFirst();
2. findAny():
● findAny() is a terminal operation that returns any element that matches the given
condition (or any element in the stream, if no condition is provided).
● Unlike findFirst(), findAny() does not guarantee a deterministic result. The specific
element returned can vary between invocations or when working with parallel
streams.
● findAny() is typically used when you need any element that satisfies the condition but
does not require a specific order or result.
Example:
Optional<Integer> anyEven = numbers.stream()
.filter(n -> n % 2 == 0)
.findAny();
In summary:
● findFirst() returns the first element that matches the condition, ensuring a
deterministic result and making it suitable for ordered streams.
53
● findAny() returns any element that matches the condition and does not guarantee a
deterministic result, making it suitable for unordered streams or when you only need
any element that meets the criteria.
The choice between findFirst() and findAny() depends on your specific use case and
whether you prioritise order or simply need any matching element.
Where T is the type of the input object to be tested, and the method test takes an input of
type T and returns a boolean value, indicating whether the condition defined by the predicate
is satisfied or not.
Here's a simple example of using the Predicate interface to check if a number is even:
Predicate<Integer> isEven = n -> n % 2 == 0;
Predicate is commonly used in various contexts in Java, such as when working with Java
Streams for filtering elements based on a condition or when writing general-purpose code to
check conditions. It allows you to express and encapsulate boolean conditions as objects,
making your code more readable and facilitating reusability.
54
Supplier<String> messageSupplier = () -> "Hello, World!";
String message = messageSupplier.get(); // Returns "Hello, World!"
2. Consumer:
* The Consumer functional interface represents an operation that consumes or processes
data, typically without producing any result.
* It defines a single abstract method: void accept(T t), which takes an argument of type T
and performs an action on it.
* Consumers are frequently used for performing side effects, like printing or logging, on
elements in a collection, stream, or other data source.
Example:
Consumer<String> printConsumer = s -> System.out.println("Print: " + s);
printConsumer.accept("Hello, World!"); // Outputs "Print: Hello, World!"
Both Supplier and Consumer are fundamental to functional programming in Java and are
often used in combination with other functional interfaces such as Predicate and Function to
work with collections, streams, and perform various operations on data. They make code
more expressive and allow for a more functional and declarative programming style.
1. Using Arrays.stream():You can use the Arrays.stream() method to create a Stream from
an array of elements. This method is available for arrays of all types.
Example:
String[] array = {"apple", "banana", "cherry"};
Stream<String> streamFromArray = Arrays.stream(array);
2. Using Arrays.asList():
You can first convert the array into a List using Arrays.asList() and then obtain a Stream from
the List using the stream() method available for collections.
Example:
Integer[] array = {1, 2, 3, 4, 5};
List<Integer> list = Arrays.asList(array);
Stream<Integer> streamFromList = list.stream();
55
Both of these methods allow you to convert an array into a Stream, which enables you to
use the powerful Stream API for various data processing tasks, including filtering, mapping,
and reducing elements in the array.
What is the parallel Stream? How can you get a parallel stream from a
List?
In Java, a parallel stream is a special type of stream that is designed to take advantage of
multi-core processors for parallel processing. It allows you to perform operations on data in
parallel, which can significantly improve the performance of certain data processing tasks,
especially when working with large datasets.
To obtain a parallel stream from a List or any other collection, you can use the
parallelStream() method, which is available for collections that implement the Collection
interface, such as List, Set, and Map. Here's how you can get a parallel stream from a List:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Once you have a parallel stream, you can use it to perform operations in parallel. For
example, you can use parallel streams with operations like filter(), map(), and reduce() to
process data concurrently, which can lead to improved performance on multi-core
processors. Here's an example of using a parallel stream to double the values in a list:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Keep in mind that parallel streams are most effective when working with large datasets and
when the operations can be parallelised efficiently. However, they are not always the best
choice for all data processing tasks, as there is an overhead associated with parallelism, and
it's important to consider the nature of the problem and the hardware you are running the
code on.
Parallel streams are a powerful feature in Java that allows for more efficient use of multi-core
processors, but they should be used judiciously to achieve the desired performance benefits
while avoiding unnecessary complexity in your code.
56