0% found this document useful (0 votes)
4 views

Java - Collections

Uploaded by

kmewar981
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views

Java - Collections

Uploaded by

kmewar981
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 56

ArrayList.................................................................................................................................

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:

● Using a Set (Java 8 and later):


● Convert the ArrayList to a Set to remove duplicates automatically.
● Convert the Set back to an ArrayList.
● Using a LinkedHashSet (Java 7 and later):
● Convert the ArrayList to a LinkedHashSet to preserve order and remove duplicates.
● Convert the LinkedHashSet back to an ArrayList.
● Using a loop and a new ArrayList (Java 5 and later):
● Create a new ArrayList.
● Iterate through the original ArrayList, adding elements to the new ArrayList only if
they are not already present.

Choose the method that best suits your requirements and Java version. The Set-based approaches
are generally more efficient for larger lists.

How to reverse ArrayList in Java?


To reverse an ArrayList in Java, you can use the Collections class, which provides a reverse()
method. The Collections.reverse() method modifies the original ArrayList in place, reversing its
elements. Keep in mind that this method is applicable to any List implementation, not just ArrayLists.

What is the difference between an array and an ArrayList in Java?


The key differences between arrays and ArrayLists in Java are:

● Arrays have a fixed size, while ArrayLists have a dynamic size.


● Arrays can store both primitive types and objects, while ArrayLists can only store objects.
● Arrays have limited built-in methods, while ArrayLists provide a rich set of methods for
manipulation.
● Arrays might have slightly better performance for simple operations, while ArrayLists have
some overhead due to resizing.

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:

● Use Collections.synchronizedList() method:


○ Create a regular ArrayList.
○ Use Collections.synchronizedList() to obtain a synchronised version of the ArrayList.
○ The synchronised list can be used safely with multiple threads without explicit
synchronisation.

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class SynchronizedArrayListExample {


public static void main(String[] args) {
List<Integer> arrayList = new ArrayList<>();
List<Integer> synchronizedList =
Collections.synchronizedList(arrayList);

// Now, 'synchronizedList' is a thread-safe ArrayList


// You can use it with multiple threads without explicit synchronization

// Example usage:
synchronizedList.add(1);
synchronizedList.add(2);
synchronizedList.add(3);
}
}

● Use explicit synchronisation with synchronised blocks:


○ Create a regular ArrayList.
○ Use synchronised blocks to perform operations on the ArrayList within the blocks.
○ Ensure that all threads accessing the ArrayList synchronise on the same object (the
ArrayList itself).

import java.util.ArrayList;
import java.util.List;

public class SynchronizedArrayListExample {


public static void main(String[] args) {
List<Integer> arrayList = new ArrayList<>();

// ... Initialize or add elements to the arrayList ...

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.

When to use ArrayList and LinkedList in Java?


● Use ArrayList when you need frequent random access, read operations, and dynamic
array-like behaviour.
● Use LinkedList when you need frequent insertions or deletions in the middle of the list and
when sequential access (iteration) is more common than random access.

What is the difference between ArrayList and HashSet in Java?


● ArrayList allows duplicates and maintains the order of elements based on their insertion.
● HashSet does not allow duplicates and does not maintain any specific order of elements. It
stores unique elements based on their hash codes.

Choose ArrayList when duplicates and order preservation are important. Choose HashSet when you
need unique elements and order is not significant.

How to loop over ArrayList in Java?


To loop over an ArrayList in Java:

● 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.

What is the difference between Vector and ArrayList in Java?


Both Vector and ArrayList are classes that implement the List interface and are used to store
collections of elements.

● Vector is synchronised, providing thread-safety but with slower performance in


single-threaded environments.
● ArrayList is not synchronised by default, offering better performance in single-threaded
scenarios.
● Use ArrayList in modern Java development unless you specifically need thread-safety, in
which case you can use Collections.synchronizedList() with 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.

What is the difference between HashMap and ArrayList in Java?


● HashMap is used to store key-value pairs, providing fast access based on unique keys.
● ArrayList is used to store elements in a linear order and provides fast access based on
numerical indices.

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.

How to convert ArrayList to String in Java?


● Use the toString() method to get the default representation of the ArrayList, which includes
elements enclosed in square brackets and separated by commas.

How to get a sublist from ArrayList in Java?


● Use the subList() method of the ArrayList to extract a portion of the original list as a new
sublist.
● The method takes two parameters: the starting index (inclusive) and the ending index
(exclusive) of the sublist.
● The subList() method returns a view of the original list, not a separate copy.
● If you need an independent copy of the sublist, create a new ArrayList from the sublist using
the constructor, passing the sublist as an argument.

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).

What is CopyOnWriteArrayList in Java?


● CopyOnWriteArrayList is a thread-safe implementation of a dynamic array in Java's
java.util.concurrent package. It allows safe concurrent access from multiple threads without

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.

How to remove objects from ArrayList in Java?


● Use the remove() method with the object you want to remove to delete the first occurrence of
that object.
○ Example: arrayList.remove("banana");
● Use the remove() method with the index of the element you want to remove to delete an
element by its index.
○ Example: arrayList.remove(1);
● Use an iterator to safely remove elements while iterating over the ArrayList.

How to make ArrayList read-only in Java?


● Use Collections.unmodifiableList() to get an unmodifiable view of the ArrayList.

Example: List<String> readOnlyList = Collections.unmodifiableList(originalList);

● Alternatively, you can use third-party libraries like Apache Commons Collections to achieve
the same result.

Example: Collection<String> readOnlyList =


CollectionUtils.unmodifiableCollection(originalList);
After obtaining the read-only view, any attempt to modify the list will result in an
UnsupportedOperationException, but you can still read elements from the list.

How to sort an ArrayList in descending order in Java?


● Use the Collections.sort() method with a custom comparator that reverses the comparison
logic.

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

ArrayList<Integer> arrayList = new ArrayList<>();


// Add elements to the arrayList

Collections.sort(arrayList, new Comparator<Integer>() {


@Override
public int compare(Integer num1, Integer num2) {
return num2 - num1; // Sort in descending order
}
});

● Alternatively, with Java 8 or later, use a lambda expression as the custom comparator

10
import java.util.ArrayList;
import java.util.Collections;

ArrayList<Integer> arrayList = new ArrayList<>();


// Add elements to the arrayList

Collections.sort(arrayList, (num1, num2) -> num2 - num1); // Sort in


descending order

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.

What is the requirement for an object to be used as a key or value in


HashMap?
For an object to be used as a key or value in a HashMap in Java, it needs to satisfy certain
requirements:
Requirements for Keys:

● 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.

Requirements for Values:


There are no specific additional requirements for values beyond those mentioned for keys. However,
it's generally a good practice for values to be immutable or, at the very least, not to change in ways
that affect their equality while they're stored in the map.
class Key {
private final int id;

public Key(int id) {


this.id = id;
}

@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
}
}

