Programming 3

University of Alicante, 2026–2027

Easy Guide to Using the Java Collection Framework

Java Collection Framework is how the container class library of Java, found in the standard java.util package, is known. These classes serve to store collections of objects, such as lists, sets, maps, …

All of these classes allow storing references to objects in them. For example:

// List of integers. There can be repeated integers in the list:
List<Integer> list_of_apples;  

// Set of integers. There cannot be repeated integers:
Set<Integer> set_of_oranges;

// A map that associates a string with an integer, as in an exam grade list:
//    [("Juan Goytisolo", 9.5), ("Pablo Iglesias", 5.0), ...]
Map<String, Integer> map_of_grades;

Thanks to boxing/unboxing, we can also use them with primitive types (int, double, etc.). However, we must define them using the classes that represent these primitive types (Integer, Double, etc.):

List<Integer> list_of_integers;  // here we can directly store and retrieve 'int' values.

Let’s see how these object collections work. You can find most of the code on this page in the file GuiaFacilJCF.java.

Lists

We call a list any collection of objects ordered by position, as in an array. In a list, we can add elements, access them by their position in the list, remove elements from the list, and perform other operations, such as emptying the list, copying it, etc. In a list, there can be duplicate objects, that is, objects that are equal according to the equals() method of their class.

Creating a list

Let’s create a list of objects of type Integer:

List<Integer> lista_de_enteros = new ArrayList<Integer>();

The reason why we use ‘new ArrayList’ and no ‘new List’ will become clear when we talk about polymorphism and interfaces.

Since version 1.7 of Java, the type of element in the list can be omitted in the new statement:

List<Integer> lista_de_enteros = new ArrayList<>();

