Java Collection : ArrayList

Exploring Java ArrayList: A Dynamic Array Implementation

Introduction:
Java, being one of the most popular programming languages, provides a rich collection of data structures to handle complex tasks efficiently. Among these, the ArrayList stands out as a versatile and widely used data structure for storing and manipulating collections of elements.

In this blog, we will dive deep into Java ArrayList, understanding its key features, benefits, and real-world applications.

What is Java ArrayList?
Java ArrayList is a class that extends the AbstractList and implements the List interface. It is part of the Java Collections Framework and provides a dynamic array implementation. Unlike traditional arrays in Java, which have a fixed size, ArrayLists automatically resize themselves when elements are added or removed, making them a convenient choice for working with varying data sets.

Key Features of Java ArrayList:

  1. Dynamic Resizing: The ArrayList dynamically resizes itself as elements are added or removed. It internally manages the array size, eliminating the need for manual resizing and providing a more flexible approach to storing elements.
  2. Access Efficiency: ArrayLists offer constant-time (O(1)) access to elements based on their index. Retrieving elements from the list
    is fast and efficient.
  3. Easy Insertion and Deletion: Adding or removing elements from ArrayLists is straightforward, and the ArrayList class takes care of managing the underlying array’s size.
  4. Supports Generics: ArrayLists in Java support the use of generics, enabling type-safety and allowing developers to work with specific data types.
  5. Iterable Interface: ArrayLists are iterable, meaning they can be used with enhanced for loops (for-each) to easily traverse and process elements.
  6. Backed by an Array: Despite its dynamic resizing capabilities, an ArrayList is essentially backed by an array data structure, making it memory-efficient and lightweight.

Benefits of Java ArrayList:

  1. Flexible Storage: The ability to resize dynamically allows developers to create ArrayLists without worrying about the initial size or how many elements will be added. This flexibility makes ArrayLists suitable for scenarios where the number of elements is uncertain.
  2. Simplified Manipulation: With built-in methods for adding, removing, and updating elements, ArrayLists simplify the manipulation of data collections, saving development time and effort.
  3. Enhanced Performance: Java ArrayLists offer constant-time access to elements, resulting in efficient and speedy retrieval of data, which is crucial for large-scale applications.
  4. Type-Safety: Generics support in ArrayLists ensures that data types are checked at compile-time, reducing the risk of runtime errors and enhancing the overall code quality.

Real-world Applications of Java ArrayList:

  1. Data Storage and Retrieval: ArrayLists are widely used for storing data in memory, such as user records, product details, or sensor readings. Their quick access time makes them suitable for applications requiring frequent data retrieval.
  2. Implementing Stacks and Queues: ArrayLists can serve as the underlying data structure for implementing stack and queue data structures due to their ease of manipulation and constant-time access.
  3. Caching Mechanisms: In caching systems, ArrayLists can be employed to store frequently accessed data, optimizing response times and reducing the need for expensive computations.
  4. Sorting and Filtering: ArrayLists can be utilized to sort and filter data collections based on various criteria, making them valuable in search and data processing tasks.

Java ArrayList is a fundamental and powerful data structure that provides dynamic resizing, efficient access, and easy manipulation of data collections. Its flexibility and ease of use make it an indispensable tool for a wide range of Java applications. Whether you are working on a simple program or a large-scale enterprise system, understanding and utilizing Java ArrayLists effectively can significantly improve the performance and maintainability of your code

By leveraging the strengths of Java ArrayLists, developers can create more robust and efficient applications that handle dynamic data with ease. So, the next time you need to work with a dynamic collection of elements in Java, consider using ArrayLists to streamline your development process and optimize your application’s performance.

Here are some code examples demonstrating the usage of Java ArrayList in different scenarios mentioned above:

1. Data Storage and Retrieval:

import java.util.ArrayList;

public class DataStorageExample {
    public static void main(String[] args) {
        // Creating an ArrayList to store user records
        ArrayList<String> userRecords = new ArrayList<>();

        // Adding user records to the ArrayList
        userRecords.add("John Doe");
        userRecords.add("Jane Smith");
        userRecords.add("Michael Johnson");

        // Retrieving and displaying user records
        for (String user : userRecords) {
            System.out.println(user);
        }
    }
}