public class HashMapExample {


public static void main(String[] args) {
HashMap<Key, String> map = new HashMap<>();
Key key1 = new Key(1);
Key key2 = new Key(2);

map.put(key1, "Value 1");


map.put(key2, "Value 2");

System.out.println(map.get(key1)); // Output: Value 1


}
}

Can you store a null key in Java HashMap?


Yes, you can store one null key in a Java HashMap, ​which is stored at the first location of the bucket
array. In Java, a HashMap allows one null key to be stored. When you call the put(null, value) method,
the value will be associated with the null key. Similarly, you can retrieve the value associated with the
null key using the get(null) method.

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.

Can you store a null value inside HashMap in Java?


Yes, you can store a null value inside a Java HashMap. In a HashMap, both keys and values are
allowed to be null. This means you can associate a null value with a key in the map.

How does HashMap handle collisions in Java?


HashMap in Java handles collisions using a technique called "chaining". When two different keys
produce the same hash code (hash collision) or map to the same bucket due to a hash code collision,
the HashMap uses a linked list (or a balanced tree in Java 8+) to store multiple key-value pairs in the
same bucket.

● 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.

Which data structure does HashMap represent?


In Java, the HashMap class represents a data structure that combines the concepts of an array and
linked lists (or trees in Java 8+) to efficiently store and manage key-value pairs. It is a form of hash
table, a widely used data structure for fast key-based access.
The underlying data structure of a HashMap includes two main components:

● 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.

Can you store the duplicate value in Java HashMap?


Yes, you can store duplicate values in a Java HashMap, but the keys must still be unique. Different
keys can map to the same value, creating duplicate values associated with different keys.

Is HashMap thread-safe in Java?


No, the standard HashMap is not thread-safe in Java. If you need thread-safe behaviour, consider
using ConcurrentHashMap or applying external synchronisation mechanisms.

What will happen if you use HashMap in a multithreaded Java


application?
Using a non-thread-safe HashMap in a multithreaded Java application without proper synchronisation
can lead to issues like inconsistent data, exceptions, data corruption, and race conditions. To avoid
these problems, use ConcurrentHashMap, apply synchronisation, use thread-local maps, or consider
immutable maps.

What are the different ways to iterate over HashMap in Java?


● Using Entry Set (Recommended): Iterate using entrySet() to access keys and values
together efficiently. Iterating over a HashMap using the entrySet() method is recommended
and efficient because it retrieves both keys and values together, reducing lookups and
providing faster iteration compared to other approaches.
● Using Key Set: Iterate using keySet() to access keys and then retrieve values.
● Using Values Collection: Iterate using values() to access values but not keys.
● Using Iterators: Use iterators to iterate with more control over the process.

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.

HashMap<String, Integer> map = new HashMap<>();


// Add key-value pairs to the map

Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();


while (iterator.hasNext()) {
Map.Entry<String, Integer> entry = iterator.next();
if (/* some condition */) {
iterator.remove(); // Safely removes the current entry
}
}

● Using Stream API (Java 8+):


○ You can also use the Java 8+ Stream API to filter and collect the entries you want to
keep into a new map.

HashMap<String, Integer> map = new HashMap<>();


// Add key-value pairs to the map

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.

In which order are mappings stored in HashMap?


The order of mappings in a standard Java HashMap is not guaranteed to be in any specific order. The
order can vary based on factors such as hash codes, bucket positions, and the way elements were
added to the map. In other words, the mappings in a HashMap are unordered.

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.

Can you sort HashMap in Java?


A standard HashMap in Java cannot be directly sorted, as it does not inherently maintain any specific
order for its entries. However, if you want to achieve a sorted order for the entries in a map, you can
follow these steps:

● Copy the entries to a List.


● Sort the List using a custom comparator.
● Create a new LinkedHashMap and insert the sorted entries.

import java.util.*;

public class SortHashMapExample {


public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<>();
map.put("one", 1);
map.put("three", 3);
map.put("two", 2);

List<Map.Entry<String, Integer>> entryList = new


ArrayList<>(map.entrySet());

Collections.sort(entryList, Map.Entry.comparingByKey()); // Sorting by


keys

LinkedHashMap<String, Integer> sortedMap = new LinkedHashMap<>();


for (Map.Entry<String, Integer> entry : entryList) {
sortedMap.put(entry.getKey(), entry.getValue());
}

System.out.println(sortedMap); // Output: {one=1, three=3, two=2}


}
}
Keep in mind that this approach creates a new map with sorted entries while leaving the original
HashMap unchanged. If you frequently need sorted mappings, you might consider using data
structures like TreeMap or external libraries that provide sorted map implementations.

What is the load factor in HashMap?


