A list is a data structure that stores an ordered collection of items. Each item in a list is called an element, and a list may have duplicate elements. In Java, the high level methods for manipulating data in a list are defined by the java.util.List interface. Classes under the java.list package such as ArrayList, LinkedList and Stack provide concrete implementations of the List interface.

Creating a list in Java

To initialize a list, we need to instantiate an object of the concrete implementation classes such as ArrayList, LinkedList, or Stack. Since these classes need to store a specific data type as their elements, we also need to specify the stored data type such as an Integer or String in angle brackets (<>).

For example, to initialize an ArrayList or Stack that stores integers, we would write:

ArrayList<Integer> list1 = new ArrayList<Integer>();
Stack<Integer> list2 = new Stack<Integer>();

In the above example, we've mentioned the stored data type (Integer) on both sides of the assignment operator (=). Mentioning the stored data type on the right side is optional, so we can write this instead:

ArrayList<Integer> list1 = new ArrayList<>();
Stack<Integer> list2 = new Stack<>();

Since ArrayList, LinkedList and Stack implement the List interface, we can also mention the List type on the left side of the assignment instead of the specific class, like this:

List<Integer> list1 = new ArrayList<Integer>();
List<Integer> list2 = new Stack<Integer>();

However, some classes such as Stack implement additional methods such as push() and pop(), which can't be accessed if we instantiate it this way. This is because the methods such as push() and pop() are not part of the List interface.

Java provides both primitive data types such as int, double and char, and class data type equivalents such as Integer, Double and Character. We can only use the class data types, therefore statements such as this one are invalid:

ArrayList<int> list1 = new ArrayList<int>(); // WRONG

Adding elements to the end of a list

To add a single element to the end of a list, we can use the add() method. In the example below, we instantiate an ArrayList and add three strings, and then print the contents of the list.

ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");

System.out.println(list); // prints: [a, b, c]

In addition to the add() method, the Stack class method also provides a push() method to add elements at the end:

Stack<String> stack = new Stack<>();
stack.push("a");
stack.push("b");
stack.push("c");

System.out.println(stack); // prints: [a, b, c]

Getting the size of a list

We can get the size of a list by using the size() method. The snippet below demonstrates the use of this method - there are three elements in the list, and therefore we get 3 as the output.

ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");

int sizeOfList = list.size();
System.out.println(sizeOfList); // prints: 3

To check if a list is empty, we can use the isEmpty() method, as shown below:

ArrayList<String> list = new ArrayList<>();
System.out.println(list.isEmpty()); // prints: true

list.add("a");
list.add("b");
System.out.println(list.isEmpty()); // prints: false

Adding elements to a list at a given index

To allow adding elements to the beginning or a middle of a list, the add() method has a second form where it accepts the index of insertion, and the element to be inserted.

In the following example, we insert the strings "a", "b", and "c" in that order, and then we insert "d" at index 0, at the beginning. Next, we add "e" between "d" and "a", at index 1. The list now has 5 elements. We insert "f" at the end by specifying the index 5.

ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
System.out.println(list); // prints: [a, b, c]

list.add(0, "d"); // insert d at the beginning
System.out.println(list); // prints: [d, a, b, c]

list.add(1, "e"); // insert "e" between "d" and "a"
System.out.println(list); // prints: [d, e, a, b, c]

list.add(5, "f"); // insert "f" at the end
System.out.println(list); // prints: [d, e, a, b, c, f]

If the index is negative or greater than the size of the of the list, a IndexOutOfBoundsException is thrown. This is shown in the next example, where we initially add the strings "a" and "b" to the list, and then try to insert an element at index 3. Since 3 is greater than the size of the array, it throws an exception which is handled in the try-catch block and the error message "could not insert" is printed.

ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");

// since index 3 is invalid, it prints: could not insert
try {
  list.add(3, "c");
} catch (IndexOutOfBoundsException e) {
  System.out.println("could not insert");
}

Adding elements from a separate list

We can add elements from a list into another list using the addAll() method. In this example, we have two lists, list1 and list2, and we add the elements from list2 to the end of list1.

