Category Archives: Java Collections Framework

ArrayList

What and Why ArrayList

ArrayList is most commonly used data structure from Collections framework.

ArrayList is an implementation of the List interface in the Java Collections Framework. It provides a resizable array-based implementation of a dynamic list, allowing you to store and manipulate elements in a flexible and efficient manner.

ArrayList allows storing duplicates and also null values. It’s important to note that ArrayList treats each occurrence of a duplicate or null value as a separate entry.

Here are some key features and reasons why you might choose to use ArrayList:

  1. Dynamic Size: ArrayList automatically adjusts its size as elements are added or removed, making it convenient for situations where the number of elements may vary over time. You do not need to worry about manually managing the underlying array’s size.
  2. Random Access: ArrayList allows for constant-time random access to elements based on their index. This means you can quickly retrieve elements from the list using the get(int index) method.
  3. Fast Iteration: ArrayList provides efficient iteration over its elements using enhanced for loops or the Iterator interface. It is particularly useful when you need to iterate through the entire list or access elements sequentially.
  4. Dynamic Modification: ArrayList supports various methods for adding, removing, and modifying elements. You can easily append elements to the end of the list (add(E element)), insert elements at specific positions (add(int index, E element)), remove elements (remove(int index) or remove(Object o)), and modify existing elements (set(int index, E element)).
  5. Backed by an Array: Internally, ArrayList is backed by an array, which provides efficient element storage and retrieval. This array-based implementation offers faster random access compared to linked data structures.
  6. Compatibility with Algorithms: ArrayList is widely used and supported in Java libraries and APIs. It is compatible with various algorithms, utility classes, and methods provided by the Java Collections Framework, making it easy to integrate with existing code.
  7. Simple and Familiar API: ArrayList has a straightforward and intuitive API, allowing for easy adoption and usage. It inherits methods from the List interface, providing a consistent interface for working with other list implementations.

However, it’s important to note that ArrayList might not be the best choice in every situation. If you frequently insert or remove elements in the middle of the list, LinkedList may offer better performance. Additionally, if thread safety is a concern, you might consider using Vector or other concurrent collection classes.

In summary, ArrayList is a commonly used implementation of the List interface, providing dynamic size, random access, fast iteration, and efficient element manipulation. It is a versatile choice for many scenarios where you need to work with ordered collections of elements.

** If appending elements to the list (or) removing the last element is a frequent operation, then you can use ArrayList as both these operations can be performed in constant time O(1) time. NOTE : While appending element, if the ArrayList internal capacity is reached, it would trigger resizing and additional time involved.


ArrayList class syntax in Java API

The syntax of the ArrayList class in the Java API is as follows:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, Serializable {

    // Constructors
    public ArrayList();
    public ArrayList(int initialCapacity);
    public ArrayList(Collection<? extends E> c);

    // Methods
    public boolean add(E e);
    public void add(int index, E element);
    public boolean addAll(Collection<? extends E> c);
    public boolean addAll(int index, Collection<? extends E> c);
    public void clear();
    public boolean contains(Object o);
    public E get(int index);
    public int indexOf(Object o);
    public boolean isEmpty();
    public int lastIndexOf(Object o);
    public E remove(int index);
    public boolean remove(Object o);
    public boolean removeAll(Collection<?> c);
    public boolean retainAll(Collection<?> c);
    public E set(int index, E element);
    public int size();
    public List<E> subList(int fromIndex, int toIndex);
    public Object[] toArray();
    public <T> T[] toArray(T[] a);
    public void trimToSize();
}

In the above syntax, the ArrayList class is defined as a generic class (ArrayList<E>) that can work with elements of any specific type represented by the type parameter E.

The class extends the AbstractList class and implements the List interface, RandomAccess interface (indicating fast random access), Cloneable interface (supporting cloning), and Serializable interface (supporting serialization).


ArrayList – Capacity

Since an array is used internally to implement this data structure, it obviously has some size associated with it and by default it is set to 10. The capacity of an ArrayList refers to the size of this internally array.

The ArrayList class in Java dynamically resizes itself to accommodate the elements added to it. It internally maintains an array that holds the elements. The capacity of an ArrayList refers to the size of this internal array, which may be larger than the number of elements currently stored in the list.