The load factor in a HashMap is a value between 0 and 1 that determines when the hash table should
resize itself. When the number of entries reaches the current capacity multiplied by the load factor, the
HashMap automatically increases its capacity and redistributes the elements to maintain efficient
performance. The default load factor is 0.75, but you can set a custom load factor when creating a
HashMap instance.

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:

● A new hash table with doubled capacity is created.


● Existing entries are rehashed to new hash codes.
● Entries are redistributed into the new hash table.
● Load factor remains the same; no update needed.

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.

What is the difference between the capacity and size of HashMap in


Java?
● Capacity: Number of buckets in the internal array; can change dynamically and affects
memory and collision handling.
● Size: Count of key-value pairs stored in the map; changes as entries are added or removed.

What is the time complexity for adding, retrieving, and removing


elements from a HashMap?
On average, adding, retrieving, and removing elements from a HashMap is O(1), assuming a
well-distributed hash function. However, in worst-case scenarios, it can degrade to O(n) due to
collisions leading to long linked lists.

How does HashMap determine equality between keys?


HashMap determines equality between keys by using the equals() method. It first compares the hash
codes of the keys, and if they match, it uses the equals() method to compare the keys for exact
equality.

What is the purpose of the hashCode() method in HashMap?


The hashCode() method is used to calculate the hash code of a key, which is used to determine the
index where the key-value pair will be stored within the HashMap. It is crucial for efficient key retrieval.

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.

What happens if you insert a duplicate key into a HashMap?


If you insert a duplicate key into a HashMap, the existing key's value will be replaced with the new
value associated with the duplicate key.

How do you iterate through the entries of a HashMap?


You can iterate through the entries of a HashMap using methods like entrySet() and an enhanced for
loop or an iterator.

What's the difference between HashMap and ConcurrentHashMap?


HashMap is not thread-safe, whereas ConcurrentHashMap is designed to support concurrent
operations safely. ConcurrentHashMap achieves this by dividing the map into segments and providing
fine-grained locks.

How can you make a HashMap thread-safe?

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?
You might consider using a TreeMap when you need the keys to be sorted in a natural order or a
custom order. TreeMap is a sorted map implementation, whereas HashMap does not guarantee any
specific order of keys.

How can you make a deep copy of a HashMap?


To make a deep copy of a HashMap, you need to create new instances of both the keys and values
while iterating through the original HashMap and populating the new HashMap.

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.

How does HashMap's performance degrade as the number of collisions


increases?
As the number of collisions increases, the linked lists within buckets grow longer. This leads to
increased time complexity for retrieval and other operations. In extreme cases, where all keys have

18
the same hash code, the HashMap essentially degrades to a linked list with O(n) performance for
many operations.

Can a custom object be used as a key in a HashMap without


implementing hashCode() and equals()?
Technically, a custom object can be used as a key without overriding hashCode() and equals().
However, it's strongly recommended to override these methods to ensure proper functioning of
HashMap and correct behaviour when used in collections.

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.

Is there a way to get the exact position of an element in a HashMap?


No, the HashMap class does not provide a direct method to get the exact position of an element.
HashMap is designed to allow efficient retrieval based on keys, but it doesn't expose the internal index
calculations.

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.

Can you guarantee the order of elements when iterating through a


HashMap?
No, the iteration order of elements in a HashMap is not guaranteed to be in any specific order. If you
need a specific order, you should use a LinkedHashMap, which maintains insertion order, or a
TreeMap for sorted order.

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.

Can you explain the internal data structure of a HashMap?


Internally, a HashMap uses an array of linked lists (buckets). Each bucket contains a list of key-value
pairs. When a collision occurs, new entries are added to the linked list within the corresponding
bucket.

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.

How does the hashCode() function affect the performance of a


HashMap?
The quality of the hashCode() function directly impacts the distribution of keys across buckets. An
uneven distribution might lead to more collisions and performance degradation. A well-distributed
hash code function results in a more efficient HashMap.

How does the Java 8 update improve performance of HashMap?


Java 8 introduced a more efficient collision handling mechanism called "tree bins" for HashMaps.
When the number of elements in a bucket exceeds a certain threshold, the linked list is transformed
into a balanced tree. This improves retrieval time for elements in buckets with high collision rates,
reducing the worst-case performance from O(n) to O(log n).

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.

How does ConcurrentHashMap achieve thread safety?


ConcurrentHashMap achieves thread safety by dividing its data into segments, each with its own lock.
This allows multiple threads to read from and write to different segments simultaneously, reducing
contention. Inside each segment, lock striping further divides the data, enabling concurrent read
access and efficient write synchronisation. This approach ensures that multiple threads can work on
the map concurrently without causing conflicts or data inconsistencies.

Can multiple threads read from ConcurrentHashMap same time?


Yes, ConcurrentHashMap allows concurrent reading without locking, as reading operation doesn't
require locking or thread safety.

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.

How does ConcurrentHashMap work internally?


ConcurrentHashMap works by dividing its data into segments, each with its own lock. Within each
segment, data is further divided into stripes, each with its lock. This allows multiple threads to read
and write concurrently without conflicts. Read operations can occur simultaneously, and write
operations in different segments can proceed in parallel. This segmentation, fine-grained locking, and
lock striping ensure thread safety and high concurrency.

How do you atomically update a value in ConcurrentHashMap?


You can atomically update a value in a ConcurrentHashMap using the compute family of methods
(compute, computeIfPresent, computeIfAbsent). These methods take a key and a function that
calculates the new value based on the existing value (if any) and returns the updated value. The
update is performed atomically, ensuring thread safety.

How do you remove a mapping while iterating over


ConcurrentHashMap?
To remove a mapping from a ConcurrentHashMap while iterating over it:

● Use an iterator obtained from entrySet().iterator() of the map.


● When you want to remove an entry, use the iterator's remove() method.
● This method ensures safe removal without causing concurrency issues or disrupting ongoing
iterations.

Does the Iterator of ConcurrentHashMap fail-safe or fail-fast?


