You can iterate over collections using Iterator or enhanced for
loop.
Using Iterator:
1 | Iterator<String> iterator = fruits.iterator(); |
Using Enhanced for-loop:
1 | for (String fruit : fruits) { |
The Collections class provides several utility methods to work with collections, such as:
A List is an ordered collection (or sequence) that allows duplicate elements. Elements in a List are indexed, meaning each element is positioned at a specific index and can be accessed via its index.
List
:Implementations of the List Interface:
get()
and set()
operations).1 | List<String> fruits = new ArrayList<>(); |
1 | List<String> fruits = new LinkedList<>(); |
Feature | ArrayList | LinkedList |
---|---|---|
Memory structure | Stored in a contiguous block of memory (like an array). | Stored as nodes in heap memory, each holding data + pointers. |
Resizing | Automatically resizes when full by copying elements to a new, 1.5x larger array. | No resizing — just adds/removes nodes, updating pointers. |
Memory overhead | Less overhead (stores only data). | More overhead (stores data + pointers for next/prev nodes). |
Cache performance | Cache-friendly — elements are stored sequentially, helping CPU fetch data faster. | Cache-unfriendly — nodes are scattered in memory, causing pointer chasing. |
Insertion/Deletion | Slower — needs shifting elements when adding/removing (except at the end). | Faster — adding/removing nodes is quick (only pointer updates). |
Access time | Faster (O(1)) — can directly access any index. | Slower (O(n)) — must traverse nodes from the head/tail to find an element. |
Best use case | Frequent access to elements by index. | Frequent additions/removals (especially at the start/middle). |
push()
, pop()
, and peek()
for stack operations.1 | Stack<String> stack = new Stack<>(); |
In Java, a Queue is a collection used to store elements in a particular order, typically following the FIFO (First-In-First-Out) principle. This means the first element added to the queue will be the first one to be removed.
Queue
:add(E e)
: Adds the specified element to the queue. If the element cannot be added (due to capacity restrictions), it throws an IllegalStateException
.offer(E e)
: Adds the specified element to the queue. Unlike add()
, it does not throw an exception if the queue is full; it simply returns false
if the element cannot be added.remove()
: Removes and returns the head of the queue. Throws NoSuchElementException
if the queue is empty.poll()
: Removes and returns the head of the queue. Returns null
if the queue is empty.element()
: Returns the head of the queue without removing it. Throws NoSuchElementException
if the queue is empty.peek()
: Returns the head of the queue without removing it. Returns null
if the queue is empty.LinkedList
:
LinkedList
implements the Queue
interface and can be used as a basic queue. It is efficient for adding/removing elements from both ends.1 | Queue<String> queue = new LinkedList<>(); |
PriorityQueue
:
PriorityQueue
orders its elements based on their natural ordering or a specified comparator. It does not follow FIFO strictly; instead, it processes elements based on their priority.1 | Queue<Integer> queue = new PriorityQueue<>(); |
In Java, a Set is a collection that does not allow duplicate elements and does not guarantee any specific order of elements. It models the mathematical set abstraction, where each element in the collection must be unique.
add(E e)
: Adds the specified element to the set. If the element already exists, the set remains unchanged, and it returns false
.
remove(Object o)
: Removes the specified element from the set. If the element exists, it is removed, and the method returns true
. Otherwise, it returns false
.
contains(Object o)
: Returns true
if the set contains the specified element, otherwise false
.
size()
: Returns the number of elements in the set.
isEmpty()
: Returns true
if the set contains no elements, otherwise false
.
clear()
: Removes all elements from the set.
iterator()
: Returns an iterator over the elements in the set. The iteration order is typically not predictable (unless using LinkedHashSet
or TreeSet
).
1 | Iterator<String> iterator = set.iterator(); |
HashSet:
Set
interface and does not maintain any order of its elements.1 | Set<String> set = new HashSet<>(); |
LinkedHashSet:
Set
interface and maintains the order in which elements were inserted.1 | Set<String> set = new LinkedHashSet<>(); |
TreeSet:
Set
interface and stores elements in a sorted order according to their natural ordering or a specified comparator.1 | Set<Integer> set = new TreeSet<>(); |
A Map
in Java is a collection that stores key-value pairs — kind of like a dictionary! It’s part of the java.util
package but does not extend Collection
. Let’s break it down into key points:
Stores key-value pairs: Each key is unique, and each key maps to one value.
No duplicate keys: If you insert a new entry with an existing key, it overwrites the old value.
Allows null values
(depending on the implementation).
HashMap
allows 1 null key and multiple null values.TreeMap
and Hashtable
don’t allow null keys.Here are the most used Map
implementations and their differences:
Implementation | Ordering | Null Keys/Values | Thread-Safe | Performance |
---|---|---|---|---|
HashMap | ❌ Unordered | ✅ 1 null key, multiple null values | ❌ No | ⚡ Fast (O(1) get/put) |
LinkedHashMap | ✅ Insertion order | ✅ Same as HashMap |
❌ No | ⚡ Slightly slower than HashMap |
TreeMap | ✅ Sorted (natural/comparator) | ❌ No null keys, ✅ null values | ❌ No | 🐢 Slower (O(log n)) due to sorting |
Hashtable | ❌ Unordered | ❌ No null keys/values | ✅ Yes | 🐌 Slower (legacy) |
ConcurrentHashMap | ❌ Unordered | ❌ No null keys/values | ✅ Yes (better than Hashtable) | ⚡ Good for concurrency |
Method | Description |
---|---|
put(key, value) |
Inserts or updates a key-value pair |
get(key) |
Returns the value for the given key |
remove(key) |
Removes the key-value pair |
containsKey(key) |
Checks if the map has the given key |
containsValue(value) |
Checks if the map has the given value |
keySet() |
Returns a Set of all keys |
values() |
Returns a Collection of all values |
entrySet() |
Returns a Set of Map.Entry (key-value pairs) |
putIfAbsent(key, value) |
Inserts only if the key doesn’t exist |
replace(key, value) |
Replaces the value for an existing key |
Here are 3 popular ways to loop through a map:
Using for-each
and entrySet()
1 | for (Map.Entry<String, Integer> entry : map.entrySet()) { |
Using keySet()
(keys only)
1 | for (String key : map.keySet()) { |
Using forEach()
(Java 8 Lambda)
1 | map.forEach((key, value) -> System.out.println(key + " : " + value)); |
Immutable Maps (Java 9+)
1 | Map<String, String> map = Map.of("key1", "value1", "key2", "value2"); |
Sorted Maps:
TreeMap
keeps keys sorted by natural order or a custom Comparator
.
Thread-safe Maps:
Hashtable
(legacy, slow).ConcurrentHashMap
(modern, faster alternative).HashCode()
hashCode()
is a method in Java that returns an integer value that is used to identify objects in hash-based collections (like HashMap
, HashSet
, Hashtable
).hashCode()
is to efficiently map objects to hash buckets for fast lookups.Why Override hashCode()
equals()
method, you must override hashCode()
as well. This ensures that objects that are considered equal by equals()
will return the same hash code.The General Contract of hashCode()
a.equals(b)
is true
, then a.hashCode()
must be equal to b.hashCode()
.hashCode()
value should remain consistent throughout the lifetime of the object, unless a property used in equals()
and hashCode()
is modified.Array of Buckets: A HashMap
is implemented using an array of buckets (typically an array of Node
or Entry
objects). Each bucket is a linked list (or a tree if collisions become too high) that stores key-value pairs.
When a key is added, the HashMap
computes a hash code for the key using the key’s hashCode()
method.
The hash code is then transformed into an index for the bucket array using a hash function (e.g., index = hashCode % array.length
).
Collision Handling:
HashMap
uses chaining, where each bucket stores a linked list of key-value pairs. Each node in the list contains the key, value, and a reference to the next node in the same bucket.TreeNode
) for better performance (O(log n) instead of O(n) in case of long chains).When the number of elements exceeds a certain threshold (based on the load factor, typically 0.75), the HashMap
doubles the array size and rehashes the existing entries to new bucket locations.
In Java, both Comparable
and Comparator
are functional interfaces used for comparing objects, but they serve different purposes and are used in different contexts.
Comparable
InterfacePurpose: Used to define the natural ordering of objects. It allows a class to specify how its instances should be compared.
Location: It is implemented by the class whose instances need to be compared.
Method: It has a single method:
1 | int compareTo(T o); |
this
) with another object (o
).this
is less than o
.this
is equal to o
.this
is greater than o
.Example:
1 | class Student implements Comparable<Student> { |
When to Use: Use Comparable
when the class has a natural order (e.g., sorting by age, name, or any primary field).
Comparator
InterfacePurpose: Used to define custom ordering of objects, separate from the natural order defined by Comparable
. A Comparator
is useful when you want to compare objects based on multiple fields or in different ways.
Location: A separate class implements the Comparator
interface, not the object being compared.
Method: It has two methods:
int compare(T o1, T o2);
boolean equals(Object obj);
(optional for Comparator
interface, not always used)The compare
method compares two objects and returns:
o1
is less than o2
.o1
is equal to o2
.o1
is greater than o2
.Example:
1 | class Student { |
When to Use: Use Comparator
when you need to define multiple ways to compare objects or if you want to compare objects based on fields that don’t represent the natural ordering.
Feature | Comparable |
Comparator |
---|---|---|
Implemented by | The class whose instances are being compared. | A separate class or anonymous class. |
Purpose | Defines the natural ordering of objects. | Defines custom orderings of objects. |
Method | compareTo(T o) |
compare(T o1, T o2) |
When to Use | When you want to define a default ordering (natural order). | When you need multiple or different ways to compare objects. |
A functional interface in Java is an interface that has exactly one abstract method, but it can have multiple default or static methods. Functional interfaces are used primarily in lambda expressions and method references.
The @FunctionalInterface
annotation is optional, but it helps to clarify that the interface is intended to be functional. The compiler will also check that the interface conforms to the rules of a functional interface.
Predicate<T>
boolean test(T t)
: Evaluates the predicate on the given argument.1 | javaCopy codeimport java.util.function.Predicate; |
Function<T, R>
T
and returns a result of type R
. It is useful for transformations and mappings.R apply(T t)
: Applies the function to the given argument.default <V> Function<T, V> andThen(Function<? super R, ? extends V> after)
: Composes the function to apply after the current function.default <V> Function<V, R> compose(Function<? super V, ? extends T> before)
: Composes the function to apply before the current function.1 | javaCopy codeimport java.util.function.Function; |
Consumer<T>
void accept(T t)
: Performs the operation on the given argument.default Consumer<T> andThen(Consumer<? super T> after)
: Combines two Consumer
operations where the second is executed after the first one.1 | javaCopy codeimport java.util.function.Consumer; |
Supplier<T>
T
. It is often used to generate or supply values.T get()
: Returns a result.1 | javaCopy codeimport java.util.function.Supplier; |