2. Implementing Stacks and Queues:

import java.util.ArrayList;

public class StackAndQueueExample {
    public static void main(String[] args) {
        // Creating an ArrayList to implement a stack
        ArrayList<Integer> stack = new ArrayList<>();

        // Pushing elements onto the stack
        stack.add(10);
        stack.add(20);
        stack.add(30);

        // Popping elements from the stack (LIFO order)
        int poppedElement = stack.remove(stack.size() - 1);
        System.out.println("Popped element: " + poppedElement);

        // Creating an ArrayList to implement a queue
        ArrayList<String> queue = new ArrayList<>();

        // Enqueue elements into the queue
        queue.add("A");
        queue.add("B");
        queue.add("C");

        // Dequeue elements from the queue (FIFO order)
        String dequeuedElement = queue.remove(0);
        System.out.println("Dequeued element: " + dequeuedElement);
    }
}

3. Caching Mechanisms:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class CachingExample {
    // Simulating a cache using an ArrayList and a Map
    private static ArrayList<String> cacheKeys = new ArrayList<>();
    private static Map<String, String> cacheData = new HashMap<>();
    private static final int CACHE_SIZE = 3;

    public static void addToCache(String key, String value) {
        if (cacheKeys.size() >= CACHE_SIZE) {
            // Remove the oldest entry if the cache is full
            String oldestKey = cacheKeys.remove(0);
            cacheData.remove(oldestKey);
        }

        cacheKeys.add(key);
        cacheData.put(key, value);
    }

    public static String getFromCache(String key) {
        return cacheData.get(key);
    }

    public static void main(String[] args) {
        addToCache("user_1", "John Doe");
        addToCache("user_2", "Jane Smith");
        addToCache("user_3", "Michael Johnson");

        String cachedUser = getFromCache("user_2");
        System.out.println("Cached user: " + cachedUser);
    }
}

4. Sorting and Filtering:

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

public class SortingAndFilteringExample {
    public static void main(String[] args) {
        ArrayList<Integer> numbers = new ArrayList<>();

        numbers.add(5);
        numbers.add(3);
        numbers.add(8);
        numbers.add(1);
        numbers.add(6);

        // Sorting the ArrayList in ascending order
        Collections.sort(numbers);
        System.out.println("Sorted numbers: " + numbers);

        // Filtering even numbers from the list
        ArrayList<Integer> evenNumbers = new ArrayList<>();
        for (int number : numbers) {
            if (number % 2 == 0) {
                evenNumbers.add(number);
            }
        }
        System.out.println("Even numbers: " + evenNumbers);
    }
}

These examples showcase some of the practical uses of Java ArrayList in different contexts. The versatility and ease of manipulation offered by ArrayList make it a valuable tool for various scenarios in Java programming.

Here are the same examples, but using Java 8 syntax, such as lambda expressions and stream operations:

1. Data Storage and Retrieval:

import java.util.ArrayList;

public class DataStorageExample {
    public static void main(String[] args) {
        // Creating an ArrayList to store user records
        ArrayList<String> userRecords = new ArrayList<>();

        // Adding user records to the ArrayList
        userRecords.add("John Doe");
        userRecords.add("Jane Smith");
        userRecords.add("Michael Johnson");

        // Retrieving and displaying user records using forEach and lambda expression
        userRecords.forEach(user -> System.out.println(user));
    }
}

2. Implementing Stacks and Queues:

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;

public class StackAndQueueExample {
    public static void main(String[] args) {
        // Creating a Stack using ArrayList
        Stack<Integer> stack = new Stack<>();

        // Pushing elements onto the stack
        stack.push(10);
        stack.push(20);
        stack.push(30);

        // Popping elements from the stack (LIFO order)
        int poppedElement = stack.pop();
        System.out.println("Popped element: " + poppedElement);

        // Creating a Queue using LinkedList (ArrayList can also be used)
        Queue<String> queue = new LinkedList<>();

        // Enqueue elements into the queue
        queue.offer("A");
        queue.offer("B");
        queue.offer("C");

        // Dequeue elements from the queue (FIFO order)
        String dequeuedElement = queue.poll();
        System.out.println("Dequeued element: " + dequeuedElement);
    }
}