ArrayList<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");

ArrayList<String> list2 = new ArrayList<>();
list2.add("c");
list2.add("d");

list1.addAll(list2); // add the elements in list2 to the end of list1
System.out.println(list1); // prints: [a, b, c, d]

Similar to the add() method, we can use the addAll() method with an index to add elements to the beginning, middle or the end. In this example, we add the elements from list2 into list1 between "a" and "b", at index 1.

ArrayList<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");

ArrayList<String> list2 = new ArrayList<>();
list2.add("c");
list2.add("d");

list1.addAll(1, list2);
System.out.println(list1); // prints: [a, c, d, b]

Getting an element at a given index

The get() method can be used to retrieve an element from the list using an index. In the following program, we insert three strings into the list, and then retrieve the element at index 1, which is "b".

ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");

String element = list.get(1);
System.out.println(element); // prints: b

If we provide an invalid index to this method, it throws an IndexOutOfBoundsException. In this example, we try to fetch the element at index 3, which is invalid. The list only has three elements, so only the indexes 0, 1 and 2 are valid. The exception is handled with a try-catch block which leads the program to print the message "invalid index".

ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");

// since the index 3 is invalid, it prints: invalid index
try {
  String element = list.get(3);
  System.out.println(element);
} catch (IndexOutOfBoundsException e) {
  System.out.println("invalid index");
}

Removing elements at a given index

We can remove an element based on its index through the remove() method. In this example, we insert the integers 10, 20, 30 and 40 into the list. Then, we remove the elements at index 1, which is 20, and are left with 10, 30 and 40.

ArrayList<Integer> list = new ArrayList<Integer>();
list.add(10);
list.add(20);
list.add(30);
list.add(40);
System.out.println(list); // prints [10, 20, 30, 40]

list.remove(1);
System.out.println(list); // prints: [10, 30, 40]

If we specify an invalid index, we get an IndexOutOfBoundsException. In the program below, we try to remove the 4th index, which is invalid, as the list only has two elements. We handle the exception with a try-catch block, and print the message "could not remove".

ArrayList<Integer> list = new ArrayList<>();
list.add(10);
list.add(20);

// since the index 4 is invalid, it prints: could not remove
try {
  list.remove(4);
} catch (IndexOutOfBoundsException e) {
  System.out.println("could not remove");
}

Removing elements based on comparison

We can remove a specific element based on object equality through the remove() method. To remove an element in this form of the remove method, the entire list is traversed and the first occurrence is removed.

In this example, we insert the strings "a", "b", "c", "d" and "c" into a list. Next, we remove the string "c" from it. Since there are two "c" strings in the list, only the first one is removed, and we're left with "a", "b", "d" and "c". Next, we remove the string "e". Since it is not present in the list, nothing happens, and the list still has the same four elements.

ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("c");
System.out.println(list); // prints: [a, b, c, d, c]

list.remove("c"); // removes the first occurrence
System.out.println(list); // prints: [a, b, d, c]

list.remove("e"); // no matching element, so nothing happens
System.out.println(list); // prints: [a, b, d, c]

In the case of a list that stores Integer values, we can't call this version of the function directly. This is because, passing in an integer value would call the index-based version of remove(). If we try to remove the element 1 by passing it directly, we would end up removing the 2 instead, as 2 is located at index 1 of the list.

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

// it would remove the element at index 1, which is 2.
list.remove(1);
System.out.println(list); // prints: [1, 3]

As mentioned earlier, the List interface works with the class-based versions of the primitive types such as Integer instead of int. Therefore, to call the comparison-based version of remove() on a list that stores Integers, we should cast the argument of remove() to an Integer object by calling Integer.valueOf(). As shown below, we are able to remove 1 from the list through this method:

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

list.remove(Integer.valueOf(1)); // removes 1 from the list
System.out.println(list); // prints: [2, 3]

Removing multiple elements from a list

If we have multiple items to remove based on object equality, we can use the removeAll() method. Unlike remove(), removeAll() removes all occurrences of the element in the list.