Here are a few key points about the capacity of an ArrayList:

  1. Initial Capacity: When you create an ArrayList using the default constructor (ArrayList()), it starts with an initial capacity of 10. This means the internal array can hold up to 10 elements without needing to resize.
  2. Resizing: If you add more elements to an ArrayList and its internal array reaches its capacity, the ArrayList automatically increases the capacity by creating a new, larger array and copying the elements from the old array to the new one. This resizing process typically doubles the size of the internal array (or) in some cases, it increased by 50%.
  3. Capacity vs. Size: The capacity of an ArrayList is not the same as its size. The size refers to the number of elements currently stored in the list, while the capacity refers to the total number of elements the ArrayList can hold before it needs to resize its internal array.
  4. Controlling Capacity: If you know the expected number of elements in advance, you can specify the initial capacity when creating the ArrayList using the constructor ArrayList(int initialCapacity). This can help avoid unnecessary resizing operations if you have an estimate of the number of elements you will be adding.
  5. TrimToSize: If you want to minimize the internal array’s size to match the current number of elements, you can use the trimToSize() method. This method reduces the capacity of the ArrayList to be the same as its size, potentially saving memory if you no longer expect to add many more elements.

In summary, the capacity of an ArrayList refers to the total number of elements it can hold before needing to resize its internal array. The ArrayList class manages the capacity dynamically, automatically resizing as needed to accommodate additional elements.

** When the capacity is reached, a new array is created with a size that is 50% or double than the old array size. Old array contents are copied into the new array. However, excessive resizing due to adding large numbers of elements can impact performance, so it’s beneficial to estimate the expected size when possible.


ensureCapacity(int) method

The ensureCapacity(int) method is a method in the ArrayList class that allows you to ensure that the ArrayList has a minimum capacity specified by the parameter. It increases the capacity of the internal array, if necessary, to accommodate at least the specified number of elements without automatic resizing.

public void ensureCapacity(int minCapacity)

Here’s how the ensureCapacity(int) method works:

  1. If the current capacity of the ArrayList is less than the minCapacity parameter, the ensureCapacity(int) method increases the capacity of the internal array to be at least minCapacity. This ensures that the ArrayList can accommodate the specified number of elements without resizing.
  2. If the current capacity is already greater than or equal to the minCapacity, no resizing occurs. The ensureCapacity(int) method has no effect in this case.

Using ensureCapacity(int) can be helpful when you have an estimate of the number of elements to be added to the ArrayList. It allows you to allocate sufficient capacity in advance, reducing the number of resizing operations and improving performance.

// Ensure that the ArrayList has a minimum capacity of 20
myList.ensureCapacity(20);


trimToSize() method example

Here is the example :

import java.util.ArrayList;

public class ArrayListExample {
    public static void main(String[] args) {

        // Create an ArrayList with an initial capacity of 20
        ArrayList<String> myList = new ArrayList<>(20);

        // Add elements to the ArrayList
        myList.add("Apple");
        myList.add("Banana");
        myList.add("Orange");

        // Print the elements and size of the ArrayList before trimming
        System.out.println("Elements in the ArrayList: " + myList);
        System.out.println("Size of the ArrayList before trimming: " + myList.size());

        // Trim the ArrayList to match its size
        myList.trimToSize();

        // Print the elements and size of the ArrayList after trimming
        System.out.println("Elements in the ArrayList after trimming: " + myList);
        System.out.println("Size of the ArrayList after trimming: " + myList.size());
    }
}

In this example, we create an ArrayList called myList with an initial capacity of 20 using the ArrayList(int initialCapacity) constructor. We add elements to the myList using the add() method.

We use the trimToSize() method to trim the ArrayList to match its size, reducing the internal array’s capacity to match the number of elements.

The trimToSize() method is useful when you want to minimize the memory footprint of the ArrayList by reducing its internal array’s capacity to match the actual number of elements. This can be beneficial if you no longer expect to add many more elements to the list and want to save memory.

Note that calling trimToSize() does not affect the functionality of the ArrayList. If you subsequently add more elements, the ArrayList will automatically resize its internal array to accommodate the additional elements, as needed.


ArrayList – duplicate and null values

It’s important to note that ArrayList treats each occurrence of a duplicate or null value as a separate entry.

Duplicate Values: ArrayList permits storing duplicate values. You can add multiple occurrences of the same element to the list, and they will be stored as separate entries.

Null Values: ArrayList allows storing null values. You can add null as an element to the list without any issues.

ArrayList<String> myList = new ArrayList<>();
myList.add("Apple");
myList.add(null);
myList.add("Banana");
myList.add(null);
myList.add("Apple");

System.out.println(myList.contains(null));       // Output: true
System.out.println(myList.indexOf(null));        // Output: 1
System.out.println(myList.lastIndexOf(null));    // Output: 3

System.out.println(myList);  // Output: [Apple, null, Banana, null, Apple]


ArrayList methods

add(index, element) method

The ArrayList class in Java provides the add(int index, E element) method, which allows you to insert an element at a specific position in the list. Here’s how the add(int index, E element) method works:

public void add(int index, E element)
  • The index parameter represents the position at which you want to insert the element. It should be within the range of 0 to size() (inclusive).
  • The element parameter represents the element to be inserted at the specified index.

