HelloKoding

Practical coding guides

ArrayList in Java

This article will give you a guide about ArrayList features in Java along with introducing some good practices when using it

  • ArrayList in Java is a dynamic array data structure implementation of the List interface
  • Provides zero-based index operations (get, add, set, remove). Get, set operations run in constant-time performance. Add operation runs in amortized constant time. Other operations run in linear time
  • Permits all elements including duplicated and null
  • You can iterate ArrayList elements in the same order as they are inserted
  • Elements are compared base on their equals method implementation
  • ArrayList is not thread-safe as it is an unsynchronized implementation. In multi-threading environment with at least one thread modifies the list, it must be synchronized externally

Let’s get started!

List implementation

ArrayList implements the List interface which extends from Collection and Iterable interfaces hierarchically

List implementations in Java Collections Framework

As a result from the class hierarchy, you can declare an ArrayList either with the List interface or the ArrayList class as the variable type

ArrayList<Integer> arrayList1 = new ArrayList<>();
List<Integer> arrayList2 = new ArrayList<>();

Use interface type variable for input parameters when declaring a method if you gonna design it for all implementations of that interface

public void flexible(List<Integer> list) {
    //...
}

public void consumeMethod1() {
    ArrayList<Integer> arrayList = new ArrayList();
    flexible(arrayList);
}

public void consumeMethod2() {
    LinkedList<Integer> linkedList = new LinkedList();
    flexible(linkedList);
}

Dynamic array

In contrast to a fixed array, ArrayList is called as dynamic thanks to its capacity can be grown internally when full

int[] fixedArray = new int[2]; // capacity is 2
fixedArray[0] = 1;
fixedArray[1] = 2;
fixedArray[2] = 3; // throws ArrayIndexOutOfBoundsException when full

List<Integer> arrayList = new ArrayList<>(); // capacity is 10
for (int i=1; i<12; i++) {
    arrayList.add(i);
}
// no errors, capacity is 20 now

Capacity and Size

Capacity is the number of elements that an ArrayList can hold. Size is the number of elements that an ArrayList is holding

You can query the size of an ArrayList by using size() method. On the opposite, there’re no methods or properties to get the capacity since it is handled internally

List<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.add(2);
arrayList.add(3);

System.out.println(arrayList.size()); // 3
// arrayList's capacity: 10

Initial capacity

By default, the initial capacity of an ArrayList is 10, when you construct a new instance

List<Integer> arrayList = new ArrayList<>(); // capacity is 10

When an ArrayList is full, it has to create a new double-capacity array and copies the elements from the old to the new array. Thus, if you can measure the number of elements, it’s a good practice to specify it at the creation time

List<Integer> arrayList = new ArrayList<>(100); // capacity is 100

Max capacity

The maximum number of elements that an ArrayList can hold is Integer.MAX_VALUE (2147483648) - 8 (2 billions approximately)

Ensure capacity

If you gonna add a large number of elements which larger than the current ArrayList capacity, it is a good practice to call ensureCapacity() method to reduce the amount of incremental reallocation

List<Integer> arrayList = new ArrayList<>();
for (int i=1; i<=10; i++) {
    arrayList.add(i);
}

// ...

arrayList.ensureCapacity(10+100);
for (int i=1; i<=100; i++) {
    arrayList.add(i);
}

The ensureCapacity() method will increase the capacity of arrayList to ensure that it can hold at least 10+100 elements

Initialize an ArrayList in one line

Use List.of, since Java 9, or Arrays.asList to init an ArrayList in one line at the creation time. List.of does not accept null elements, it will throw NullPointerException

// initialize an ArrayList in one line
List<Integer> lst1 = new ArrayList<>(List.of(3, 1, 3));
List<Integer> lst2 = new ArrayList<>(List.of(5, 4, null)); // throws NullPointerException
List<Integer> lst3 = new ArrayList<>(Arrays.asList(8, 7, 8, null));

If you are having an existing List or Set collection, you can also use it to init a new ArrayList at the creation time

// initialize an ArrayList in one line
List<Integer> lst1 = new ArrayList<>(List.of(3, 1, 3));
List<Integer> lst2 = new ArrayList<>(lst1);

Double brace initialization should not be used in practice since it creates unnecessary anonymous classes with a hidden reference to the owner instance object

List<Integer> arrayList = new ArrayList<>(){{
    add(3);
    add(1);
    add(2);
}};

Add elements to an ArrayList

You can use the following methods to add elements in ArrayList

  • Use add(element) to add a single element
  • Use add(index, element) to insert a single element at the specified index
  • Use addAll(Collection) to add multiple elements
  • Use addAll(index, Collection) to insert multiple elements at the specified index

You can add duplicate and null elements. The insertion order is reserved as it is maintained by ArrayLists

// Create a new ArrayList
List<String> arrayList1 = new ArrayList<>();

// Add a single element
arrayList1.add("a");

// Add a single element at the specified index
arrayList1.add(0, "b");

// Can add null element
arrayList1.add(null);