3. Caching Mechanisms:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class CachingExample {
    // Simulating a cache using an ArrayList and a Map
    private static ArrayList<String> cacheKeys = new ArrayList<>();
    private static Map<String, String> cacheData = new HashMap<>();
    private static final int CACHE_SIZE = 3;

    public static void addToCache(String key, String value) {
        if (cacheKeys.size() >= CACHE_SIZE) {
            // Remove the oldest entry if the cache is full
            String oldestKey = cacheKeys.remove(0);
            cacheData.remove(oldestKey);
        }

        cacheKeys.add(key);
        cacheData.put(key, value);
    }

    public static String getFromCache(String key) {
        return cacheData.get(key);
    }

    public static void main(String[] args) {
        addToCache("user_1", "John Doe");
        addToCache("user_2", "Jane Smith");
        addToCache("user_3", "Michael Johnson");

        // Retrieving cached user using getOrDefault method with lambda expression
        String cachedUser = cacheData.getOrDefault("user_2", "User not found");
        System.out.println("Cached user: " + cachedUser);
    }
}

4. Sorting and Filtering:

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class SortingAndFilteringExample {
    public static void main(String[] args) {
        ArrayList<Integer> numbers = new ArrayList<>();

        numbers.add(5);
        numbers.add(3);
        numbers.add(8);
        numbers.add(1);
        numbers.add(6);

        // Sorting the ArrayList in ascending order using the sorted method
        List<Integer> sortedNumbers = numbers.stream()
                                            .sorted()
                                            .collect(Collectors.toList());
        System.out.println("Sorted numbers: " + sortedNumbers);

        // Filtering even numbers from the list using the filter method
        List<Integer> evenNumbers = numbers.stream()
                                           .filter(number -> number % 2 == 0)
                                           .collect(Collectors.toList());
        System.out.println("Even numbers: " + evenNumbers);
    }
}

Above examples use Java 8 features to make the code more concise and expressive. The use of lambda expressions and stream operations 
in Java 8 simplifies working with collections like ArrayList, making the code more elegant and efficient.


Below is a basic implementation of an ArrayList from scratch in Java. Please note that this implementation is simplified and may lack 
some optimizations found in the built-in Java ArrayList for performance reasons.

public class MyArrayList<E> {
    private static final int DEFAULT_CAPACITY = 10;
    private Object[] elements;
    private int size;

    public MyArrayList() {
        elements = new Object[DEFAULT_CAPACITY];
        size = 0;
    }

    public MyArrayList(int initialCapacity) {
        if (initialCapacity < 0) {
            throw new IllegalArgumentException("Initial capacity cannot be negative: " + initialCapacity);
        }
        elements = new Object[initialCapacity];
        size = 0;
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public void add(E element) {
        ensureCapacity();
        elements[size++] = element;
    }

    @SuppressWarnings("unchecked")
    public E get(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }
        return (E) elements[index];
    }

    public void remove(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }
        System.arraycopy(elements, index + 1, elements, index, size - index - 1);
        elements[--size] = null; // Avoids memory leak
    }

    private void ensureCapacity() {
        if (size == elements.length) {
            int newCapacity = elements.length * 2;
            Object[] newElements = new Object[newCapacity];
            System.arraycopy(elements, 0, newElements, 0, size);
            elements = newElements;
        }
    }

    public static void main(String[] args) {
        MyArrayList<Integer> myList = new MyArrayList<>();

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

        System.out.println("Size: " + myList.size());
        System.out.println("Element at index 1: " + myList.get(1));

        myList.remove(1);
        System.out.println("Size after removal: " + myList.size());
    }
}

In this implementation, we use a dynamic array (Object[]) to store the elements. The add() method appends an element to the end of the array, while the get() method retrieves an element based on its index. The remove() method removes an element from the list at the specified index, and the array is adjusted accordingly.