The iterator of ConcurrentHashMap is fail-safe. It operates on a snapshot of the data taken when the
iterator was created and doesn't throw ConcurrentModificationException if the map is modified during
iteration. This allows safe concurrent iteration and modification.

What will happen if you add a new mapping in ConcurrentHashMap


while one thread is iterating over it?
In a ConcurrentHashMap, if you add a new mapping while one thread is iterating over it, the iterating
thread might or might not see the new mapping. It depends on whether the new mapping was added
before or after the iterator was obtained. The iterator works on a snapshot taken at iterator creation,
so it won't throw errors, but the new mapping might not be visible to the iterator.

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.

Equals and HashCode


When you are writing the equals() method, which other method or
methods do you need to override?
When you override the equals() method in a class, it's recommended to also override the hashCode()
method. The equals() method compares object contents for equality, while the hashCode() method
generates a hash code used by hash-based collections. If two objects are equal according to equals(),
their hashCode() values must be the same for proper behaviour in collections.

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.

Where have you written equals() and hashCode in your project?


In your own projects, you would typically implement the equals() and hashCode() methods for classes
where you want to define custom equality comparisons and hash code calculations. These methods
are often implemented for classes that you intend to use as keys in hash-based collections like
HashMap or HashSet.

Suppose your Class has an Id field; should you include it in equals()?


Why?
Whether or not you should include an Id field in the equals() method depends on the semantics of
your class and the role of the Id field in defining object equality.
Here are a few considerations to help you decide:

● 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:

● Sorting and sorted collections may behave unexpectedly.


● Algorithms relying on consistent comparisons can fail or produce incorrect results.
● Objects considered equal by equals() should have a compareTo() that returns 0 for
consistency and predictable behaviour.

What happens if you compare an object to a null using equals()?


When comparing an object to null using the equals() 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 in using instanceof and getClass() method for


checking type inside equals?
In equals():

● instanceof checks type compatibility, including subclasses.


● getClass() ensures strict type matching, without considering subclasses.
● Use instanceof when you want to treat subclasses as equal.
● Use getClass() for exact type matching.

How do you avoid NullPointerException, while comparing two Strings in


Java?
To avoid NullPointerException while comparing strings in Java:

● Use the equals() method for content comparison.


● Place known non-null strings first in the comparison.
● Use the ternary operator to handle null and non-null cases.

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.

What is the purpose of the equals() method in Java?


The equals() method is used to compare the contents of two objects for equality. It is defined in the
Object class and can be overridden in user-defined classes to provide 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 is the contract between equals() and hashCode() methods?


According to the contract, if two objects are equal (as per the equals() method), their hash codes must
also be equal (as per the hashCode() method). However, the reverse is not necessarily true: objects
with equal hash codes are not required to be equal.

What is the default implementation of the hashCode() method in Java?


The default implementation in the Object class uses the memory address of the object to generate the
hash code.

Why is it important to override the hashCode() method when you


override the equals() method?
Overriding only the equals() method can lead to inconsistencies in hash-based data structures (e.g.,
HashMap), as the default hashCode() implementation will generate different hash codes for equal
objects.

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.

How can you handle null values in equals() and hashCode()


implementations?
You should account for null values in the equals() method by using Objects.equals() for comparisons,
and in the hashCode() method, you can use Objects.hash() to handle null values in fields.

Can hash codes be negative?


Yes, hash codes can be negative, as the hashCode() method returns an int. Collections like HashMap
and HashSet can handle negative hash codes without issues.

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 do you ensure that an immutable class's equals() and hashCode()


methods are correctly implemented?
For an immutable class, you can ensure that the fields used for comparison in equals() are all final
and initialised in the constructor. The hashCode() method can use the hash codes of these fields
directly.

How do you handle inheritance while implementing equals() and


hashCode()?
When dealing with inheritance, you should call the parent class's equals() method and include its
hash code in the calculation of the subclass's hashCode() method, along with the subclass-specific
fields. Always remember to maintain the contract between equals() and hashCode() in both parent
and subclass implementations.

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.

Can you provide an example of a scenario where a circular reference


between two objects causes problems with equals() and hashCode()?
Circular references can lead to stack overflow errors when comparing objects in the equals() method.
Similarly, infinite recursion can occur when calculating hash codes in the hashCode() method. Avoid
circular references or implement custom handling for such cases.

What's the danger of using mutable fields in the equals() and


hashCode() methods?
Using mutable fields can lead to unexpected behavior if the fields are changed after the object is
added to a hash-based collection. The hash code might change, leading to difficulties in finding or
removing objects from the collection.

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 implement a caching mechanism for hash codes to


improve performance?
You can create a transient field within the class to store the hash code once it's calculated. In the
hashCode() method, check if the cached hash code is present; if not, calculate and cache it. This can
significantly improve performance if hash codes are frequently requested.

How would you design a system to update an object's hash code


automatically when its mutable fields change?
You could use an observer pattern, where the object's mutable fields are observed, and any changes
trigger an update to the hash code. Alternatively, you could make the object's fields immutable and
create a new instance with updated fields when changes occur.

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.

Here's a simplified visual representation of how a HashMap works:


HashMap:
Bucket 0: [Key1 -> Value1] -> [Key4 -> Value4]
Bucket 1: [Key2 -> Value2] -> [Key5 -> Value5]

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.

What is the difference between poll() and remove() method of Queue


interface?
Both poll() and remove() methods are part of the Queue interface in Java, which represents a
collection that holds elements and supports various operations related to adding, removing, and
examining elements. However, there is a key difference between these two methods:

● 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.

Here's a usage example of both methods:


import java.util.LinkedList;
import java.util.Queue;