// Can add duplicate element
arrayList1.add("a");

// Add multiple elements
arrayList1.addAll(Arrays.asList("d", "c"));

// Add multiple elements at the specified index
arrayList1.addAll(2, Arrays.asList("f", "e"));

// ArrayLists maintain the insertion order
// so the output ordering will be same as the insertion oder
System.out.println(arrayList1); // [b, a, f, e, null, a, d, c]

Update elements in ArrayList

You can use the following methods to update elements in ArrayList

  • Use set(index, element) to update an element at the specified index in ArrayList. IndexOutOfBoundsException and NullPointerException will be thrown if the specified index is out of range and the specified element is null, respectively
  • Use replaceAll(UnaryOperator) to update multiple elements with the result returned from the specified UnaryOperator lambda function
List<String> arrayList1 = new ArrayList<>(List.of("a", "b", "c"));
arrayList1.set(0, "aa");
arrayList1.set(3, "d"); // throws IndexOutOfBoundsException
arrayList1.set(0, null); // throws NullPointerException

List<String> arrayList2 = new ArrayList<>(List.of("a", "b", "c"));
arrayList2.replaceAll(e -> e + "b");
System.out.println(arrayList2); // [ab, bb, cb]

Remove elements from ArrayList

You can use the following methods to remove elements from ArrayList

  • Use remove(element) to remove the first occurrence of the specified element
  • Use remove(index) to remove the element at the specified index
  • Use removeAll(Collection) to remove all occurrences of the specified collection
  • Use removeIf(Predicate) to remove all occurrences of elements that satisfy the specify predicate lambda function
List<String> arrayList1 = new ArrayList<>(List.of("a", "b", "c", "b"));
System.out.println(arrayList1.remove("b")); // true
System.out.println(arrayList1); // [a, c, b]

System.out.println(arrayList1.remove(0)); // a
System.out.println(arrayList1); // [c, b]

List<String> arrayList2 = new ArrayList<>(List.of("a", "b", "c", "b"));
System.out.println(arrayList2.removeAll(Arrays.asList("b", "d"))); // true
System.out.println(arrayList2); // [a, c]

List<String> arrayList3 = new ArrayList<>(List.of("a", "b", "c", "b"));
System.out.println(arrayList3.removeIf(e -> e.equals("b"))); // true
System.out.println(arrayList3); // [a, c]

Get and examine elements in ArrayList

You can use the following methods to filter, retrieve and examine elements from ArrayList

  • Use stream().filter(Predicate) to filter and retrieve multiple elements that satisfy the specified predicate lambda function
  • Use subList(fromIndex, toIndex) to retrieve multiple elements from an index range between fromIndex (inclusive) and toIndex (exclusive)
  • Use get(int) to retrieve a single element at the specified index. IndexOutOfBoundsException will be thrown if the specified index is out of range
  • Use contains(element) to examine if the specified element is existing
  • Use containsAll(Collection) to examine if the specified collection of elements are existing
List<Integer> arrayList1 = new ArrayList<>(List.of(3, 1, 2, 5));

Integer[] arr = arrayList1.stream().filter(e -> e >= 2).toArray(Integer[]::new);
System.out.println(Arrays.toString(arr)); // [3, 2, 5]

System.out.println(arrayList1.subList(0, 2)); // [3, 1]

System.out.println(arrayList1.get(3)); // 5

System.out.println(arrayList1.contains(1)); // true

System.out.println(arrayList1.containsAll(Arrays.asList(1, 6))); // false
System.out.println(arrayList1.containsAll(Arrays.asList(1, 5))); // true

Iterate over an ArrayList

You can use the following ways to iterate over an ArrayList

  • Use index-loops (while, do-while, for-index) to iterate forward and backward
  • Use for-each loop to iterate forward
  • Use forEach(Consumer) method to iterate forward
  • Use iterator() method to iterate forward, listIterator() method to iterate backward
import java.util.*;

public class ArrayListIterationExample {
    public static void main(String[] args) {
        List<Integer> arrayList1 = new ArrayList<>(List.of(1, 2, 3));

        // Iterate forward with for-index loop
        for(int i=0; i<arrayList1.size(); i++){
            int ele = arrayList1.get(i);
            System.out.printf("%d ", ele); // 1 2 3
        }

        System.out.println();

        // Iterate backward with for-index loop
        for(int i=arrayList1.size()-1; i>=0; i--){
            int ele = arrayList1.get(i);
            System.out.printf("%d ", ele); // 3 2 1
        }

        System.out.println();

        // Iterate forward with for-each loop
        for(int ele : arrayList1){
            System.out.printf("%d ", ele); // 1 2 3
        }

        System.out.println();

        // Iterate forward with forEach(Consumer) method
        arrayList1.forEach(e -> System.out.printf("%d ", e)); // 1 2 3

        System.out.println();

        // Iterate forward with iterator() method
        Iterator<Integer> iterator = arrayList1.iterator();
        while (iterator.hasNext()){
            System.out.printf("%d ", iterator.next()); // 1 2 3
        }

        System.out.println();

        // Iterate backward with listIterator() method
        ListIterator<Integer> listIterator = arrayList1.listIterator(arrayList1.size());
        while (listIterator.hasPrevious()){
            System.out.printf("%d ", listIterator.previous()); // 3 2 1
        }
    }
}