In the program below, we have two lists list1 and list2, and we remove all the items in list1 which are present in list2, leaving us with "c" and "d" in list1.

ArrayList<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
list1.add("a");
list1.add("c");
list1.add("d");
System.out.println(list1); // prints: [a, b, a, c, d]

ArrayList<String> list2 = new ArrayList<>();
list2.add("a");
list2.add("b");

list1.removeAll(list2);
System.out.println(list1); // prints: [c, d]

On the other hand, if we want to remove all other elements from list1 except for the ones in list2, we would use the retainAll() method. In our example, only "a", "b" and "a" are retained in list1, and the rest are discarded.

ArrayList<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
list1.add("a");
list1.add("c");
list1.add("d");
System.out.println(list1); // prints: [a, b, a, c, d]

ArrayList<String> list2 = new ArrayList<>();
list2.add("a");
list2.add("b");

list1.retainAll(list2);
System.out.println(list1); // prints: [a, b, a]

Removing elements from a list based on a condition

To remove elements based on a condition, we can use the removeIf() method. It takes in a static method or a lambda expression that returns a boolean value. The static method or lambda expression is called with each element of the list, and the elements for which the lambda expression returned true are removed.

For example, if a list has even and odd integers and we wanted to remove all the odd integers, we would use the lambda expression x -> x % 2 == 1 as the argument of removeIf(). The lambda expression returns true when x is odd, and false when x is even.

ArrayList<Integer> list = new ArrayList<>();
list.add(2);
list.add(3);
list.add(6);
list.add(7);

list.removeIf(x -> x % 2 == 1);
System.out.println(list); // prints: [2, 6]

If we want to use a static method instead, we need to use the syntax <className>::<methodName>, where methodName is the name of the static method, and className is the class inside which the static method has been implemented. For example, if we assume our methods are enclosed in a class named Main, we can write a program like this:

public static boolean isOdd(Integer x) {
  return x % 2 == 1;
}

public static void main(String[] args) {
  ArrayList<Integer> list = new ArrayList<>();
  list.add(2);
  list.add(3);
  list.add(6);
  list.add(7);

  // we have assumed a class named "Main", so we have referred to isOdd as "Main::isOdd"
  list.removeIf(Main::isOdd);
  System.out.println(list); // prints: [2, 6]
}

The removeIf() method returns true if at least one element was removed from the list, and false otherwise.

Checking if an element is present in a list

We can check if an element is present in a list by using the contains() method. It returns true if the element is present, and false otherwise, as demonstrated by this example below:

ArrayList<Integer> list = new ArrayList<>();
list.add(2);
list.add(3);
list.add(6);

// 5 is not in the list, so it prints: 5 is not in the list
if (list.contains(5)) {
  System.out.println("5 is in the list");
} else {
  System.out.println("5 is not in the list");
}

If we want to check if all items of a list are present in another list, we can use the containsAll() method. In the following program, list1 has the elements 10, 20 and 30, and list2 has the elements 20 and 30. Since all the elements of list2 are present in list1, containsAll() returns true:

ArrayList<Integer> list1 = new ArrayList<>();
list1.add(10);
list1.add(20);
list1.add(30);

ArrayList<Integer> list2 = new ArrayList<>();
list2.add(20);
list2.add(30);

// all elements of list2 are contained in list1, so it
// prints: list2 is contained in list1
if (list1.containsAll(list2)) {
  System.out.println("list2 is contained in list1");
} else {
  System.out.println("list2 is not contained in list1");
}

Searching for an element in a list

We've seen that the contains() method tells us whether an element is present in the list or not. However, if we want to know the exact index at which the element at which the element is present, the List interface provides two methods named indexOf() and lastIndexOf().

The indexOf() method tells us the first index at which the given element occurs, and lastIndexOf() tells us the last index at which the element occurs. If the element wasn't found, it returns -1.