When you call add(int index, E element), the method inserts the specified element at the specified index in the ArrayList. The existing elements from the index onwards are shifted to the right (their indices are incremented by 1).

        // Insert an element at index 1
        myList.add(1, "Mango");

It’s important to note that the add(int index, E element) method should be used with caution, as it can potentially affect the performance for large lists. Shifting elements requires additional processing time, especially when inserting at the beginning or middle of the list.

remove(index) method

The ArrayList class in Java provides the remove(int index) method, which allows you to remove an element at a specific position in the list. Here’s how the remove(int index) method works:

public E remove(int index)
  • The index parameter represents the position of the element to be removed. It should be within the range of 0 to size() - 1 (inclusive).

When you call remove(int index), the method removes the element at the specified index from the ArrayList. The elements to the right of the removed element are shifted to the left (their indices are decremented by 1).

** The method returns the element that was removed from the list.

        // Remove the element at index 1
        String removedElement = myList.remove(1);

It’s important to note that when you remove an element using remove(int index), the indices of the subsequent elements change. Therefore, if you plan to iterate over the list using indices, be cautious when removing elements to avoid skipping or accessing the wrong elements.

boolean contains(Object o)

The contains(Object o) method is a convenient way to check for the presence of an element in an ArrayList without explicitly iterating over the list. It provides a simple and efficient way to perform membership checks.

The contains(Object o) method in the ArrayList class is used to check whether the list contains a specific element. It returns a boolean value indicating whether the specified object is present in the list or not. Here’s how the contains(Object o) method works:

public boolean contains(Object o)
  • The o parameter represents the object that you want to check for presence in the list.

When you call contains(Object o), the method checks if the specified object is present in the ArrayList. It iterates over the elements in the list and uses the equals() method to compare the specified object with each element. If a matching element is found, the method returns true. If the specified object is not found in the list or if the list is empty, the method returns false.

        // Create an ArrayList
        ArrayList<String> myList = new ArrayList<>();

        // Add elements to the ArrayList
        myList.add("Apple");
        myList.add("Banana");
        myList.add("Orange");

        // Check if "Banana" is present in the ArrayList
        boolean containsBanana = myList.contains("Banana");
        System.out.println("ArrayList contains 'Banana': " + containsBanana);
 // true

We then use the contains("Banana") method to check if the ArrayList contains the element “Banana”. The method returns true because “Banana” is present in the list.

contains(Object o) relation with equals() method

The contains(Object o) method in the ArrayList class uses the equals() method to determine whether the specified object is present in the list.

When contains(Object o) is called, it iterates over the elements in the ArrayList and calls the equals() method on each element to compare it with the specified object o. By using equals(), the method can perform customized equality checks if the element class overrides the equals() method.

By default, the equals() method in the Object class compares the references of objects, which means two different instances with the same content are considered unequal. However, many classes in Java, including String, Integer, and custom classes, override the equals() method to provide meaningful equality comparisons based on their internal state.

Therefore, when using contains(Object o) with an ArrayList, it’s important to consider how the equals() method is defined for the objects you are working with.

** If you’re working with custom classes, you may need to override the equals() method to ensure proper equality comparisons according to your specific requirements.

By relying on the equals() method, contains(Object o) provides flexibility to handle various types of objects and supports customized equality comparisons, making it suitable for general use cases.

NOTE – The methods contains(), indexOf(), lastIndexOf() – all these methods use equals() method to compare the object with each of the element in the list.


boolean removeAll(Collection<?> c) method

The removeAll(Collection<?> c) method in the ArrayList class is used to remove all elements from the list that are contained in the specified collection.

Here’s how the removeAll(Collection<?> c) method works internally:

  1. Iteration: The removeAll(Collection<?> c) method iterates over each element in the specified collection (c).
  2. Element Comparison: For each element in the specified collection, the removeAll method internally calls the remove(Object o) method to remove all occurrences of that element from the ArrayList. It uses the equals() method to determine the equality between the elements.
  3. Removal: When remove(Object o) is called, it iterates over the elements in the ArrayList and removes all occurrences of the specified object o. This is achieved by shifting the subsequent elements to the left and updating the size of the ArrayList accordingly.
  4. Iteration Completion: After iterating through all the elements in the specified collection, the removeAll method completes the removal process.

The removeAll(Collection<?> c) method returns true if at least one element was removed from the ArrayList. If no elements were removed, it returns false.

Here’s an example that demonstrates the usage of the removeAll(Collection<?> c) method:

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

public class ArrayListExample {
    public static void main(String[] args) {
        // Create an ArrayList
        ArrayList<String> myList = new ArrayList<>(Arrays.asList("Apple", "Banana", "Orange", "Apple"));

        System.out.println("ArrayList before removeAll: " + myList);

        // Create a collection with elements to remove
        List<String> toRemove = Arrays.asList("Apple", "Orange");

        // Remove elements from the ArrayList
        boolean removed = myList.removeAll(toRemove);

        System.out.println("Elements removed: " + removed);
        System.out.println("ArrayList after removeAll: " + myList);
    }
}