The ensureCapacity() method is called internally when the ArrayList reaches its current capacity, doubling its size to accommodate more elements efficiently.

Please remember that this is a basic implementation meant for educational purposes and does not include all the features and optimizations of the built-in Java ArrayList, such as concurrency handling, iterator support, or efficient resizing strategies.
For production use, it is recommended to use the built-in ArrayList provided by the Java Collections Framework.

Adding iterator support to the custom ArrayList implementation involves creating an inner class that implements the Iterator interface. This allows us to iterate through the elements of the ArrayList using the enhanced for loop (for-each) or explicitly with the iterator’s methods (e.g., hasNext(), next()).

Here’s the updated code with iterator support:

import java.util.Iterator;

public class MyArrayList<E> implements Iterable<E> {
    // ... (existing above code)

    @Override
    public Iterator<E> iterator() {
        return new MyArrayListIterator();
    }

    private class MyArrayListIterator implements Iterator<E> {
        private int currentIndex = 0;

        @Override
        public boolean hasNext() {
            return currentIndex < size;
        }

        @SuppressWarnings("unchecked")
        @Override
        public E next() {
            if (!hasNext()) {
                throw new java.util.NoSuchElementException();
            }
            return (E) elements[currentIndex++];
        }

        // Optional: Implement remove() method if needed
        @Override
        public void remove() {
            throw new UnsupportedOperationException("remove() method is not supported.");
        }
    }

    public static void main(String[] args) {
        MyArrayList<Integer> myList = new MyArrayList<>();

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

        // Using the iterator with the enhanced for loop (for-each)
        for (int element : myList) {
            System.out.println(element);
        }

        // Using the iterator explicitly
        Iterator<Integer> iterator = myList.iterator();
        while (iterator.hasNext()) {
            int element = iterator.next();
            System.out.println(element);
        }
    }
}

With the added iterator support, you can now use the enhanced for loop (for-each) or the iterator explicitly to traverse the elements 
of the custom ArrayList, similar to how you would do it with the built-in Java ArrayList. The iterator also allows you to remove elements, 
but the `remove()` method in the iterator implementation is not supported and throws an `UnsupportedOperationException`. 


If you want to support element removal, you can implement the `remove()` method in the iterator class as needed.

To support element removal in the iterator class, we need to implement the `remove()` method and ensure that it correctly removes the 
element from the ArrayList. Here's the updated iterator class with support for element removal:

import java.util.Iterator;

public class MyArrayList<E> implements Iterable<E> {
    // ... (existing code as above)

    @Override
    public Iterator<E> iterator() {
        return new MyArrayListIterator();
    }

    private class MyArrayListIterator implements Iterator<E> {
        private int currentIndex = 0;
        private boolean canRemove = false;

        @Override
        public boolean hasNext() {
            return currentIndex < size;
        }

        @SuppressWarnings("unchecked")
        @Override
        public E next() {
            if (!hasNext()) {
                throw new java.util.NoSuchElementException();
            }
            canRemove = true; // The next() call allows removal of the current element
            return (E) elements[currentIndex++];
        }

        @Override
        public void remove() {
            if (!canRemove) {
                throw new IllegalStateException("next() must be called before remove()");
            }

            // Remove the last element returned by next() (currentIndex - 1)
            MyArrayList.this.remove(currentIndex - 1);
            currentIndex--;
            canRemove = false;
        }
    }

    public static void main(String[] args) {
        MyArrayList<Integer> myList = new MyArrayList<>();

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

        Iterator<Integer> iterator = myList.iterator();
        while (iterator.hasNext()) {
            int element = iterator.next();
            System.out.println(element);

            if (element == 2) {
                iterator.remove(); // Remove the element 2
            }
        }

        System.out.println("Size after removal: " + myList.size());
    }
}

With the updated iterator class, you can now call the remove() method on the iterator to remove elements while iterating through the ArrayList. The remove() method removes the last element returned by the next() method and adjusts the iterator’s state accordingly.