public class QueueMethodsExample {


public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<>();

// Add elements to the queue


queue.add(1);
queue.add(2);
queue.add(3);

// Using poll() method


Integer polledElement = queue.poll();
System.out.println("Polled Element: " + polledElement); // Output: 1

// Using remove() method


Integer removedElement = queue.remove();

29
System.out.println("Removed Element: " + removedElement); // Output: 2

// Remove all remaining elements


queue.remove(); // Removes 3

// Try removing an element from an empty queue using remove()


// This will throw a NoSuchElementException
try {
queue.remove();
} catch (Exception e) {
System.out.println("Exception: " + e); // Output:
java.util.NoSuchElementException
}
}
}

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.

What is the difference between fail-fast and fail-safe Iterators?


● Fail-Fast Iterators: Detect concurrent modifications and throw exceptions immediately to
ensure data consistency. They are used in non-thread-safe collections.
● Fail-Safe Iterators: Work on a copy of the collection's data and do not throw exceptions for
concurrent modifications. They provide a stable view of the collection at the time of iteration
and are often used in thread-safe collections.

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?
To remove an entry from a collection in Java, you can use the remove() method provided by the
Collection interface. The remove() method of the Collection interface removes the specified element
from the collection based on its value. You can also use the remove() method of an iterator to remove
the current element during iteration.
Here's how you can use the remove() method of the Collection interface:
List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);

numbers.remove(2); // Removes the element with value 2

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);

Iterator<Integer> iterator = numbers.iterator();


while (iterator.hasNext()) {
Integer number = iterator.next();
if (number == 2) {
iterator.remove(); // Removes the current element (2)
}
}

The key difference between the remove() methods of Collection and Iterator is their behaviour and
use cases during iteration:

● remove() Method of Collection:


○ This method removes the first occurrence of the specified element from the collection
based on its value.
○ If multiple occurrences of the same element exist, only the first occurrence is
removed.
○ It returns a boolean indicating whether the element was removed.
● remove() Method of Iterator:
○ This method removes the current element (the last element returned by next()) from
the collection.
○ It can be called only once per call to next().
○ It doesn't require specifying the value of the element to remove since it removes the
element the iterator is currently pointing to.
○ It's a safe way to remove elements during iteration without causing concurrent
modification exceptions.

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.

What is the difference between Synchronized Collection and Concurrent


Collection?
● Synchronized Collections:
○ Synchronized collections are traditional Java collections (e.g., ArrayList, HashMap,
HashSet, etc.) that have been synchronized externally using synchronization blocks
or methods. You can create a synchronized collection using the
Collections.synchronizedXxx() methods, where Xxx represents the type of collection.
○ These synchronized collections ensure thread safety by synchronizing all methods
that modify the collection. This means that only one thread can modify the collection
at a time, preventing concurrent modifications.
○ While synchronized collections provide thread safety, they might result in reduced
performance due to contention for the lock when multiple threads are accessing the
collection concurrently.
● Concurrent Collections:

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.

When designing multi-threaded applications, it's recommended to use concurrent collections


whenever possible, as they offer better performance and scalability while handling concurrent access
to collections.

What is the difference between Iterator and Enumeration?

Feature Iterator Enumeration


Package java.util java.util
Introduced Java 1.2 (JDK 1.2) Java 1.0
Methods hasNext(), next(), remove() hasMoreElements(), nextElement()
Remove Yes (using remove()) No
Support
Concurrency Fail-fast (throws exceptions on change) Fail-safe (no exceptions on
change)
Usage Most Java Collections Framework Legacy classes and interfaces
classes

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.

How does HashSet is implemented in Java, How does it use Hashing?


HashSet in Java is implemented using a hash table, specifically the HashMap class. It uses hashing
to provide efficient storage and retrieval of elements while ensuring that each element is unique within
the set.
Here's how HashSet works and uses hashing:

● 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.

What do you need to do to use a custom object as a key in Collection


classes like Map or Set?
To use a custom object as a key in collection classes like Map or Set, you need to ensure that the
custom object meets the requirements for proper usage as a key. Specifically, you need to consider
two important aspects: equality and hash code.

● 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.

Here's an example of how to define a custom object to be used as a key in a HashMap:


import java.util.Objects;

class CustomKey {
private String keyData;

public CustomKey(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);
}
}

public class CustomKeyExample {


public static void main(String[] args) {
CustomKey key1 = new CustomKey("abc");
CustomKey key2 = new CustomKey("abc");

System.out.println(key1.equals(key2)); // Output: true

HashMap<CustomKey, String> map = new HashMap<>();


map.put(key1, "Value associated with key1");

System.out.println(map.get(key2)); // Output: Value associated with key1


}
}

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.

The difference between HashMap and Hashtable?

Feature HashMap Hashtable


Synchronisatio Not synchronised Synchronized
n
Null Values Allows null keys and values Doesn't allow null keys or values
Performance Better performance (unsynchronized) Slower performance (due to sync)
Inheritance Extends AbstractMap, implements Map Extends Dictionary
Concurrency Not designed for concurrent access Designed for concurrent access
Usage Single-threaded, external Multi-threaded, legacy,
synchronisation synchronisation

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.

When do you use ConcurrentHashMap in Java?


You should use ConcurrentHashMap in Java when you need to manage concurrent access to a map
(key-value pairs) in a multi-threaded environment. It's a thread-safe alternative to the regular
HashMap that provides better performance and scalability in scenarios where multiple threads need to
access, update, or modify the map concurrently.

What is the difference between Set and List in Java?


In Java, both Set and List are interfaces that are part of the Java Collections Framework, but they
represent different types of collections with distinct characteristics. Here are the key differences
between Set and List:
Set:

● 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.

How do you Sort objects in the collection?


To sort objects in a collection in Java, you can follow these steps:

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.

Here's an example using a custom class named Person:


import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

class Person implements Comparable<Person> {


private String name;
private int age;

public Person(String name, int age) {


this.name = name;
this.age = age;
}

@Override
public int compareTo(Person otherPerson) {
// Compare based on age
return this.age - otherPerson.age;
}

@Override
public String toString() {
return name + " (" + age + " years old)";
}
}