In the program below, we add numbers to the list, and use the indexOf() and lastIndexOf() methods to find their index. Since there is only one occurrence of 10, the first occurrence is also the last occurrence, so we get the same result from both methods. On the other hand, there are three occurrences of 20, thus we get different results for indexOf() and lastIndexOf(). Finally, there is no occurrence of 30, so, we get -1 from both methods.

ArrayList<Integer> list = new ArrayList<Integer>();
list.add(10);
list.add(20);
list.add(40);
list.add(20);
list.add(60);
System.out.println(list); // prints: [10, 20, 40, 20, 60]

System.out.println(list.indexOf(10)); // prints: 0
System.out.println(list.lastIndexOf(10)); // prints: 0

System.out.println(list.indexOf(20)); // prints: 1
System.out.println(list.lastIndexOf(20)); // prints: 3

System.out.println(list.indexOf(30)); // prints: -1
System.out.println(list.lastIndexOf(30)); // prints: -1

Replacing an element at a given index in a list

We can use the set() method replace an existing element in a list based on its index. It takes two arguments - the index at which the replacement should be done, and the object that should be replaced.

In this example, we add the strings "a" through "c" to the list, and then replace the element at index 1 ("b") with the string "d". The list contains the elements "a", "d" and "c" after the replacement.

ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");

list.set(1, "d");
System.out.println(list); // prints: [a, d, c]

The set() method returns the original element that was present in the list at that index. Thus, it can be used to fetch the old element during replacement, like so:

ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");

String oldElement = list.set(1, "d");
System.out.println(oldElement); // prints: b

Despite its name, the set() method can only be used to replace existing elements and it can't be used to add elements to the list. If an invalid index is given, the method throws IndexOutOfBoundsException. In this example, calling the set() on the index 3 causes an exception to be thrown, since the list doesn't have an element at index 3. We handle the exception with a try-catch block and print an error.

ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");

// since the index 3 is invalid, it prints: invalid index
try {
  list.set(3, "d");
} catch (IndexOutOfBoundsException e) {
  System.out.println("invalid index");
}

Clearing a List

To remove all the elements of a list, we use the clear() method. The program below demonstrates its use:

ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");

System.out.println(list); // prints: [a, b, c]
System.out.println(list.size()); // prints: 3

list.clear();
System.out.println(list); // prints: []
System.out.println(list.size()); // prints: 0

Reversing a list

The java.util.Collections package provides a reverse() method which can be used to reverse the elements of a list. The following program shows how we can use it:

ArrayList<Integer> list = new ArrayList<Integer>();
list.add(10);
list.add(20);
list.add(30);
System.out.println(list); // prints: [10, 20, 30]

Collections.reverse(list);
System.out.println(list); // prints: [30, 20, 10]

Sorting a list

To sort the elements of a list, we need to call the sort() method with a static method or a lambda expression that determines how the elements should be sorted. The method or lambda expression should take two arguments a and b of the same data type which has been stored in the list, and it should return an integer, based on the following rules:

  • 0, if a == b
  • > 0, if a > b
  • < 0, if a < -b

For example, if we have a list of integers, and we wanted to sort them in ascending order, we can call sort() on the list with a lambda expression that implements this logic.

ArrayList<Integer> list = new ArrayList<>();
list.add(5);
list.add(10);
list.add(3);
list.add(4);

list.sort((a, b) -> {
  if (a == b) {
    return 0;
  }
  if (a > b) {
    return 1;
  }
  return -1;
});
System.out.println(list); // prints: [3, 4, 5, 10]

As mentioned in removing elements from a list based on a condition, if we want to use a static method, we need to use the <className>::<methodName> syntax.

In the above example, we saw how we could implement the sorting logic ourselves, however Java already provides this functionality as part of the compareTo() static method in the java.lang.Integer class. Therefore, we can simplify our program by calling that method instead, as shown:

ArrayList<Integer> list = new ArrayList<>();
list.add(5);
list.add(10);
list.add(3);
list.add(4);

list.sort(Integer::compare);
System.out.println(list); // prints: [3, 4, 5, 10]