Output :

ArrayList before removeAll: [Apple, Banana, Orange, Apple]
Elements removed: true
ArrayList after removeAll: [Banana]

We use the removeAll(toRemove) method to remove all occurrences of the elements in the toRemove collection from the ArrayList. In this case, “Apple” and “Orange” are removed.

Internally, the removeAll(Collection<?> c) method iterates over the elements in the specified collection, calls contains() method and if element is present, removes the matching element from the ArrayList, using the remove(Object o) method. It repeats this process for each element in the collection until all occurrences are removed.


ArrayList example

Here’s an example that demonstrates the usage of various methods in the ArrayList class :

import java.util.ArrayList;

public class ArrayListExample {
    public static void main(String[] args) {
        // Create an ArrayList
        ArrayList<String> myList = new ArrayList<>();

        // Add elements to the ArrayList
        myList.add("Apple");
        myList.add("Banana");
        myList.add("Orange");

        // Accessing elements using get() method
        System.out.println("Element at index 0: " + myList.get(0));
        System.out.println("Element at index 1: " + myList.get(1));
        System.out.println("Element at index 2: " + myList.get(2));

        // Modifying elements using set() method
        myList.set(1, "Grapes");

        // Printing the modified ArrayList
        System.out.println("Modified ArrayList: " + myList);

        // Checking the size of the ArrayList
        System.out.println("Size of ArrayList: " + myList.size());

        // Checking if the ArrayList is empty
        System.out.println("Is ArrayList empty? " + myList.isEmpty());
    }
}

Output :

Element at index 0: Apple
Element at index 1: Banana
Element at index 2: Orange
Modified ArrayList: [Apple, Grapes, Orange]
Size of ArrayList: 3
Is ArrayList empty? false

In the above example:

  1. We create an ArrayList called myList.
  2. We use the add() method to add three elements (“Apple”, “Banana”, and “Orange”) to the ArrayList.
  3. We use the get() method to access and print the elements at specific indices (0, 1, and 2).
  4. We use the set() method to modify the element at index 1, replacing “Banana” with “Grapes”.
  5. We print the modified ArrayList using System.out.println().
  6. We use the size() method to check the size of the ArrayList and print it.
  7. We use the isEmpty() method to check if the ArrayList is empty (contains no elements) and print the result.

Another example :

import java.util.ArrayList;

public class ArrayListExample {
    public static void main(String[] args) {
        // Create an ArrayList
        ArrayList<String> myList = new ArrayList<>();

        // Add elements to the ArrayList
        myList.add("Apple");
        myList.add("Banana");
        myList.add("Orange");

        // Accessing elements using get() method
        System.out.println("Element at index 0: " + myList.get(0));
        System.out.println("Element at index 1: " + myList.get(1));
        System.out.println("Element at index 2: " + myList.get(2));

        // Modifying elements using set() method
        myList.set(1, "Grapes");

        // Printing the modified ArrayList
        System.out.println("Modified ArrayList: " + myList);

        // Checking the size of the ArrayList
        System.out.println("Size of ArrayList: " + myList.size());

        // Checking if the ArrayList is empty
        System.out.println("Is ArrayList empty? " + myList.isEmpty());

        // Removing elements using remove() method
        myList.remove("Apple");

        // Printing the ArrayList after removing an element
        System.out.println("ArrayList after removing an element: " + myList);

        // Removing all elements using removeAll() method
        myList.removeAll(myList);

        // Printing the ArrayList after removing all elements
        System.out.println("ArrayList after removing all elements: " + myList);

        // Adding elements to the ArrayList again
        myList.add("Mango");
        myList.add("Pineapple");

        // Iterating over elements using an enhanced for loop
        System.out.println("Iterating over elements:");
        for (String element : myList) {
            System.out.println(element);
        }
    }
}

Output :

Element at index 0: Apple
Element at index 1: Banana
Element at index 2: Orange
Modified ArrayList: [Apple, Grapes, Orange]
Size of ArrayList: 3
Is ArrayList empty? false
ArrayList after removing an element: [Grapes, Orange]
ArrayList after removing all elements: []
Iterating over elements:
Mango
Pineapple

