In Java, we often need to store a group of elements. Arrays can be used for this, but they have fixed size, and managing them can be tricky when data keeps changing.
That’s where Collections Framework comes in. And one of the most popular and frequently used classes from this framework is ArrayList.
ArrayList is one of the most commonly used classes in the Java Collection Framework. It provides a dynamic array that grows as needed, making it a powerful tool for developers building scalable Java applications.
This tutorial is designed like a classroom lecture, starting from the basics and gradually diving into all aspects of ArrayList with examples and detailed explanations.
What is ArrayList in Java?
ArrayList is a resizable array implementation of the List interface. Unlike arrays in Java, which are of fixed size, an ArrayList can grow and shrink at runtime as elements are added or removed.
import java.util.ArrayList;
public class Demo {
public static void main(String[] args) {
ArrayList<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Mango");
System.out.println(fruits); // Output: [Apple, Mango]
}
}
Key Features of ArrayList
- Dynamic size: Grows automatically when needed.
- Ordered: Maintains the insertion order.
- Allows duplicates: You can add the same element multiple times.
- Indexed: Supports random access using index (like an array).
- Non-synchronized: Not thread-safe by default.
When to use ArrayList?
Use ArrayList when:
- You need a resizable array.
- You want fast random access to elements using an index.
- You are working in a single-threaded environment or using external synchronization for threads.
Internal Implementation of ArrayList
➤ Backed by Array
Internally, ArrayList uses a dynamic array (i.e., Object[] array). When you create an ArrayList, it initializes an empty array with a default capacity (usually 10).
transient Object[] elementData; // array buffer
➤ Capacity vs Size
- Size: Number of elements currently in the list.
- Capacity: Length of the internal array.
➤ Growth Strategy
When the internal array becomes full, the ArrayList increases its capacity by 1.5 times the current size:
newCapacity = oldCapacity + (oldCapacity >> 1);
This strategy balances performance and memory overhead.
How ArrayList Operations Works Internally
Add Operation
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
- Checks capacity
- Resizes array if needed
- Adds the element at
sizeindex - Increments
size
Get Operation
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
- Performs index bounds check
- Returns element at given index
Remove Operation
public E remove(int index) {
rangeCheck(index);
E oldValue = elementData(index);
int numMoved = size - index - 1;
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; // free memory
return oldValue;
}
- Shifts remaining elements left by one
- Reduces size
- Frees memory (prevents memory leak)
Example: Basic Operations
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> animals = new ArrayList<>();
animals.add("Dog");
animals.add("Cat");
animals.add("Horse");
System.out.println("Animals: " + animals); // [Dog, Cat, Horse]
animals.set(1, "Elephant"); // Replace Cat with Elephant
System.out.println("After set: " + animals); // [Dog, Elephant, Horse]
animals.remove("Dog");
System.out.println("After remove: " + animals); // [Elephant, Horse]
System.out.println("Size: " + animals.size()); // 2
System.out.println("Contains Horse? " + animals.contains("Horse")); // true
}
}
Iterating through an ArrayList
There are several ways to loop through an ArrayList:
1. Using for-each loop
for (String animal : animals) {
System.out.println(animal);
}
2. Using for loop with index
for (int i = 0; i < animals.size(); i++) {
System.out.println(animals.get(i));
}
3. Using Iterator
import java.util.Iterator;
Iterator<String> it = animals.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
Generics with ArrayList
Without Generics (Not type-safe)
ArrayList list = new ArrayList();
list.add("Apple");
list.add(10); // No error at compile time
String fruit = (String) list.get(0); // Requires type casting
With Generics (Type-safe)
ArrayList<String> list = new ArrayList<>();
list.add("Apple");
// list.add(10); // ❌ Compile time error
String fruit = list.get(0); // No casting needed
Conversion Between Array and ArrayList
Array to ArrayList
String[] array = {"A", "B", "C"};
ArrayList<String> list = new ArrayList<>(Arrays.asList(array));
ArrayList to Array
ArrayList<String> list = new ArrayList<>();
list.add("A");
list.add("B");
String[] array = list.toArray(new String[0]);
Thread Safety
ArrayList is not synchronized.
To make it thread-safe:
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
Or use alternatives like CopyOnWriteArrayList.
ArrayList Example Program
import java.util.*;
public class Example {
public static void main(String[] args) {
ArrayList<Integer> nums = new ArrayList<>();
nums.add(10);
nums.add(20);
nums.add(30);
for (int num : nums) {
System.out.println(num);
}
}
}
Explanation of above program:
import java.util.*;: This line imports the entirejava.utilpackage, which includes theArrayListclass.ArrayList<Integer> nums = new ArrayList<>();: Creates an emptyArrayListthat storesIntegervalues (not primitiveint).nums.add(10);,nums.add(20);,nums.add(30);: Adds values to the list dynamically.for (int num : nums): Uses an enhanced for-loop to iterate through the list.System.out.println(num);: Prints each number in the list one by one.
Output:
10
20
30
This is the most basic and clear example of how an ArrayList works.
1. Insert at Specific Index
nums.add(1, 15); // Inserts 15 at index 1
Explanation:
add(index, element)inserts the specified element at the given index.- This shifts the existing elements (and their indices) to the right.
- In our case,
15is inserted at index 1 (between 10 and 20).
Before Insertion: [10, 20, 30]
After Insertion: [10, 15, 20, 30]
Note: If the index is greater than size(), Java will throw IndexOutOfBoundsException.
2. Remove Elements by Index
nums.remove(2); // Removes element at index 2
Explanation:
- This removes the element present at index
2of the list. - After removing, all elements after that index shift one place to the left.
Before Removal: [10, 15, 20, 30]
After Removal: [10, 15, 30]
Removing by index is different from removing by object (nums.remove(Integer.valueOf(30));).
3. Sorting an ArrayList
Collections.sort(nums); // Ascending order
Explanation:
- This uses the
Collections.sort()method to sort the elements of the list in ascending order. - It sorts the list in-place, meaning it modifies the original list.
Before Sorting: [30, 10, 20]
After Sorting: [10, 20, 30]
To sort in descending order, use:
nums.sort(Collections.reverseOrder());
Summary of What You Learned in Above Example
| Operation | Method Used | Description |
|---|---|---|
| Add | add(element) | Adds at the end |
| Add at index | add(index, element) | Inserts at specified index |
| Remove | remove(index) | Removes the element at the index |
| Sort | Collections.sort(list) | Sorts list in ascending order |
Time and Space Complexity of some ArrayList operations
| Operation | Time Complexity | Explanation |
|---|---|---|
add() | O(1) amortized | O(1) unless resize is needed |
add(index) | O(n) | May require shifting elements |
remove(index) | O(n) | Shifting needed |
get(index) | O(1) | Direct access |
contains() | O(n) | Needs to scan elements |
size() | O(1) | Direct field access |
Common Pitfalls
- IndexOutOfBoundsException: Accessing invalid index.
- ConcurrentModificationException: When modifying list while iterating using enhanced for-loop.
for (int i : list) {
list.remove(0); // throws exception
}
Fix using Iterator:
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
it.next();
it.remove(); // safe removal
}
Best Practices
- Use
ArrayListwhen frequent access is needed. - For frequent insertions/deletions in middle, prefer
LinkedList. - Initialize with expected size if known: javaCopyEdit
ArrayList<String> list = new ArrayList<>(100);
ArrayList Methods in Java with Examples
| Method | Description | Version |
|---|---|---|
add(E e) | Adds the element to the end of the list. | ✅ Java 1.2+ |
add(int index, E element) | Inserts the element at the specified index. | ✅ Java 1.2+ |
addAll(Collection<? extends E> c) | Adds all elements from the specified collection. | ✅ Java 1.2+ |
addAll(int index, Collection<?> c) | Inserts all elements from the collection at the given index. | ✅ Java 1.2+ |
get(int index) | Returns the element at the specified index. | ✅ Java 1.2+ |
set(int index, E element) | Replaces the element at the specified position. | ✅ Java 1.2+ |
remove(int index) | Removes the element at the specified index. | ✅ Java 1.2+ |
remove(Object o) | Removes the first occurrence of the specified element. | ✅ Java 1.2+ |
clear() | Removes all elements from the list. | ✅ Java 1.2+ |
size() | Returns the number of elements in the list. | ✅ Java 1.2+ |
isEmpty() | Checks if the list is empty. | ✅ Java 1.2+ |
contains(Object o) | Checks if the list contains the specified element. | ✅ Java 1.2+ |
indexOf(Object o) | Returns the index of the first occurrence of the specified element. | ✅ Java 1.2+ |
lastIndexOf(Object o) | Returns the index of the last occurrence. | ✅ Java 1.2+ |
toArray() | Returns an array containing all elements. | ✅ Java 1.2+ |
toArray(T[] a) | Returns an array of the specified type. | ✅ Java 1.2+ |
ensureCapacity(int minCapacity) | Ensures the list has at least the specified capacity. | ✅ Java 1.4+ |
trimToSize() | Trims capacity to current size. | ✅ Java 1.2+ |
clone() | Returns a shallow copy of the list. | ✅ Java 1.2+ |
iterator() | Returns an iterator over the list. | ✅ Java 1.2+ |
listIterator() | Returns a list iterator for forward/backward traversal. | ✅ Java 1.2+ |
subList(int fromIndex, int toIndex) | Returns a view of the portion between given indexes. | ✅ Java 1.2+ |
retainAll(Collection<?> c) | Retains only elements also in the specified collection. | ✅ Java 1.2+ |
removeAll(Collection<?> c) | Removes all elements present in the specified collection. | ✅ Java 1.2+ |
equals(Object o) | Compares the list to another object for equality. | ✅ Java 1.2+ |
hashCode() | Returns the hash code of the list. | ✅ Java 1.2+ |
forEach(Consumer<? super T> action) | Performs the given action for each element. | 🟨 Java 8+ |
sort(Comparator<? super E> c) | Sorts the list using a custom comparator. | 🟨 Java 8+ |
removeIf(Predicate<? super E> filter) | Removes elements that match a given condition. | 🟨 Java 8+ |
replaceAll(UnaryOperator<E> operator) | Replaces each element with the result of applying the operator. | 🟨 Java 8+ |
spliterator() | Creates a Spliterator over the elements in the list (used for parallelism). | 🟨 Java 8+ |
Read the Full Article: ArrayList Methods in Java
🆚 ArrayList vs Array
| Feature | Array | ArrayList |
|---|---|---|
| Size | Fixed | Dynamic |
| Type | Can be primitives | Only objects |
| Performance | Slightly faster | More flexible |
| Methods | No built-in methods | Many utility methods |
Real-World Use Cases
- Maintaining a dynamic list of records (e.g., shopping cart).
- Temporary storage when element order matters.
- Caching and lookups where fast random access is needed.
Frequently Asked Questions (FAQs) on ArrayList in Java
Q1. What is the difference between an Array and an ArrayList in Java?
Answer:
- An
Arrayis a fixed-size data structure that can hold both primitive and object types. - An
ArrayListis a dynamic array that can grow or shrink in size and only holds object references. - Arrays use less memory and are slightly faster, but ArrayLists provide more flexibility and built-in methods.
Q2. Is ArrayList synchronized in Java?
Answer:
No, ArrayList is not synchronized by default. It is not thread-safe for concurrent access. To make it synchronized:
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
For thread-safe operations in multi-threaded environments, you can also use CopyOnWriteArrayList.
Q3. What is the initial capacity of an ArrayList?
Answer:
The default capacity of an ArrayList when created with the default constructor is 10. You can specify a custom initial capacity using:
ArrayList<Integer> list = new ArrayList<>(50);
Q4. How does ArrayList grow internally?
Answer:
When the ArrayList exceeds its current capacity, it resizes its internal array by increasing the size by 1.5 times (i.e., newCapacity = oldCapacity + oldCapacity / 2).
Q5. Can we store null in an ArrayList?
Answer:
Yes, ArrayList can store null values. You can add multiple null entries as it allows duplicates.
ArrayList<String> list = new ArrayList<>();
list.add(null);
list.add(null);
System.out.println(list); // [null, null]
Q6. How to sort an ArrayList?
Answer:
You can use Collections.sort() for ascending order:
Collections.sort(list);
Or use a custom comparator for descending:
list.sort(Collections.reverseOrder());
Q7. What is the time complexity of ArrayList operations?
Answer:
| Operation | Time Complexity |
|---|---|
| Add (end) | O(1) (amortized) |
| Add (middle) | O(n) |
| Remove by index | O(n) |
| Get by index | O(1) |
| Search (contains) | O(n) |
Q8. How do you remove an element while iterating over an ArrayList?
Answer:
Use an Iterator to safely remove elements during iteration:
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
if (it.next() < 10) {
it.remove();
}
}
Avoid using for-each loop for removal, as it may throw ConcurrentModificationException.
Q9. Can we make ArrayList read-only?
Answer:
Yes, by using:
List<String> readOnlyList = Collections.unmodifiableList(list);
Any attempt to modify readOnlyList will throw UnsupportedOperationException.