If we wanted to sort the list of numbers in descending order, then we simply need to invert the rules, by returning a number less than 0 when a > b, and a number greater than 0 when a < b. This is demonstrated in the example below:

ArrayList<Integer> list = new ArrayList<>();
list.add(5);
list.add(10);
list.add(3);
list.add(4);

list.sort((a, b) -> {
  if (a == b) {
    return 0;
  }
  if (a > b) {
    return -1;
  }
  return 1;
});
System.out.println(list); // prints: [10, 5, 4, 3]

Iterating over a list with a for-each loop

Sometimes, we may need to iterate or traverse over the elements of an list in sequential order, or in other words, from the first element to the last. The simplest way to do so is to use the for-each loop provided in Java.

In the example below, we calculate the sum of all integers in a list by iterating through it using the for-each loop. The for-each loop goes over the elements sequentially, and in each iteration, it stores the next value from the list in the i variable. Once all the elements have been iterated over, the loop is terminated.

ArrayList<Integer> list = new ArrayList<>();
list.add(10);
list.add(20);
list.add(30);

int sum = 0;
for (Integer i: list) {
  sum += i;
}

System.out.println(sum); // prints: 60

Iterating over a list with an iterator

The Iterator class provides another way to iterate over the elements of an array. To use this method, we begin by obtaining an iterator object by calling the iterator() method on the list. The Iterator class provides two methods which are useful to iterate over the array:

  • The next() method retrieves elements sequentially whenever it is called.
  • The hasNext() method checks if there are further elements that can be returned. So, when we have retrieved all elements by calling next() repeatedly, hasNext() returns false. Otherwise, if we have not retrieved all the elements by calling next(), there are more elements to retrieve and hasNext() returns true.

We can use these methods to iterate over the elements. Similar to the previous example, we calculate the sum of all integers in a list, but we use iterators in this example:

ArrayList<Integer> list = new ArrayList<>();
list.add(10);
list.add(20);
list.add(30);

int sum = 0;
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
  sum += it.next();
}

System.out.println(sum); // prints: 60
  • We initially begin at index 0 of the list. Since we have not retrieved any elements from the iterator, hasNext() returns true. We retrieve 10 by calling next() and add it to sum.
  • Then, we call hasNext() again as part of the while loop. Since we have not iterated through all the elements, hasNext() still returns true. Now, we retrieve 20 by calling next() and add it to sum. A similar process for the next element, 30.
  • Finally, we have iterated through all the elements. Thus, hasNext() returns false, terminating the loop. At the end, we print the sum.

Iterating over a list with the forEach() method

Sometimes, we may need to call a function on every element of the array. For example, to concatenate strings stored in a list, we may initialize a StringBuilder and call the append() method with each element, like so:

ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");

StringBuilder sb = new StringBuilder();
for (String i: list) {
  sb.append(i);
}

String s = sb.toString();
System.out.println(s); // prints: abc

Here we're only calling the sb.append() for each element. Fortunately, the List interface provides a forEach() method that can be used in such a situation without using a for loop or iterator. The forEach() method takes a lambda expression or a static method. The provided lambda expression or static method should accept the same data type as the elements of the list.

We can rewrite the above example using forEach() by passing the sb.append method to it.

ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");

StringBuilder sb = new StringBuilder();
list.forEach(sb::append);

String s = sb.toString();
System.out.println(s); // prints: abc

Converting a list to an array

To convert a list to an array, we can use the toArray() method. The method returns an array of the java.lang.Object type, so we have to cast the elements of the array before being able to use them.

In the example below, first, we obtain an array by using toArray(). We then get the first element of the array, but since it is of the Object type, we cast it to an Integer by using (Integer) array[0] and store it in the variable x. Next, we increment x by 5.

ArrayList<Integer> list = new ArrayList<>();
list.add(10);
list.add(20);
list.add(30);

Object[] array = list.toArray();
Integer x = (Integer) array[0];
x += 5;

System.out.println(x); // prints 15

If we did not cast array[0] to Integer, we wouldn't have been able to perform the increment operation. Increments are not allowed on the Object type.