In the above example:

  1. We create an ArrayList called myList.
  2. We use the add() method to add three elements (“Apple”, “Banana”, and “Orange”) to the ArrayList.
  3. We use the get() method to access and print the elements at specific indices (0, 1, and 2).
  4. We use the set() method to modify the element at index 1, replacing “Banana” with “Grapes”.
  5. We print the modified ArrayList using System.out.println().
  6. We use the size() method to check the size of the ArrayList and print it.
  7. We use the isEmpty() method to check if the ArrayList is empty (contains no elements) and print the result.
  8. We use the remove() method to remove the element “Apple” from the ArrayList.
  9. We print the ArrayList after removing an element.
  10. We use the removeAll() method to remove all elements from the ArrayList.
  11. We print the ArrayList after removing all elements.
  12. We add elements “Mango” and “Pineapple” to the ArrayList again.
  13. We use an enhanced for loop to iterate over the elements in the ArrayList and print each element.

List Interface

What and Why List interface

The List interface in Java is used when you need to store a collection of elements in a specific order that allows duplicate elements. It is part of the Java Collections Framework and provides a flexible and powerful way to manage and manipulate ordered collections.

Here are some reasons why you would use the List interface in Java:

  1. Order Preservation: The List interface maintains the order of elements as they are inserted. This allows you to access elements by their index and perform operations like appending, inserting, and removing elements at specific positions.
  2. Indexed Access: List provides direct access to elements based on their index using methods like get(int index). This makes it convenient to retrieve and modify elements at specific positions within the list.
  3. Duplicate Elements: Unlike other collection types like Set, List allows duplicate elements. You can add multiple occurrences of the same element to the list, and they will be stored as separate entries.
  4. Dynamic Size: List implementations, such as ArrayList or LinkedList, automatically handle resizing as elements are added or removed. This allows you to dynamically change the size of the list without needing to manage the underlying data structure manually.
  5. Iteration and Manipulation: The List interface provides several methods to iterate over elements, such as enhanced for loop, Iterator, or ListIterator. It also offers numerous methods to add, remove, or modify elements, allowing you to perform various operations on the collection.
  6. Compatibility with Algorithms: The List interface is widely used in Java libraries and APIs. It is compatible with numerous algorithms, sorting methods, and utility classes provided by the Java Collections Framework. It allows you to leverage the rich functionality available for working with ordered collections.
  7. Flexibility: The List interface has various implementations to choose from, depending on your specific requirements. For example, ArrayList provides fast element access and manipulation, while LinkedList offers efficient insertion and removal at both ends of the list.

Overall, the List interface is suitable when you need to manage ordered collections that allow duplicates and require frequent access to elements based on their position. It provides a rich set of methods and compatibility with existing Java libraries, making it a versatile choice for many use cases.


List interface syntax as in Java API

The syntax of the List interface in the Java API is as follows :

public interface List<E> extends Collection<E> {
    // Positional Access
    E get(int index);
    E set(int index, E element); // inserts and returns previous element at index.
    void add(int index, E element);
    void add(E element); // adds to the end of list
    E remove(int index);
    boolean addAll(int index, Collection<? extends E> c); // add all from Collection

    // Search Operations
    int indexOf(Object o); // returns -1 if object not found
    int lastIndexOf(Object o);

    // List Iterators
    ListIterator<E> listIterator();
    ListIterator<E> listIterator(int index);

    // View Operations
    List<E> subList(int fromIndex, int toIndex);
}

In the above syntax, the List interface is defined as a generic interface (List<E>) that can work with elements of any specific type represented by the type parameter E.

The interface extends the Collection interface, inheriting its methods and adding additional methods specific to lists.

The List interface includes methods categorized into positional access, search operations, list iterators, and view operations.

The positional access methods include get(int index), set(int index, E element), add(int index, E element), and remove(int index). These methods allow you to retrieve an element at a specific index, modify the element at a given index, insert an element at a particular index, and remove an element at a specific index, respectively.

The search operations methods are indexOf(Object o) and lastIndexOf(Object o). These methods return the index of the first occurrence and the last occurrence of the specified object in the list, respectively. If the object is not found, these methods return -1.

The list iterator methods are listIterator() and listIterator(int index). These methods return a ListIterator that allows bidirectional iteration over the elements of the list. The second variant allows you to specify the starting position for the iterator.

  • ListIterator is a sub interface of Iterator interface.

The range view operation method is subList(int fromIndex, int toIndex). This method returns a view of the list between the specified fromIndex (inclusive) and toIndex (exclusive).

  • Returned list is backed by the original list, meaning any change made in the returned list will be reflected in original list and vice versa. Changes to the sublist will be reflected in the original list and vice versa.
  • ** To the returned list, we can do both data related changes(meaning updating data) or structural changes like adding / removing elements etc. The changes will get reflected in original list. However, to the original list, if we do any structural changes like adding / removing elements, then the returned list which is the view, will be invalidated and trying to access elements using returned list will throw ConcurrentModificationException.

Note that the List interface inherits other methods from the Collection interface, such as add(E e), remove(Object o), contains(Object o), size(), isEmpty(), addAll(Collection<? extends E> c), removeAll(Collection<?> c), retainAll(Collection<?> c), containsAll(Collection<?> c), toArray(), and toArray(T[] a).