public class SortingExample {


public static void main(String[] args) {
List<Person> people = new ArrayList<>();
people.add(new Person("Alice", 30));
people.add(new Person("Bob", 25));
people.add(new Person("Charlie", 35));

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.

What is the difference between Vector and ArrayList?

Feature Vector ArrayList


Synchronisation Synchronised Not synchronised
Performance Slower due to synchronisation Faster (non-concurrent scenarios)
Growth and Doubles capacity Increases capacity by 50%
Resizing
Inheritance Extends AbstractList, implements List Extends AbstractList, implements
List
Concurrency Thread-safe (synchronised) Not thread-safe
Usage Multi-threaded, legacy, Single-threaded, external sync
synchronisation
Memory Overhead Higher due to synchronisation Lower (no synchronisation)
In most modern applications, ArrayList is preferred due to its better performance and lower memory
overhead. If you require thread safety and synchronisation, you might consider using ArrayList with
external synchronisation or using the more advanced concurrent collections provided by the
java.util.concurrent package.

What is the difference between HashMap and HashSet?

Feature HashMap HashSet


Interface Map Set
Elements Key-value pairs Unique elements
Retrieval Retrieve values using keys No direct retrieval of elements
Order Order of elements is not Order of elements is not
guaranteed guaranteed
Use Associating values with unique Storing a collection of unique items
Cases keys
In summary, use HashMap when you need to associate values with unique keys, and use HashSet
when you need to store a collection of unique elements without key-value associations. Both
collections use hashing for efficient storage and retrieval.

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.

Can we replace Hashtable with ConcurrentHashMap?


Yes, you can generally replace instances of Hash-table with ConcurrentHashMap for better
concurrency performance and scalability in multi-threaded environments. Just be aware of the
differences in behaviour and features between the two classes.

What is CopyOnWriteArrayList, how is it different from ArrayList and


Vector?
CopyOnWriteArrayList is a specialised thread-safe implementation of the List interface in the Java
Collections Framework. It is designed to provide concurrent access to a list of elements without the
need for external synchronisation. The key difference between CopyOnWriteArrayList, ArrayList, and
Vector lies in their concurrency mechanisms.

Key Differences:

● CopyOnWriteArrayList allows concurrent read operations without synchronisation overhead


but incurs a cost when write operations occur due to copying.
● ArrayList and Vector require external synchronisation to ensure thread safety during both read
and write operations.
● CopyOnWriteArrayList is suitable when there are many reads and few writes, while ArrayList
and Vector are better when there are more write operations or when you control
synchronisation externally.

In summary, CopyOnWriteArrayList is a thread-safe alternative to ArrayList and Vector that optimises


for scenarios with frequent read operations and fewer write operations. It ensures thread safety for
reads without external synchronisation but introduces copying overhead for writes.

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.

When does ConcurrentModificationException occur on iteration?


A ConcurrentModificationException occurs during iteration when a collection is modified by another
thread while it is being iterated over. It's a safety mechanism to prevent inconsistent or corrupted data
due to concurrent modifications. Proper synchronisation or using thread-safe collection classes can
help avoid this exception. If you need to modify a collection while iterating over it, you can use
thread-safe collection classes like CopyOnWriteArrayList or ConcurrentHashMap, or you can use
explicit synchronisation mechanisms to ensure safe concurrent access.

Difference between Set, List and Map Collection classes?

Feature Set List Map


Purpose Collection of unique Ordered collection of Collection of key-value
elements elements pairs
Duplicates Does not allow duplicates Can have duplicate Allows duplicate keys
elements (not values)
Order No guaranteed order of Maintains insertion No guaranteed order of
elements order entries
Retrieval Retrieve elements by Retrieve elements by Retrieve values by key
value index
Common HashSet, LinkedHashSet, ArrayList, LinkedList HashMap,
Implementations TreeSet LinkedHashMap,
TreeMap
Key Characteristics Uniqueness, efficient Indexed access, Key-based access,
membership check efficient traversal efficient retrieval
Use Case Storing unique values Maintaining ordered Associating values with
sequence keys
Keep in mind that the choice between these collection types depends on the specific requirements of
your application. Each type is designed to fulfil different use cases, and the right choice will depend on
factors like data uniqueness, ordering, retrieval patterns, and more.

What is BlockingQueue, how is it different from other collection classes?


A BlockingQueue is a specialised interface in the Java Collections Framework that represents a
queue with additional blocking and synchronisation features. It's designed for scenarios where
multiple threads are producing and consuming elements from a shared queue. The key difference
between a BlockingQueue and other collection classes is its ability to block operations when the
queue is empty (for consumers) or full (for producers), providing synchronisation and coordination
between threads.
Key Features of BlockingQueue:

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.

Comparison with Other Collection Classes:

● 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.

In summary, a BlockingQueue provides synchronisation and blocking behaviour to efficiently handle


scenarios where multiple threads need to coordinate in a producer-consumer pattern. It simplifies
thread coordination and eliminates the need for explicit synchronisation, making it an essential tool for
concurrent programming.

How does LinkedList is implemented in Java, is it a Singly or Doubly


linked list?
In Java, the LinkedList class is implemented as a doubly linked list. It has references to both the
previous and next elements, enabling efficient insertion, removal, and traversal in both directions.

How do you iterate over Synchronized HashMap, do you need to lock


iteration and why?
When iterating over a synchronised HashMap (or any synchronised collection) in Java, you should be
cautious about potential concurrent modifications that could occur while you're iterating. Simply
synchronising on the map itself might not be sufficient to prevent such modifications, as other threads
can still modify the map even while it's locked. This can lead to ConcurrentModificationException or
unexpected behaviour.
To properly iterate over a synchronised HashMap, follow these steps:

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.

Map<KeyType, ValueType> synchronizedMap = Collections.synchronizedMap(new


HashMap<>());

// ...

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.

Map<KeyType, ValueType> synchronizedMap = Collections.synchronizedMap(new


HashMap<>());

// ...

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.

What is Deque? When do you use it?


A Deque (short for "double-ended queue") is a linear collection that supports adding and removing
elements from both ends with constant time complexity. It's an interface in the Java Collections
Framework that extends the Queue interface and provides methods for adding, removing, and
inspecting elements at both ends of the queue.
Key characteristics of a Deque:

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).

Use cases for a Deque:

● 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.

What is the Java Collection Framework?


The Java Collection Framework is a set of interfaces, classes, and algorithms provided by Java to
represent and manipulate groups of objects as a single unit.

What are the core interfaces of the Java Collection Framework?


The core interfaces are List, Set, and Map. List maintains an ordered collection, Set holds a unique
collection, and Map is used to store key-value pairs.

Differentiate between ArrayList and LinkedList.


ArrayList uses a dynamically resizing array to store elements, providing fast random access but
slower insertion and deletion. LinkedList uses a doubly-linked list, enabling fast insertion and deletion,
but slower random access.

Explain the HashMap and HashTable differences.


Both HashMap and HashTable store key-value pairs, but HashTable is synchronized and doesn't
allow null keys/values, while HashMap is not synchronized and permits null values/keys.

What is the difference between HashSet and LinkedHashSet?


HashSet is an unordered collection that uses hash codes to store elements, whereas LinkedHashSet
maintains the insertion order using a doubly-linked list.

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 Comparable interface relate to the Java Collection


Framework?
The Comparable interface allows objects to define their natural ordering, which is used by sorted
collections like TreeSet and TreeMap.

Explain the purpose of the Comparator interface.


The Comparator interface allows you to define custom ordering for objects, enabling you to sort
elements in a collection based on your own criteria.

What is the difference between fail-fast and fail-safe iterators?


Fail-fast iterators throw a ConcurrentModificationException if a collection is modified while being
iterated. Fail-safe iterators work on a copy of the data and don't throw exceptions but may not reflect
the latest changes.

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.

What is the purpose of the java.util.concurrent package?


The java.util.concurrent package provides classes for building concurrent and multi-threaded
applications, including thread-safe collections like ConcurrentHashMap and CopyOnWriteArrayList.

Describe the WeakHashMap class.


WeakHashMap is a map implementation that uses weak references to keys, allowing keys to be
garbage collected if they're not referenced elsewhere.

What is the significance of the hashCode and equals methods in the


context of collections?
The hashCode method is used to generate a hash code for an object, and the equals method is used
to compare objects for equality. These methods are crucial for proper functioning of hash-based
collections.

Explain the identityHashCode method.


The identityHashCode method is used to get the hash code of an object based on its memory
reference rather than its content.

How can you synchronize a non-thread-safe collection?


You can use the Collections.synchronizedXXX methods to wrap a non-thread-safe collection and
make it thread-safe. For example, Collections.synchronizedList().

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.

Explain the difference between HashSet and LinkedHashSet in terms of


performance.
HashSet has better performance for basic operations due to its hash-based nature, while
LinkedHashSet maintains order with a slight performance trade-off due to additional linked list
maintenance.

How does a ConcurrentHashMap handle concurrent read and write


operations efficiently?
ConcurrentHashMap divides the map into segments, allowing multiple threads to perform concurrent
operations on different segments, minimizing contention and improving performance.

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.

Explain the scenarios in which using CopyOnWriteArrayList might be


beneficial.
CopyOnWriteArrayList is useful when you have a high number of read operations and relatively fewer
write operations. It creates a new copy of the underlying array for each modification, making reads
thread-safe without locking.

How does the WeakHashMap differ from other map implementations in


terms of memory management?
WeakHashMap uses weak references for keys, allowing them to be garbage collected when no other
references exist, which can help manage memory usage in scenarios where keys might become
unreachable.

Describe a use case where you would prefer to use a NavigableMap


over a SortedMap.
When you need to find elements within a range or perform range-based operations (e.g., subMap,
headMap, tailMap), NavigableMap provides more efficient methods compared to the basic
SortedMap.

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.

Explain the term "fail-safe" in the context of iterators and provide an


example.
Fail-safe iterators operate on a copy of the data, allowing concurrent modifications without throwing
exceptions. An example is the iterator of CopyOnWriteArrayList.

Describe the difference between the addAll method of ArrayList and


LinkedList.
ArrayList's addAll method is more efficient when adding multiple elements, while LinkedList's addAll
method can be slower due to its linked structure and individual element insertion.

Can you implement a custom HashMap that guarantees insertion order,


like LinkedHashMap, without using the built-in LinkedHashMap class?
Yes, you can achieve this by combining a HashMap and a doubly-linked list. Store keys and values in
the HashMap and maintain a doubly-linked list of entries to preserve insertion order.

Explain how using a custom Comparator can lead to unexpected


behaviour in a TreeSet.
If a custom Comparator doesn't fulfil the requirements of a valid ordering (transitivity, consistency,
etc.), the TreeSet might not behave as expected, leading to unpredictable sorting results.

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.

