Link

Lists

The List interface is the base interface for collections which allows to store an ordered collection of elements in a resizable container.

Table of contents

  1. Create Lists
  2. Vector
  3. ArrayList
  4. LinkedList
  5. Which list to use?
  6. Double brace initialization
  7. Mutable and Immutable Lists

Create Lists

There are several ways how a list can be created. Following is an example of how a list be can created from an array, using the Arrays class.

package demo;

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

public class App {
  public static void main( final String[] args ) {
    final List<String> a = Arrays.asList( "a", "b", "c" );
    System.out.printf( "List %s%n", a );
  }
}

Java 9 added static methods to the List interface List.of()

package demo;

import java.util.List;

public class App {
  public static void main( final String[] args ) {
    final List<String> a = List.of( "a", "b", "c" );
    System.out.printf( "List %s%n", a );
  }
}

Output

List [a, b, c]

Vector

Vector uses an array internally as data structure. They are dynamically resizable. By default, Vector doubles the size of its array when its size is increased.

package demo;

import java.util.List;
import java.util.Vector;

public class App {
  public static void main( final String[] args ) {
    final List<String> a = new Vector<>();
    a.add( "b" );
    a.add( "c" );

    /* Add at a given existing location */
    a.add( 0, "a" );

    System.out.printf( "List %s%n", a );
  }
}

When initialising the Vector, it is best to provide an indication of the Vector’s size. This enables the Vector to create an array once and mitigate the need of array resize.

package demo;

import java.util.List;
import java.util.Vector;

public class App {
  public static void main( final String[] args ) {
    final List<String> a = new Vector<>( 3 );
    a.add( "b" );
    a.add( "c" );

    /* Add at a given existing location */
    a.add( 0, "a" );

    System.out.printf( "List %s%n", a );
  }
}

Both examples will produce the same output.

List [a, b, c]

The Vector’s methods are syncronised. This provides a thread-safety.

ⓘ NoteWhile each individual method is thread-safe and atomic, a gaurd is needed when wworking with multiple methods.

ArrayList

ArrayList uses Array internally as data structure. They are dynamically resizable. By default, ArrayList increases by half of its size when its size is increased.

package demo;

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

public class App {
  public static void main( final String[] args ) {
    final List<String> a = new ArrayList<>( 3 );
    a.add( "b" );
    a.add( "c" );

    /* Add at a given existing location */
    a.add( 0, "a" );

    System.out.printf( "List %s%n", a );
  }
}

Output

List [a, b, c]

LinkedList

LinkedList is implemented as a double linked list.

LinkedList

package demo;

import java.util.LinkedList;
import java.util.List;

public class App {
  public static void main( final String[] args ) {
    final List<String> a = new LinkedList<>();
    a.add( "b" );
    a.add( "c" );

    /* Add at a given existing location */
    a.add( 0, "a" );

    System.out.printf( "List %s%n", a );
  }
}

Output

List [a, b, c]

Which list to use?

Array based collections are faster and take less space when compare to linked list based. Linked list has higher overheads per item when compared to arrays based. The only one place linked list out performs the array is FIFO queue. Iterating is faster with arrays as items in the array are close to each other. ArrayList is slow when we need to add or remove elements as we need to shift things down.

Technically, Stack and other classes also implement the List interface and provide the list features you expect. However, they are meant for other purposes, and their use as a list is therefore discouraged.

Double brace initialization

Consider the following example.

package demo;

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

public class App {
  public static void main( final String[] args ) {
    final List<String> a = new ArrayList<>() { {
      add( "a" );
      add( "b" );
      add( "c" );
    } };
    System.out.printf( "List %s%n", a );
  }
}

The above example makes use of double brace initialization. An inner anonymous class is created and the init block is used to add the elements to the list. The above example is similar to the following.

package demo;

import java.util.ArrayList;

public class MyStringList extends ArrayList<String> {

  /* Initialisation block */
  {
    add( "a" );
    add( "b" );
    add( "c" );
  }
}

I’ve never used this pattern and prefer other constructs instead, such as List.of() or Guava.’s Lists.asList(). I’ve added this example here as you may encounter this in code.

Mutable and Immutable Lists

Unmodifiable lists cannot be modified

⚠ The following example will compile but will throw UnsupportedOperationException!!
package demo;

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

public class App {
  public static void main( final String[] args ) {
    final List<String> a = new ArrayList<>( 3 );
    a.add( "a" );
    a.add( "b" );
    a.add( "c" );

    /* Cannot modify the list through b */
    final List<String> b = Collections.unmodifiableList( a );

    /* Throws UnsupportedOperationException */
    b.add( "d" );
  }
}

Changing the unmodifiable list will throw an UnsupportedOperationException.

Exception in thread "main" java.lang.UnsupportedOperationException
  at java.base/java.util.Collections$UnmodifiableCollection.add(Collections.java:1062)
  at demo.App.main(App.java:18)

Changes to the underlying list will also affect the immutable list

package demo;

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

public class App {
  public static void main( final String[] args ) {
    final List<String> a = new ArrayList<>( 3 );
    a.add( "a" );
    a.add( "b" );
    a.add( "c" );

    /* Cannot modify the list through b */
    final List<String> b = Collections.unmodifiableList( a );

    /* The immutable list b will be modified too */
    a.add( "d" );

    System.out.printf( "List a: %s%n", a );
    System.out.printf( "List b: %s%n", b );
  }
}

Output

List a: [a, b, c, d]
List b: [a, b, c, d]