The List interface serves as a contract for list implementations and provides a wide range of methods to manage ordered collections.

Collection interface

What is Collection Interface

The Collection interface in Java is the foundation of the Java Collections Framework. It represents a group of objects, known as elements, and provides a set of methods to manipulate and operate on these elements.

The Collection interface is a root interface that extends the Iterable interface, allowing collections to be iterated over using the enhanced for loop or Iterator interface. Since, it is root interface, polymorphically it provides maximum generality.

The AbstractCollection class in Java is an abstract base class that provides a skeletal implementation of the Collection interface. It serves as a convenient starting point for creating custom collection classes by handling common functionality and reducing the implementation effort required for concrete collection classes.

Collection interface extends another interface called Iterable interface which would enable any collection object to be used in for each loops.


Collection interface syntax as in Java API

public interface Collection<E> extends Iterable<E> {
    // Basic Operations
    boolean add(E e);
    boolean remove(Object o);
    boolean contains(Object o);
    int size();
    boolean isEmpty();
    void clear();

    // Bulk Operations
    boolean addAll(Collection<? extends E> c);
    boolean removeAll(Collection<?> c);
    boolean retainAll(Collection<?> c);
    boolean containsAll(Collection<?> c);
    void clear(); // optional, removes all elements from collection.
  
    // Array Conversion
    Object[] toArray();
    <T> T[] toArray(T[] a);

    // Iteration
    Iterator<E> iterator();

    // Stream Support (Java 8+)
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }

    default Stream<E> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
    }
}

In the above syntax, the Collection interface is defined as a generic interface (Collection<E>) that can work with elements of any specific type represented by the type parameter E.

The interface extends the Iterable interface, allowing collections to be iterated over using the enhanced for loop or the Iterator interface.


Collection interface methods

The Collection interface defines a set of methods categorized as basic operations, bulk operations, array conversion, iteration, and stream support (added in Java 8).

The basic operations include methods like add(), remove(), contains(), size(), isEmpty(), and clear(). These methods provide common functionality for adding, removing, checking the presence of elements, and managing the size and emptiness of the collection.

The bulk operations include methods like addAll(), removeAll(), retainAll(), and containsAll(). These methods allow collections to be modified based on other collections, such as adding all elements from another collection, removing elements common to another collection, retaining only elements from another collection, and checking if the collection contains all elements of another collection.

The array conversion methods toArray() and toArray(T[] a) allow the collection to be converted into an array. The first method returns an Object array, while the second method allows specifying the type of the array.

The iterator() method returns an Iterator over the elements of the collection, allowing for iteration and sequential access to the elements.

Starting from Java 8, the stream() and parallelStream() methods are provided for supporting streaming operations using the Stream API.

Concrete implementations of the Collection interface, such as ArrayList, LinkedList, and HashSet, provide additional methods and functionalities specific to their implementations.


Examples

<T> T[] toArray(T[] a) method

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

public class ToArrayExample {
    public static void main(String[] args) {
        List<String> myList = new ArrayList<>();
        myList.add("Hello");
        myList.add("World");

        // Create an array of String with the same size as the list
        String[] array = new String[myList.size()];

        // Convert the list to an array using the toArray() method
        String[] resultArray = myList.toArray(array);

        // Print the elements of the resulting array
        for (String element : resultArray) {
            System.out.println(element);
        }
    }
}

In this example, we have a List of strings called myList. We want to convert this list to an array of strings using the toArray(T[] a) method.

First, we create a String array called array with the same size as the list using myList.size(). Then, we call the toArray() method on the myList object and pass array as the argument.

** The toArray(T[] a) method takes the provided array as an argument and populates it with the elements of the list. If the provided array is large enough to accommodate all the elements, it will be used. Otherwise, a new array of the same runtime type and size will be created and returned.

This usage of toArray(T[] a) allows you to convert a List or any other Collection into an array of the desired type. It provides a way to obtain an array representation of the collection’s elements, which can be useful in scenarios where you specifically need an array or want to interface with code that requires an array.

Object[] toArray()

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

public class ToArrayExample {
    public static void main(String[] args) {
        List<String> myList = new ArrayList<>();
        myList.add("Hello");
        myList.add("World");

        // Convert the list to an array using the toArray() method
        Object[] resultArray = myList.toArray();

        // Print the elements of the resulting array
        for (Object element : resultArray) {
            System.out.println(element);
        }
    }
}

In this example, we have a List of strings called myList. We want to convert this list to an array of Object using the toArray() method.

We simply call the toArray() method on the myList object without passing any argument. The toArray() method returns an array of Object, where each element of the list is copied into the corresponding position of the array.