How can you perform a binary search on an element within an unsorted


ArrayList?
You can use the Collections.binarySearch method after sorting the ArrayList. However, this approach
is not efficient for unsorted lists due to the sorting step.

Explain the internal structure of a LinkedHashMap and how it maintains


insertion order.
LinkedHashMap maintains a doubly-linked list of entries in addition to the hash table. When an entry
is accessed, it's moved to the end of the list, preserving insertion order.

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.

STREAM AND FUNCTIONAL PROGRAMMING


What is the difference between Collection and Stream?
● Collections are data structures that store and manage data in a mutable form,
suitable for direct access and modification.
● Streams are sequences of data elements that are processed in a functional and
immutable manner, with lazy evaluation, making them ideal for data transformation
and processing without changing the original data.

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:

1. Input: You have a stream of data with certain elements.


2. Operation: You provide a mapping function that defines how to convert each element of
the stream. This function is applied to each element in the stream.
3. Output: The result is a new stream with the transformed elements, according to the
mapping function.

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].

Common use cases for filter() include:


● Data Filtering: Selecting elements that meet specific criteria, such as filtering
numbers based on their value, filtering a list of objects based on certain properties, or
excluding elements that don't meet a certain condition.
● Data Validation: Verifying the validity of data by removing or flagging elements that
do not conform to specific rules or constraints.
● Data Cleansing: Removing outliers or data anomalies from a dataset.

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.