The compiler is responsible for performing what is called type inference, inferring from the compile-time type of lista_de_enteros what the type of its elements is and therefore what type must go between <> (<> is known as the diamond operator in Java jargon)[.

Adding elements to the list

The add(·) method adds a reference to an object at the end of the list:

lista_de_enteros.add(new Integer(4));
lista_de_enteros.add(new Integer(5));
lista_de_enteros.add(new Integer(7));
lista_de_enteros.add(2,new Integer(6)); // adds it at position 2, between 5 and 7
lista_de_enteros.add(4); // add 4 again, using 'boxing'
// this last line is equivalent to: lista_de_enteros.add(new Integer(4));

Size of a list

Use size() to know the size of a list:

int tam = lista_de_enteros.size();

returns 5: the list contains 5 elements.

Getting elements from a list

The get(int) method serves to access any element of the list, by its position:

Integer primero = lista_de_enteros.get(0);
int primer = lista_de_enteros.get(0); // 'unboxing', directly getting an 'int'.

returns the object at position 0, that is, the first of those I added.

Integer ultimo = lista_de_enteros.get(lista_de_enteros.size()-1);

returns the last element of the list.

Knowing if an element is in the list

Use the contains(·) method to ask the list if it contains the given object:

lista_de_enteros.contains(new Integer(7)); // will return 'true'
lista_de_enteros.contains(new Integer(8)); // will return 'false'

Getting the position of an object in the list

The indexOf(·) method tells me the position of the first occurrence of the object in the list (remember that the same object can be repeated in different positions):

lista_de_enteros.indexOf(new Integer(6)); // returns 2
lista_de_enteros.indexOf(new Integer(10)); // returns -1

Traversing the elements of the list

We can use a conventional loop:

for (int i=0; i < lista_de_enteros.size(); i++) {
  System.out.println(lista_de_enteros.get(i));
}

which would allow us to traverse only part of the list if we are interested (e.g., the first four elements).

With a for loop like this:

for (Integer entero : lista_de_enteros) {
  System.out.println(entero);
}

where the variable ‘entero’ of type Integer (it can also be of type ‘int’ in this case) takes the value of the next element of the list ‘lista_de_enteros’ in each iteration. With this type of loop, we traverse ALL elements of the list.

A somewhat more sophisticated, but more flexible way to do this is to use iterators:

Iterator<Integer> iterador = lista_de_enteros.iterator(); 
while (iterador.hasNext()) {
  Integer entero = iterador.next();
  System.out.println(entero); // prints an element
}

Notice that in the ‘while’ loop we could add more conditions to stop the loop where we are interested, which we cannot do with the previous ‘for’ loop.

Removing an object from the list

For this, we use the remove(·) method in two ways:

boolean quitado = lista_de_enteros.remove(new Integer(7)); // or remove(7)

removes the first occurrence of 7 in the list and returns ‘true’. If there is no 7, it would return ‘false’.

Integer unEntero = lista_de_enteros.remove(1);

removes the second element of the list and returns it.

NOTE: You might think that in this second call to remove() on a List, the Java compiler will perform boxing and call the first version of the method, but it does not and calls this second version, which accepts exactly one argument of type int (try it). On lists with another type of elements, this doubt would not arise.

Knowing if a list is empty or clearing it

This is easy:

boolean estaVacia = lista_de_enteros.isEmpty(); // will return 'false'
lista_de_enteros.clear(); // now it is indeed empty

Sets

We call a set any collection of objects of the same class without any particular order. Furthermore, each element appears only once, unlike in a list, where they could be repeated.

Creating a set

Let’s create a set of objects of type Integer:

Set<Integer> conjunto_de_enteros = new HashSet<Integer>();

Why ‘new HashSet’ and not ‘new Set’ will become clear when we talk about polymorphism and interfaces.

Adding elements to the set

The add(·) method adds a reference to an object to the set.

conjunto_de_enteros.add(new Integer(4));
conjunto_de_enteros.add(new Integer(5));
conjunto_de_enteros.add(new Integer(7));
boolean repe = conjunto_de_enteros.add(new Integer(4));
// does not add 4 because it is already in the set and also returns 'false'

(with all collections of the JCF we can use boxing/unboxing, so we will not comment on it further.)

Size of a set

conjunto_de_enteros.size();

returns 3: the set contains 3 elements.

Knowing if an element is in the set

conjunto_de_enteros.contains(new Integer(7)); // will return 'true'
conjunto_de_enteros.contains(new Integer(8)); // will return 'false'

A Set does not have the get(·) and indexOf(·) methods; elements are not in any particular position. Basically, with a set what we can do is add elements, remove them, and ask if an element belongs to the set.

Traversing all elements of the set

With a for loop like this:

for (Integer entero : conjunto_de_enteros) {
  System.out.println(entero);
}

This loop prints all the integers in the set, one on each line. The problem is that it does not traverse the set in any particular order. There is no defined order in the set.

Removing an object from the set

boolean quitado = conjunto_de_enteros.remove(new Integer(7));

returns ‘true’. If there is no 7, it would return ‘false’.

Knowing if a set is empty or clearing it

boolean estaVacio = conjunto_de_enteros.isEmpty(); // will return 'false'
conjunto_de_enteros.clear(); // now it is indeed empty

Lists and Sets == Collections

As you can observe, there are operations on lists and sets that are identical. And the fact is that both lists and sets are collections of objects and therefore share some operations. This is so because both classes are in turn of type Collection, so that we can do the following:

Collection<Integer> coleccion = lista_de_enteros;

// add an integer to the list
coleccion.add(new Integer(10));

coleccion = conjunto_de_enteros;

// add an integer to the set
coleccion.add(new Integer(11));

Notice that both integers are added to different collections: the first to the list, the second to the set, even though we use the same reference to refer to both collections. If you do not quite understand why it is like this, you will see it clearly when we study inheritance.

Queues

A queue is a collection of elements that are ‘enqueued’, so that the element inserted first is the element at the ‘head’ of the queue. There are three basic operations that can be done with a queue:

  • enqueue (or insert) : add an element to the end of the queue
  • examine : inspect the element that is at the head of the queue
  • dequeue : remove the element that is at the head of the queue

In general, we cannot access elements at any position in the queue, as in a list, but only the one at the head. In the same way, we cannot remove elements from the queue that are not at its head.

Creating a queue

Queue<Integer> cola = new LinkedList<>(); // LinkedList implements the Queue interface

Enqueuing, dequeuing, and examining the head of the queue

There are two ways to perform these operations with a queue: using methods that throw unchecked exceptions if they fail (for example, if we try to dequeue from an empty queue), or methods that return a special value (null or false) in case of error. In this table you have the operations of Queue that throw exceptions (throw) or return a value (return) if they fail:

throw return
enqueue add(e) offer(e)
dequeue remove() poll()
examine element() peek()

Enqueuing elements

// using add()
cola.add(5);
cola.add(6);

// using offer()
cola.offer(5);
cola.offer(6);

If there are no errors (for some reason we cannot enqueue more elements), both methods behave identically.

Examining the element at the head

cola.offer(5);
cola.offer(6);
int a = cola.peek(); // the element is not dequeued
a = cola.element();  // same operation

Dequeuing an element

cola.offer(5);
cola.offer(6);
int a = cola.poll(); // returns the head element and dequeues it
a = cola.remove(); // same operation

The usual operations that we can use with other collections, such as size(), clear(), isEmpty(), also work with queues.

Priority queues

They are a type of queue where elements are enqueued, but are ordered in the queue based on a criterion. So that at the head of the queue is the element with highest (or lowest) priority. There are two ways to define this ordering criterion: by making the elements of the list implement the Comparable interface, or by means of a Comparator object provided to the queue at creation time that implements the desired order.

For example, the Integer class implements the Comparable interface and therefore we can store integers in a priority queue, so that the smallest element among those inserted in the queue will occupy the head position:

PriorityQueue<Integer> cola = new PriorityQueue();
cola.add(5);
cola.add(2);
cola.add(3);
int a = cola.peek(); // returns 2
a = cola.poll(); // returns 2 and dequeues it
a = cola.peek(); // returns 3

If we want the integers to be ordered differently in the queue (for example, from largest to smallest), we must provide the queue with an integer comparator that orders them that way.

This is done by creating a class that implements the Comparator<T> interface, which tells us that we must implement the compare(a,b) method to compare two objects of type T ‘a’ and ‘b’, such that:

  • if a < b, then compare(a,b) returns a negative number.
  • if a.equals(b), then compare(a,b) returns zero.
  • if a > b, then compare(a,b) returns a positive number.

Continuing with the integer queue example, if we want them to be ordered ‘in reverse’ from largest to smallest, then we must invert the order making:

  • if a < b, then compare(a,b) returns a positive number.
  • if a.equals(b), then compare(a,b) returns zero.
  • if a > b, then compare(a,b) returns a negative number.

The following code does precisely this with a priority queue for integers:

Comparator<Integer> comparador = new Comparator<Integer>() {
  @Override
  public int compare(Integer a, Integer b) {
    return b - a;
  }
};

PriorityQueue<Integer> cola = new PriorityQueue(comparador);
cola.add(5);
cola.add(2);
cola.add(3);
int a = cola.peek(); // returns 5
a = cola.poll(); // returns 5 and dequeues it
a = cola.peek(); // returns 3

Maps

Maps allow establishing a correspondence between pairs of objects: one acting as a key and another as the value associated with that key. A dictionary, for example, is a map between text strings: the word we look up in the dictionary acts as a key and its meaning as the associated value.

A map in Java is defined like this:

Map<Key, Value> = new HashMap<Key, Value>();

where Key and Value are any two classes.

Thus, a dictionary would be implemented as a map between strings:

Map<String, String> diccionario = new HashMap<String, String>();

Another typical use of a map is one in which we want to ‘index’ something using not integer numbers as indices, but another type of object. For example, we could define a chessboard to play chess as a map between coordinates of the chessboard squares (A3, B7, …) and game pieces:

Map<CoordenadaAjedrez, PiezaAjedrez> damero = new HashMap<>();

The advantage of doing it this way is twofold:

  • we can use any type of object as an index (that is, as a key).
  • we do not need to store information in the map about squares that are not occupied.

If we implemented it as a two-dimensional array, the empty squares would occupy memory. But in chess, there are always more empty squares than occupied ones. In addition, we would have to index the array with integers, so we would have to ‘translate’ the coordinates somehow (A3 -> [0][2], B7 -> [1][6], …).

In any case, we could include empty squares in the map if that interests us, associating them, for example, with the value ‘null’.

Let’s see how we can work with maps:

Adding an entry to the map

The put(·) method adds a correspondence between a key and its value:

damero.put(new CoordenadaAjedrez('A',3),
           new PiezaAjedrez("ALFIL",Color.BLANCO));

damero.put(new CoordenadaAjedrez('B',7),
           new PiezaAjedrez("CABALLO",Color.NEGRO));

damero.put(new CoordenadaAjedrez('B',7),
           new PiezaAjedrez("REINA",Color.BLANCO));

The last instruction changes the piece associated with square B7 to a white queen. It is as if we had taken the black knight off the board and put the white queen in its place. The map therefore has 2 elements (key-value pairs) and not 3.

Notice that nothing prevents us from associating the same value with different keys:

damero.put(new CoordenadaAjedrez('F',4),
           new PiezaAjedrez("REINA",Color.BLANCO));
// now the map indeed has 3 key-value pairs

Or associating a key with a ‘null’ value:

damero.put(new CoordenadaAjedrez('A', 1), null);
// now the map has 4 elements

Size of a map

damero.size(); // returns 4

The map contains 4 entries: ([A3, white BISHOP], [B7, white QUEEN], [F4, white QUEEN], [A1, null]).

Getting the value associated with a key in the map

The get(·) method allows us to search in the map using a key:

PiezaAjedrez pieza = damero.get(new CoordenadaAjedrez('A',3));  

returns a reference to the bishop. If the key is not in the map, it will return ‘null’.

Knowing if a key is in the map

Use the containsKey(·) method to ask the map if the given key has any associated value:

damero.containsKey(new CoordenadaAjedrez('A',3)); // returns 'true'
damero.containsKey(new CoordenadaAjedrez('H',3)); // returns 'false'

Knowing if a value is associated with any key

Use the containsValue(·) method to ask the map if the given value is associated with at least one key:

damero.containsValue(new PiezaAjedrez("ALFIL",Color.BLANCO)); // returns 'true'
damero.containsValue(new PiezaAjedrez("REY",Color.NEGRO)); // returns 'false'

Traversing a map

Maps cannot be traversed directly, as we do with a list or a set. However, we can obtain the set of keys stored in the map, traverse this set, and ask the map for the value associated with each key:

Set<CoordenadaAjedrez> coordenadas = damero.keySet();

for (CoordenadaAjedrez coord : coordenadas) {
  PiezaAjedrez pieza = damero.get(coord);
  System.out.println(coord.toString() + " -> " + pieza.toString());
}

Removing an entry from the map

For this, we use the remove(·) method:

PiezaAjedrez pieza = damero.remove(new CoordenadaAjedrez('A',3));

Removes the entry associated with square A3 and returns the piece that was in that square. If the square were not in the map, it would return ‘null’.

Knowing if a map is empty or clearing it

This is easy:

boolean estaVacio = damero.isEmpty(); // will return 'false'
damero.clear(); // now it is indeed empty

Corollary

It is possible that you have already realized: when they need to compare objects with each other to know if they are equal, these classes use the equals(·) method of those objects. Thus, a CoordenadaAjedrez object will be equal to another if the equals(·) method of the CoordenadaAjedrez class says so.

For example, when we ask a set if it contains a given object ‘object1’, it uses the equals(·) method of the object’s class to search for an object in the set such that object1.equals(object_in_set) returns ‘true’.

hashCode()

This method, which is usually implemented in all classes, returns an integer that acts as an identifier for an object. It is a method compatible with equals(), so that if object1.equals(object2) returns ‘true’, then object1.hashCode() returns the same value as object2.hashCode().

Some implementations of lists, sets, and maps use hashCode(·) instead of equals(·) to know if two objects are equal.

JCF API

Finally, every Java programmer makes intensive use of the language’s API (Application Programming Interface) documentation. The full documentation of the Java Collections Framework for version 1.8 of Java can be consulted here:

Java Collections Framework