In the example above, the element with the value 2 is removed from the ArrayList during iteration. After the removal, the size of the ArrayList is printed to show that the element has been successfully removed.

Here is the complete code of arraylist up to iterator implementation.



import java.util.Iterator;

public class MyArrayList<E> implements Iterable<E> {
    private static final int DEFAULT_CAPACITY = 10;
    private Object[] elements;
    private int size;

    public MyArrayList() {
        elements = new Object[DEFAULT_CAPACITY];
        size = 0;
    }

    public MyArrayList(int initialCapacity) {
        if (initialCapacity < 0) {
            throw new IllegalArgumentException("Initial capacity cannot be negative: " + initialCapacity);
        }
        elements = new Object[initialCapacity];
        size = 0;
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public void add(E element) {
        ensureCapacity();
        elements[size++] = element;
    }

    @SuppressWarnings("unchecked")
    public E get(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }
        return (E) elements[index];
    }

    public void remove(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }
        System.arraycopy(elements, index + 1, elements, index, size - index - 1);
        elements[--size] = null; // Avoids memory leak
    }

    private void ensureCapacity() {
        if (size == elements.length) {
            int newCapacity = elements.length * 2;
            Object[] newElements = new Object[newCapacity];
            System.arraycopy(elements, 0, newElements, 0, size);
            elements = newElements;
        }        
        
    }
    
    
    @Override
    public Iterator<E> iterator() {
        return new MyArrayListIterator();
    }

    private class MyArrayListIterator implements Iterator<E> {
        private int currentIndex = 0;
        private boolean canRemove = false;

        @Override
        public boolean hasNext() {
            return currentIndex < size;
        }

        @SuppressWarnings("unchecked")
        @Override
        public E next() {
            if (!hasNext()) {
                throw new java.util.NoSuchElementException();
            }
            canRemove = true; // The next() call allows removal of the current element
            return (E) elements[currentIndex++];
        }

        // Optional: Implement remove() method if needed
        @Override
        public void remove() {
            if (!canRemove) {
                throw new IllegalStateException("next() must be called before remove()");
            }

            // Remove the last element returned by next() (currentIndex - 1)
            MyArrayList.this.remove(currentIndex - 1);
            currentIndex--;
            canRemove = false;
        }
    }

    public static void main(String[] args) {
    	MyArrayList<Integer> myList = new MyArrayList<>();

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

        Iterator<Integer> iterator = myList.iterator();
        while (iterator.hasNext()) {
            int element = iterator.next();
            System.out.println(element);

            if (element == 2) {
                iterator.remove(); // Remove the element 2
            }
        }

        System.out.println("Size after removal: " + myList.size());
    }
}

Adding support for concurrency handling in the custom ArrayList implementation can be achieved by using proper synchronization. In this case, we can use the synchronized keyword to ensure that concurrent access to the ArrayList is properly controlled.
Below is the updated code with concurrency handling:

import java.util.Iterator;

public class MyArrayList<E> implements Iterable<E> {
    private static final int DEFAULT_CAPACITY = 10;
    private Object[] elements;
    private int size;

    public MyArrayList() {
        elements = new Object[DEFAULT_CAPACITY];
        size = 0;
    }

    // ... (existing code)

    public synchronized void add(E element) {
        ensureCapacity();
        elements[size++] = element;
    }

    @SuppressWarnings("unchecked")
    public synchronized E get(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }
        return (E) elements[index];
    }

    public synchronized void remove(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }
        System.arraycopy(elements, index + 1, elements, index, size - index - 1);
        elements[--size] = null; // Avoids memory leak
    }

    // ... (existing code)

    @Override
    public synchronized Iterator<E> iterator() {
        return new MyArrayListIterator();
    }

    private class MyArrayListIterator implements Iterator<E> {
        // ... (existing code)
        
        // Optional: Implement remove() method with synchronization
        @Override
        public synchronized void remove() {
            if (!canRemove) {
                throw new IllegalStateException("next() must be called before remove()");
            }

            // Remove the last element returned by next() (currentIndex - 1)
            MyArrayList.this.remove(currentIndex - 1);
            currentIndex--;
            canRemove = false;
        }
    }

    // ... (existing code)

    public static void main(String[] args) {
        MyArrayList<Integer> myList = new MyArrayList<>();

        // ... (existing code)

        // Using the iterator explicitly with synchronization
        synchronized (myList) {
            Iterator<Integer> iterator = myList.iterator();
            while (iterator.hasNext()) {
                int element = iterator.next();
                System.out.println(element);

                if (element == 2) {
                    iterator.remove(); // Remove the element 2
                }
            }
        }

        System.out.println("Size after removal: " + myList.size());
    }
}