Here's how it works:


1. Input: You have a stream of data where each element is itself a collection or another
stream.
2. Operation: You provide a mapping function that transforms each element into a stream of
elements. This function is applied to each element in the outer stream.
3. Output: The result is a single, flattened stream containing all the elements from the inner
streams.

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)
);

List<Integer> flatList = nestedLists.stream()


.flatMap(Collection::stream)
.collect(Collectors.toList());

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].

Common use cases for flatMap() include:

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 is the difference between flatMap() and map() functions?


● map() is used to transform each element of a collection or sequence into a new
element, resulting in a new collection with the same structure.
● flatMap() is used to transform elements into multiple elements and flatten the result,
producing a single, flattened collection or sequence. It's useful for handling nested
data structures and complex data transformations.

What is the difference between intermediate and terminal operations on


Stream?
● Intermediate operations transform or filter the data within a stream, returning a new
stream and setting up a processing pipeline. They are lazy and don't trigger the
actual computation.
● Terminal operations produce a final result or perform side effects on the data within a
stream. They initiate the actual processing and close the 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.

Use cases for peek() include:


1. Debugging: You can use peek() to inspect and log the elements at different stages of your
stream processing pipeline to understand how the data is changing.
2. Logging: It's useful for logging the intermediate results during complex data processing
operations.
3. Monitoring: You can use peek() to observe elements as they pass through the stream to
collect statistics or metrics.
4. State Updates: You can use peek() to update external state or perform side effects based
on the elements in the stream.

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.

What do you mean by saying Stream is lazy?


When we say that a stream is "lazy," it means that stream operations do not immediately
process elements but defer execution until a result is required. This deferred execution
allows for optimised and efficient processing, potential short-circuiting, and improved
performance when working with large datasets.

What is a functional interface in Java 8?


In Java 8 and later versions, a functional interface is an interface that defines exactly one
abstract method. Functional interfaces are also known as Single Abstract Method (SAM)
types. They are a fundamental concept in Java's support for functional programming and the
introduction of lambda expressions.

Key characteristics of functional interfaces in Java 8:

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.

Example of a functional interface:


@FunctionalInterface
interface MyFunction {
int operate(int a, int b);

// This is a default method and doesn't count as an abstract


method.
default void someDefaultMethod() {
// Implementation here.
}
}

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;

int result1 = addition.operate(5, 3); // result1 is 8


int result2 = subtraction.operate(10, 4); // result2 is 6

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.

What is the difference between a normal and functional interface in


Java?
● A normal interface in Java can declare multiple abstract methods and may also have
default and static methods. It is used for various purposes, including defining
contracts for classes and providing multiple method implementations.

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.

What is the difference between the findFirst() and findAny() method?


In Java's Stream API, both findFirst() and findAny() are terminal operations used to retrieve
elements from a stream that meet certain conditions. However, there are some key
differences between the two methods:
1. findFirst():
● findFirst() is a terminal operation that returns the first element found in the stream
that matches the given condition (or the first element in the stream, if no condition is
provided).
● It is guaranteed to return the first element that satisfies the condition, and it will
always return the same result for the same stream, assuming no modifications to the
stream.
● findFirst() is typically used when you need a deterministic result or when order
matters. For example, if you have a sorted stream and want the first element,
findFirst() is the appropriate choice.

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.

What is a Predicate interface?


The Predicate interface is a functional interface introduced in Java as part of the Java 8
release. A functional interface is an interface with a single abstract method, and Predicate is
one of the many functional interfaces included in the java.util.function package. It is designed
for representing a condition or a test that can be evaluated on an input and return a boolean
result.
The Predicate interface in Java has a single abstract method, which is:
boolean test(T t);

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;

boolean result = isEven.test(6); // Returns true


boolean result2 = isEven.test(7); // Returns false

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.

The java.util.function package provides a variety of other functional interfaces, including


Consumer, Supplier, Function, and more, each tailored for different use cases in functional
programming and working with lambdas.

What are Supplier and Consumer Functional interfaces?


In Java, the Supplier and Consumer functional interfaces are part of the java.util.function
package and are used to represent functional operations for supplying or consuming data,
respectively.
1. Supplier:
* The Supplier functional interface represents an operation that supplies a result,
typically without taking any input.
* It defines a single abstract method: T get(), which takes no arguments and returns a
result of type T.
* Suppliers are often used to lazily compute or generate values, making them useful
for scenarios where you need to produce data on demand.
Example:

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.

Can you convert an array to Stream? How?


Yes, you can convert an array to a Stream in Java. To do so, you can use the
Arrays.stream() method or use the stream created from the array through the Arrays.asList()
method. Here's how you can do it:

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);

// You can now perform Stream operations on the array elements.


streamFromArray.forEach(System.out::println);

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();

// Perform Stream operations on the list elements.


streamFromList.forEach(System.out::println);

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);

// Get a parallel stream from the List


Stream<Integer> parallelStream = numbers.parallelStream();

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);

// Get a parallel stream and double each value in parallel


List<Integer> doubledNumbers = numbers.parallelStream()
.map(n -> n * 2)
.collect(Collectors.toList());

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

You might also like