Finally, we iterate over the resulting array (resultArray) and print its elements. Since the resultArray is of type Object[], we need to use the Object type to iterate over the elements.

The toArray() method is useful when you need to obtain a generic array representation of a List or any other Collection. However, keep in mind that the resulting array will have the runtime type of Object[].

** If you require a specific type of array, you can use the overloaded toArray(T[] a) method and pass an array of the desired type.


How to enable your class objects to be iterable

To enable your class objects to be iterable, you need to implement the Iterable interface and provide an implementation of the iterator() method. This allows instances of your class to be used in enhanced for loops or with the Iterator interface for iteration.

Here’s an example of how to enable your class objects to be iterable:

import java.util.Iterator;

public class MyIterableClass<T> implements Iterable<T> {
    private T[] elements;

    public MyIterableClass(T[] elements) {
        this.elements = elements;
    }

    // provide an implementation for iterator() method
    @Override
    public Iterator<T> iterator() {
        return new MyIterator();
    }

    // private class implementing Iterator interface
    private class MyIterator implements Iterator<T> {
        private int currentIndex = 0;

        @Override
        public boolean hasNext() {
            return currentIndex < elements.length;
        }

        @Override
        public T next() {
            return elements[currentIndex++];
        }
    }

    // Other methods and code specific to your class
}

In the example above, the MyIterableClass implements the Iterable interface by specifying the type parameter <T>. It provides an implementation of the iterator() method that returns an instance of the custom MyIterator class.

The MyIterator class implements the Iterator interface and defines the hasNext() and next() methods required for iteration. It keeps track of the current index to provide the next element from the array of elements.

With this implementation, you can use instances of MyIterableClass in enhanced for loops or with the Iterator interface as follows:

MyIterableClass<String> iterable = new MyIterableClass<>(new String[]{"Hello", "World"});

// Using enhanced for loop
for (String element : iterable) {
    System.out.println(element);
}

// Using Iterator
Iterator<String> iterator = iterable.iterator();
while (iterator.hasNext()) {
    String element = iterator.next();
    System.out.println(element);
}

By implementing the Iterable interface and providing an iterator implementation, you enable your class objects to be

  • iterated over using enhanced for loops or
  • by obtaining an iterator explicitly.

This allows for a more intuitive and convenient way of working with instances of your class in iterative scenarios.


Collections Framework – Introduction

What is Collections Framework

The Collections Framework in Java is a unified architecture that provides a set of interfaces, classes, and algorithms to manipulate and store collections of objects. It was introduced in Java 1.2 and is part of the java.util package.

The Collections Framework offers a wide range of data structures, such as lists, sets, queues, and maps, along with various utility classes for working with collections. These structures and utilities are designed to be efficient, flexible, and reusable.

These are ready to use, highly efficient data structures which we can use right out of the box without having to implement ourselves.

Key components of the Collections Framework

Here are some key components of the Collections Framework:

  1. Interfaces: The framework defines several core interfaces, including List, Set, Queue, Map, and their sub-interfaces. These interfaces provide common methods and contracts for working with collections.
  2. Implementations: The framework provides various classes that implement the collection interfaces. For example, ArrayList and LinkedList are implementations of the List interface, HashSet and TreeSet implement the Set interface, and HashMap and TreeMap implement the Map interface. These implementations offer different performance characteristics and behaviors to suit different needs.
  3. Algorithms: The Collections class provides a set of algorithms, such as sorting, searching, shuffling, and reversing, which can be applied to collections. These algorithms are implemented as static methods and can be used with any collection that satisfies the requirements.
  4. Utility classes: The Collections Framework includes utility classes like Collections and Arrays that provide additional functionality for manipulating and working with collections. These classes offer methods for operations like collection conversion, synchronization, and filling collections with default values.

The Collections Framework simplifies the process of working with collections by providing a consistent and standardized way to handle them. It promotes code reusability and improves code quality by encouraging the use of interfaces and providing efficient implementations for common collection types.

General Requirements

General requirements which need to be fulfilled by data structures for real world use cases :

  1. No knowledge about size
  2. Automatically extendable
  3. Fast random access
  4. Fast lookups
  5. Ordered vs Unordered
  6. null vs non-null data
  7. Duplicate vs Unique
  8. Automatic sorting
  9. <key, value> mapping

Collections Framework includes different kinds of data structures that would meet one or more of these requirements.

Core Interfaces in Collections Framework

source – https://techvidvan.com/tutorials/java-collection-framework/

  • Collection – would represent a collection of objects.
  • Map – would represent <key, value> pairs.

Most of the implementation permit storing null values; the implementations are not synchronized.

SortedSet, SortedMap – as the name implies they ensure that the data they store is automatically sorted, meaning elements are placed at appropriate positions.

NOTE – Set implementations internally use Map implementations.


Legacy Implementations