In this updated code, the `add()`, `get()`, and `remove()` methods of the custom ArrayList class are synchronized using the `synchronized` keyword.

This ensures that only one thread can execute these methods at a time, preventing potential issues with concurrent access. Additionally, the `remove()` method in the `MyArrayListIterator` class is also synchronized, ensuring that only one thread can remove elements from the ArrayList while iterating over it.

When using the iterator explicitly in the `main` method, we also synchronize on the ArrayList instance to ensure that the iteration and element removal are done in a thread-safe manner.

Here is the complete code with concurrency handling.

package com.blog;

import java.util.Iterator;

public class MyArrayList<E> implements Iterable<E> {
    private static final int DEFAULT_CAPACITY = 10;
    private Object[] elements;
    private int size;

    public MyArrayList() {
        elements = new Object[DEFAULT_CAPACITY];
        size = 0;
    }

    public MyArrayList(int initialCapacity) {
        if (initialCapacity < 0) {
            throw new IllegalArgumentException("Initial capacity cannot be negative: " + initialCapacity);
        }
        elements = new Object[initialCapacity];
        size = 0;
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public synchronized void add(E element) {
        ensureCapacity();
        elements[size++] = element;
    }

    @SuppressWarnings("unchecked")
    public synchronized E get(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }
        return (E) elements[index];
    }

    public synchronized void remove(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }
        System.arraycopy(elements, index + 1, elements, index, size - index - 1);
        elements[--size] = null; // Avoids memory leak
    }

    private void ensureCapacity() {
        if (size == elements.length) {
            int newCapacity = elements.length * 2;
            Object[] newElements = new Object[newCapacity];
            System.arraycopy(elements, 0, newElements, 0, size);
            elements = newElements;
        }        
        
    }
    
    
    @Override
    public synchronized Iterator<E> iterator() {
        return new MyArrayListIterator();
    }

    private class MyArrayListIterator implements Iterator<E> {
        private int currentIndex = 0;
        private boolean canRemove = false;

        @Override
        public boolean hasNext() {
            return currentIndex < size;
        }

        @SuppressWarnings("unchecked")
        @Override
        public E next() {
            if (!hasNext()) {
                throw new java.util.NoSuchElementException();
            }
            canRemove = true; // The next() call allows removal of the current element
            return (E) elements[currentIndex++];
        }

        // Optional: Implement remove() method if needed
        @Override
        public synchronized void remove() {
            if (!canRemove) {
                throw new IllegalStateException("next() must be called before remove()");
            }

            // Remove the last element returned by next() (currentIndex - 1)
            MyArrayList.this.remove(currentIndex - 1);
            currentIndex--;
            canRemove = false;
        }
    }

    public static void main(String[] args) {
    	MyArrayList<Integer> myList = new MyArrayList<>();

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

     // Using the iterator explicitly with synchronization
        synchronized (myList) {
            Iterator<Integer> iterator = myList.iterator();
            while (iterator.hasNext()) {
                int element = iterator.next();
                System.out.println(element);

                if (element == 2) {
                    iterator.remove(); // Remove the element 2
                }
            }
        }

        System.out.println("Size after removal: " + myList.size());
    }
}

Remember that the use of synchronized keyword introduces some level of thread contention, which might impact performance in highly concurrent scenarios. If you need more advanced concurrency support or have specific performance requirements, you may consider using the built-in concurrent collections provided by Java’s java.util.concurrent package.

(For the latest updates, always refer to the official Java documentation.)

Related posts