ArrayList of user-defined objects

You can create an ArrayList of any reference data types including primitive wrapper (Integer, Long, Boolean, Character, etc), String, collections (List, Set, Map) and user-defined

Your user-defined classes should implement equals(Object) and hashCode() methods as ArrayList operations work based on them. By default, primitive wrappers, String, date time and collections classes have implemented

import java.util.*;

public class ArrayListUserDefinedObjectsExample {
    public static void main(String[] args) {
        Book book1 = new Book(1, "Spring Boot In Practice");
        Book book2 = new Book(2, "Algorithms Bootcamp");
        List<Book> books = new ArrayList<>();
        books.add(book1);
        books.add(book2);

        System.out.println(books.contains(book1)); // true
        System.out.println(books.contains(book2)); // true
    }
}

class Book {
    private final int id;
    private final String title;

    public Book(int id, String title) {
        this.id = id;
        this.title = title;
    }

    public int getId() {
        return id;
    }

    public String getTitle() {
        return title;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Book book = (Book) o;
        return id == book.id &&
            Objects.equals(title, book.title);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, title);
    }
}

Sort an ArrayList

You can use the following methods to sort an ArrayList

  • Use Collections.sort(ArrayList) to sort the specified ArrayList in natural order
  • Use Collections.sort(ArrayList, Comparator.reverseOrder()) to sort the specified ArrayList in reverse order
  • Use ArrayList.sort(Comparator.naturalOrder()) to sort the caller ArrayList in natural order
  • Use ArrayList.sort(Comparator.reverseOrder()) to sort the caller ArrayList in reverse order

The natural order is defined by the compareTo() of Comparable implementation, default is in

  • Ascending order in primitive wrapper classes (Integer, Long, etc)
  • Chronological order in date time classes (Date, LocalDate, etc)
  • Alphabetical order in String class
import java.util.*;

public class ArrayListSortExample {
    public static void main(String[] args) {
        List<Integer> arrayList1 = new ArrayList<>(List.of(3, 1, 2));

        // sort an ArrayList of numbers in ascending order with Collections.sort()
        Collections.sort(arrayList1);
        System.out.println(arrayList1); // [1, 2, 3]

        // sort an ArrayList of numbers in descending order with Collections.sort()
        Collections.sort(arrayList1, Comparator.reverseOrder());
        System.out.println(arrayList1); // [3, 2, 1]

        List<String> arrayList2 = new ArrayList<>(List.of("c", "a", "b"));

        // sort an ArrayList of Strings in alphabetical order with sort()
        arrayList2.sort(Comparator.naturalOrder());
        System.out.println(arrayList2); // [a, b, c]

        // sort an ArrayList of Strings in reverse alphabetical order with sort()
        arrayList2.sort(Comparator.reverseOrder());
        System.out.println(arrayList2); // [c, b, a]

        LocalDate ld1 = LocalDate.of(2019, 3, 1);
        LocalDate ld2 = LocalDate.of(2019, 1, 1);
        LocalDate ld3 = LocalDate.of(2019, 2, 1);
        List<LocalDate> arrayList3 = new ArrayList<>(List.of(ld1, ld2, ld3));

        // sort an ArrayList of Dates in chronological order
        arrayList3.sort(Comparator.naturalOrder());
        System.out.println(arrayList3); // [2019-01-01, 2019-02-01, 2019-03-01]

        // sort an ArrayList of Dates in reverse chronological order
        arrayList3.sort(Comparator.reverseOrder());
        System.out.println(arrayList3); // [2019-03-01, 2019-02-01, 2019-01-01]
    }
}

Array vs ArrayList

  • Array is a fixed size (you have to specify the size explicitly at the declaration) while ArrayList is a resizable array data structure implementation (the size parameter is optional)
int[] arr = new int[2];
arr[0] = 1;
arr[1] = 2;
arr[2] = 3; // throws ArrayIndexOutOfBoundsException

List<Integer> lst = new ArrayList<>();
lst.add(1);
lst.add(2);
lst.add(3); // does not throw any exceptions
  • Array can hold both primitive and reference type elements while ArrayList can hold only reference type elements
List<int> lst = new ArrayList<>(); // compile error
  • Arrays are covariant (if S is a subtype of T, then S[] is also a subtype of T[]) and provides run-time type safety while ArrayLists, through Generics, are invariant and provides compile time type safety (so ArrayLists are more safety than Arrays)
Object[] arr = new String[3];  // covariant
arr[0] = "a"; // no errors at this line
arr[1] = 1; // this line throws ArrayStoreException at runtime

List<Object> lst = new ArrayList<String>(); // invariant, compile error: incompatible types  
lst.add("a");
Follow HelloKoding