In the Java Collections Framework, there are certain legacy implementations that have been retained for compatibility reasons but are generally considered less preferred than their modern alternatives.

The legacy implementations are considered less efficient due to factors such as synchronization overhead, lack of type safety, and limited functionality. The modern alternatives provide improved performance, type safety, and additional features.

Legacy implementations – these are synchronized data structures. Recommended, not to use these classes any more.

  • Vector – replace with ArrayList, LinkedList
  • HashTable – replace with HashMap, LinkedHashMap
  • Stack – replace with ArrayDeque


Set implementation uses Map

In Java, the implementation of the Set interface in the Collections Framework often internally uses a Map implementation to store its elements.

The Set interface is designed to store a collection of unique elements, where duplicates are not allowed. To ensure uniqueness, Set implementations rely on the keys of a Map to store the elements. The elements themselves are stored as keys, leveraging the fact that a Map cannot contain duplicate keys.

When adding elements to a Set, the implementation typically adds the elements as keys into an underlying Map implementation and uses a dummy value (e.g., Boolean.TRUE) as the associated value.

  • The map’s key set represents the elements of the Set, and the uniqueness property is automatically enforced by the map’s behavior.

By utilizing a Map internally, Set implementations can take advantage of the efficient key-based operations provided by the map, such as constant-time contains, add, and remove operations, which are crucial for maintaining the integrity of a Set.

It’s important to note that the specific implementation details may vary across different Set implementations in Java, such as HashSet, TreeSet, or LinkedHashSet. However, the underlying usage of a Map data structure to enforce uniqueness remains a common approach.


Java Collection Framework classes – Serializable

Most of the implementations in Collection Framework are serializable.

In Java, when a class implements the Serializable interface, it means that objects of that class can be converted into a stream of bytes and then saved to a file or transferred over a network. The process of converting an object into a stream of bytes is known as serialization.

The Serializable interface acts as a marker interface, meaning it does not define any methods that need to be implemented. Its purpose is to indicate that an object of the implementing class can be serialized.

For the Java Collections Framework, many of the classes and interfaces, including List, Set, Map, and their implementations like ArrayList, HashSet, HashMap, etc., are designed to be serializable. This allows you to serialize instances of these classes and store them persistently or transmit them across a network.

When a collection class is serializable, it means you can write the collection object to an ObjectOutputStream and read it back from an ObjectInputStream, preserving its state and contents.

Here’s an example of serializing a List implementation (ArrayList) to a file:

import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class SerializationExample {
    public static void main(String[] args) {
        List<String> myList = new ArrayList<>();
        myList.add("Hello");
        myList.add("World");

        // Serialize the list to a file
        try (ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("list.ser"))) {
            outputStream.writeObject(myList);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Deserialize the list from the file
        try (ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("list.ser"))) {
            List<String> deserializedList = (List<String>) inputStream.readObject();
            System.out.println(deserializedList);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

In this example, the ArrayList object myList is serialized to a file named list.ser. Later, the list is deserialized and printed, restoring its original state.

** It’s worth noting that not all classes in the Java Collections Framework are serializable. For example, concurrent collections like ConcurrentHashMap and specialized collections like PriorityQueue may not implement the Serializable interface due to their complex internal state or synchronization requirements.

When using serialization, it’s important to be aware of the potential issues related to versioning, security, and compatibility between different Java versions or platforms. It’s recommended to carefully design and test the serialization and deserialization process to ensure data integrity and compatibility.


Java Collection Framework classes – Clone

In the Java Collections Framework, some classes provide a clone() method to create a shallow copy of the object. The clone() method creates a new instance of the same class and copies the values of the internal data structures and fields into the new instance. However, it’s important to note that the clone() method is considered to be somewhat problematic and is not widely used in modern Java programming.

  • By default, the clone() method creates a shallow copy of the object. This means that the internal data structures are not cloned deeply, and both the original and cloned objects share references to the same objects. Changes made to the shared objects may affect both the original and cloned instances.
  • Required Casting: The clone() method returns a copy of the object as an Object type. To use the cloned object as the specific type, you need to cast it appropriately. For example:
ArrayList<String> originalList = new ArrayList<>();
originalList.add("Hello");
originalList.add("World");

ArrayList<String> clonedList = (ArrayList<String>) originalList.clone();

Alternatives: In modern Java programming, it is often recommended to use alternative approaches for creating copies of objects, such as copy constructors, factory methods, or the copyOf() methods provided by some collection classes (List.copyOf(), Set.copyOf(), etc.). These approaches provide more control and clarity over the copying process.

To summarize, while the clone() method exists in some classes of the Java Collections Framework, it is generally not recommended to use clone() method due to its limitations and potential issues. It is advisable to consider alternative approaches for creating copies of objects that better suit the requirements of your specific use case.