Java ArrayList and Collections: Operations, Traversals, and Algorithms

Learn how to create, modify, traverse, and analyze Java lists with ArrayList, wrapper types, standard algorithms, and the Collections utility class for AP Computer Science A.

Structure and Core Operations

An <E> is an ordered, resizable collection of object references. Unlike an array, whose length is fixed after creation, an can grow or shrink.

Important properties:

  • Ordered: Each element has an index beginning at 00.

  • Resizable: Adding or removing elements changes the number of stored elements.

  • Object-based: The collection stores objects rather than primitive values directly.

  • Duplicate-friendly: Equal values may occur more than once.

  • Mutable: Existing elements can be replaced, and the 's size can change.

A parameterized type such as <String> tells the compiler what kind of objects the should contain. Generics help detect incompatible values before the program runs. A variable may also use the interface while the object is an , allowing the implementation to be changed more easily later.

The basic operations have distinct purposes:

  • add(value) appends an element.

  • add(index, value) inserts an element and shifts the element previously at that index, along with later elements, to the right.

  • get(index) returns an existing element.

  • set(index, value) replaces an existing element without changing the size.

  • remove(...) deletes an element and shifts later elements to the left.

For insertion, an index from 00 through is valid; means insertion at the end. For access or replacement, the valid range is 00 through size()−1size() - 1. An invalid index causes an IndexOutOfBoundsException.

Takeaway: Distinguish operations that change size, such as add and remove, from operations that preserve size, such as get and set.

Traversing and Inspecting Elements

A traversal visits elements in a planned order. Choose the loop form according to whether the algorithm needs an index or changes the .

An index-based for loop is appropriate when the position matters:

for (int i = 0; i < values.; i++)

The usual condition is i<size()i < size(), not i≤size()i \leq size(). The last valid element is at index size()−1size() - 1, so using i≤size()i \leq size() eventually attempts an invalid access.

An enhanced for loop is convenient when the algorithm only needs each element:

for (String color : colors)

Do not add or remove elements from the during an enhanced for traversal. Changing the 's size can cause a ConcurrentModificationException and can make the traversal incorrect.

A while loop is useful when the index update depends on what happens during each iteration. For example, if an element is removed, the next element shifts into the same index and may need to be examined before the index increases.

Search and inspection methods complement traversal:

  • contains(value) reports whether an equal element is present.

  • indexOf(value) returns the first matching index or −1-1.

  • lastIndexOf(value) returns the last matching index or −1-1.

  • isEmpty() reports whether the contains no elements.

  • clear() removes all elements.

Takeaway: Use enhanced for for simple read-only visits, and use an index-based or controlled while loop when positions or structural changes matter.

Safe Modification During Traversal

Removing or inserting elements changes the positions of later elements, so the loop must account for shifting.

Suppose adjacent negative values occur. If a left-to-right loop removes the value at index ii and then immediately increments ii, the next value shifts into index ii and is skipped. This is a common traversal error.

Three reliable approaches are:

  1. Traverse backward. Start at the last index and move toward 00. Removing an element only shifts positions to the right, which have already been processed.

  2. Control the index with a while loop. After removal, keep the same index so the newly shifted element is examined. Increase the index only when no removal occurs.

  3. Build a new . Add only the desired elements to a separate while leaving the original unchanged.

Insertion requires similar care. Inserting at index ii shifts the old element at ii and all later elements one position right. If an algorithm inserts a value before an element and then continues forward, it must decide whether to process the inserted value, the original value, or both. The index update must reflect that decision.

A separate issue occurs when modifying an object inside a : an stores references. If two positions refer to the same mutable object, changing that object through one reference is visible through the other reference.

Takeaway: Whenever add or remove occurs during a traversal, explicitly track how the operation shifts indexes and how the next iteration should proceed.

Wrapper Types and Value Comparison

store objects, so primitive types use corresponding wrapper classes. Common pairs include:

  • int and Integer

  • double and Double

  • boolean and Boolean

  • char and Character

  • long and Long

Thus, a numeric uses <Integer>, not <int>. Java performs when it converts a primitive such as an int into an Integer object. It performs when it converts an Integer back to an int, for example during arithmetic.

Wrapper objects can be null, whereas primitives cannot. If Java tries to unbox a null wrapper, the program throws a NullPointerException, so code should ensure that a wrapper contains a value before relying on automatic conversion.

Wrapper objects are references. Therefore, use .equals to compare their values:

a.equals(b)

Using a == b compares object references rather than reliably comparing the numeric or logical values. The same value can be represented by distinct wrapper objects.

Takeaway: Know when Java is converting between primitives and objects, guard against null during , and use .equals for wrapper-value comparison.

Essential Algorithms

Many problems can be solved by one carefully designed traversal.

Aggregation and counting

For a sum, initialize an accumulator to 00 and add every element. For an average, divide the total by the number of elements, but use a decimal result when needed, such as average=totalsize\text{average} = \frac{\text{total}}{\text{size}}. Handle an empty according to the method specification before dividing.

For a minimum or maximum, initialize the candidate from the first element rather than automatically using 00. A containing only negative values shows why initializing a maximum to 00 can be incorrect. This approach requires an explicit policy for an empty .

For counting, start at 00 and increment whenever an element satisfies the required condition. A condition such as “even” can be expressed with a remainder test: a value is even when value%2=0value \mathbin{\%} 2 = 0.

Some versus all

To determine whether at least one element qualifies, return true as soon as a qualifying element is found; otherwise return false after the traversal. To determine whether all elements qualify, return false as soon as a violation is found; otherwise return true after the traversal. Check the specification for the intended behavior on an empty .

Duplicates, pairs, and reversing

A duplicate check can compare each element with later elements only. For each index ii, begin the inner comparison at i+1i + 1, avoiding self-comparison and repeated pairs.

To inspect consecutive pairs, stop the starting index at size()−2size() - 2, because each iteration also accesses i+1i + 1. To reverse a in place, swap the elements at matching positions from the two ends and move the left position rightward and the right position leftward. Use set for swapping; add would change the 's size.

Takeaway: Define the accumulator, stopping condition, empty- behavior, and early-return rule before writing the traversal.

Utilities and Problem-Solving Checks

The utility class supplies reusable algorithms for common operations. Typical calls include:

  • .sort() orders elements in ascending order and changes the .

  • .reverse() reverses the existing order.

  • .shuffle() randomly rearranges the elements.

  • .min() returns the smallest element without sorting the .

  • .max() returns the largest element without sorting the .

  • .frequency(, value) counts occurrences of a value.

.binarySearch(, target) has an important precondition: the must already be sorted according to the same ordering used by the search. The method returns an index if the target is present and a negative value if it is absent. Calling it on an unsorted does not provide a reliable result.

Choose a library operation when the task exactly matches a standard operation. Write a custom traversal when the condition is more specific, such as counting values greater than a threshold that also satisfy another property. In either case, determine whether the operation changes the or only returns information about it.

A useful final checklist is:

  • Are indexes bounded by 00 through size()−1size() - 1 for access?

  • Is add being distinguished from set?

  • Is remove(int) being distinguished from remove(Object)?

  • Could a structural modification skip an element or invalidate an enhanced traversal?

  • Could the be empty?

  • Are wrapper values compared with .equals?

  • Is a sorted- precondition satisfied before ?

Takeaway: Correct algorithms depend on both the operation's behavior and the conditions required before it is called.