# Arrays, Collections, and Generics

> Chapter 9 of the Courseiva ORACLE-OCP-JAVA17 curriculum — https://courseiva.com/learn/oracle-ocp-java17/arrays-collections-and-generics

**Official objective:** 5.1 — Use arrays, lists, sets, maps, and stacks, including generic types and utility methods (e.g., Collections, Arrays).

## Introduction

Arrays, Collections, and Generics — the fundamental toolkits for storing and organising groups of data in Java. If you have ever needed to keep a list of scores, a set of unique user IDs, or a mapping of employee names to their salaries, these structures are your answer. For the 1Z0-829 exam, understanding when to use an array versus a List, or a Set versus a Map, and how to make them type-safe with generics, is tested heavily — expect at least 5-7 questions on these topics.

## The Head Chef's Kitchen Station Analogy

A head chef runs a busy restaurant kitchen. She has different storage systems for different ingredients. A fixed-size, metal tray with 10 slots is her "array" — she knows exactly how many slots it has, and she can only hold one ingredient type per tray (say, only sliced tomatoes). If she needs more slots, she must throw the tray away and buy a bigger one. That's an array: fast to access any slot, but rigid.

Her "collection" is like a set of stackable, flexible plastic bins. She can add or remove bins as needed. One bin might hold all the chopped onions (a "List" — order matters, duplicates allowed). Another bin might be a "Set" — a single compartment where each ingredient can only appear once (like unique spice blends). The "Map" is her recipe binder, where each recipe name (key) points to a list of ingredients (value). She can quickly look up "Tiramisu" and find the ingredients, without caring about the order of recipes.

"Generics" are the labels she puts on each bin. Instead of a bin labelled "Stuff", she labels it "Chopped Onions" or "Sliced Mushrooms". This prevents her from accidentally putting carrots into the mushroom bin. Generics tell the kitchen (the compiler) what type of ingredient each bin is supposed to hold, catching mistakes before the dish is served (before the program runs).

The head chef's "Stack" is the pile of clean plates by the pass. She always takes the top plate first (Last-In, First-Out). Her "Queue" is the line of waiters waiting for their orders — first waiter in gets served first (First-In, First-Out). These are specialised collections, each with a specific rule for adding and removing items.

Without these organised systems, the kitchen would be chaos: ingredients would go missing, orders would be wrong, and the chef would have no way to scale up for a busy Saturday night dinner service.

## Core explanation

Let's start with the simplest container: the array. An array is a fixed-size, ordered container that holds elements of a single type. You declare it with square brackets, like `int[] scores = new int[5];` This creates a box that can hold exactly five integers, stored in positions 0 through 4 (Java is zero-indexed). To put a value in, you write `scores[0] = 95;` To read a value, you write `int firstScore = scores[0];` Arrays are lightning-fast for accessing any position because the memory location is calculated directly. The big downside: you cannot add or remove slots after creation. If you need six scores, you must create a new, larger array and copy everything over. That is clumsy and error-prone.

This is where the Collections Framework comes in. The Java Collections Framework is a unified architecture for storing and manipulating groups of objects. It provides interfaces (contracts) and classes (implementations) that solve the limitations of arrays. The four main interface families you must know for the exam are List, Set, Map, and Queue (which includes Deque and Stack by inheritance). A List is an ordered collection that allows duplicates. You can insert, remove, and access elements by index, and it grows and shrinks dynamically. Common implementations are ArrayList (backed by a resizable array) and LinkedList (backed by linked nodes). A Set is an unordered collection that forbids duplicates. It is perfect for ensuring uniqueness — for example, a set of all registered email addresses. HashSets use hash tables for fast lookup; TreeSets keep elements sorted. A Map is not technically a Collection (it does not extend the Collection interface), but it is part of the framework. It stores key-value pairs, where each key is unique. You look up a value by its key, like a dictionary. HashMap is the most common implementation. Queue and Deque (double-ended queue) represent collections for holding elements prior to processing. A typical Queue processes elements in FIFO (First-In, First-Out) order, while a Deque allows insertion and removal at both ends. Stack is a legacy class that represents a LIFO (Last-In, First-Out) stack, but Deque is now preferred.

Now for Generics. Before generics were introduced in Java 5, you could put any type of object into a collection, and you had to cast it back when you retrieved it. This was unsafe and led to runtime ClassCastExceptions. Generics solve this by allowing you to specify the type of elements a collection can hold at compile time. You write `ArrayList<String> names = new ArrayList<>();` The angle brackets `<String>` are the generic type parameter. They tell the compiler: "This ArrayList will only hold String objects" . If you try to add an Integer to it, the compiler will reject it with an error. This catches bugs before you even run the programme.

The diamond operator `<>` was introduced in Java 7 to reduce redundancy. On the left side of an assignment, you write the full generic type (`ArrayList<String>`). On the right side, you can just write the diamond (`new ArrayList<>()`), and the compiler infers the type from the left side. The exam loves testing whether you know where the diamond is allowed and where it is not.

Generic classes can be defined with type parameters that have bounds. For example, `List<? extends Number>` means a list that holds elements of some unknown type that is a subtype of Number (like Integer or Double). This is called a wildcard with an upper bound. Similarly, `List<? super Integer>` means a list that can hold Integer or any supertype of Integer (like Number or Object). These wildcards are critical for writing flexible, reusable methods. The PECS rule (Producer Extends, Consumer Super) helps you decide which to use: if you are only reading items from a structure, use `? extends T`; if you are only inserting items, use `? super T`.

Let's walk through a concrete example. Imagine you are building a library system. You need to store a list of books. With an array, you would create `Book[] books = new Book[100];` If the library grows to 101 books, you have a problem. With a List, you can add a 101st book dynamically: `List<Book> books = new ArrayList<>(); books.add(new Book());` You also want to ensure no duplicate ISBNs are added. For that, you use a Set: `Set<String> isbns = new HashSet<>();` And you want to look up a book by its shelf location code. That is a Map: `Map<String, Book> shelfLocation = new HashMap<>();` Each collection solves a distinct problem.

For the exam, you must memorise the key characteristics of each collection type, when to use which, and the syntax of generics including wildcards and the diamond operator. You will also be tested on utility methods from the Collections and Arrays classes, such as `Collections.sort()`, `Collections.binarySearch()`, `Arrays.asList()`, and `Arrays.sort()`. These are static helper methods that operate on collections and arrays. For instance, `Arrays.asList()` returns a fixed-size List backed by the array — you cannot add or remove elements from this list, but you can change existing elements. That's a classic exam trap.

Finally, remember that raw types (using a generic class without type parameters, e.g., `List list = new ArrayList();` ) are allowed but discouraged. They generate compiler warnings and can cause runtime cast exceptions. The exam expects you to know that raw types exist solely for backward compatibility with pre-Java 5 code.

## Real-world context

A junior developer at a retail company is tasked with building a feature to manage the company's product inventory. The system needs to store thousands of products, each with a unique SKU code, a name, a price, and a quantity in stock. The developer starts by defining the data structures.

Step one: representing a product. The developer creates a Java class `Product` with fields for SKU (String), name (String), price (double), and quantity (int). Now, she needs to store all products in memory while the application is running.

She considers using an array of Product objects, like `Product[] inventory = new Product[10000];` But the company's product catalogue changes daily — new products are added, old ones are discontinued. An array's fixed size is a nightmare. She would have to constantly check if the array is full and create a larger one, copying the data. That is brittle and slow. Instead, she chooses an `ArrayList<Product>`. This list grows automatically. She writes `List<Product> inventory = new ArrayList<>();` and calls `inventory.add(product);` for each new product. When a product is discontinued, she calls `inventory.remove(product);` and the list shrinks. The order of products matches the order she adds them, which is useful for display.

Step two: ensuring SKU uniqueness. The inventory list accidentally allows two different products with the same SKU if someone writes buggy code. To prevent this, she creates a secondary data structure: a `Set<String> skuSet = new HashSet<>();`. Every time she adds a new product, she first checks `if (!skuSet.add(product.getSku())) { // handle duplicate }`. The Set's `add()` method returns `false` if the element already exists. This enforces uniqueness at the application level.

Step three: looking up a product by SKU quickly. Currently, to find a product, she has to loop through the entire inventory list — O(n) time, which is slow for thousands of products. She needs O(1) lookup. She creates a `Map<String, Product> skuToProduct = new HashMap<>();`. When a product is added, she also does `skuToProduct.put(product.getSku(), product);`. Now, retrieving a product is instant: `Product p = skuToProduct.get(skuCode);`.

Step four: sorting products by price for a sale. She uses `Collections.sort(inventory, Comparator.comparingDouble(Product::getPrice));` This rearranges the list. To search for a specific price, she could then use `Collections.binarySearch()` on the sorted list.

Step five: generating report data. She needs to quickly find all products whose quantity is below a restock threshold. She uses a Java Stream (though that is a later exam objective) to filter the list: `inventory.stream().filter(p -> p.getQuantity() < 10).collect(Collectors.toList());`. But the foundation — the ArrayList — makes this possible.

Step six: handling future changes. The Product class becomes generic. She creates a `ProductCategory<T>` where `T` is the type of the category-specific data (e.g., `ProductCategory<ElectronicsExtra>` or `ProductCategory<ClothingExtra>`). This uses generics to provide type safety for different product types without creating separate classes for each.

A senior developer reviews her code and spots a potential issue: she is using `ArrayList` in a method signature `public void process(List<Product> items)` which is flexible, but she also has a method `public void processSpecial(Map<String, ? extends Product> specialMap)` to accept maps with product subtypes. This demonstrates her understanding of generic wildcards for API flexibility.

By the end of the sprint, the developer has built a robust inventory management system using Lists for ordered product storage, Sets for uniqueness, Maps for fast lookups, and generics for type safety. This is exactly the kind of real-world problem-solving that the 1Z0-829 exam tests conceptually — not rote memorisation of class hierarchy, but knowing which tool fits which job.

## Exam focus

The 1Z0-829 exam tests 5.1 (Arrays, Collections, and Generics) with about 6-8 questions. The examiners love to set traps using subtle distinctions between collections that look similar but behave differently. Here is what you must master.

List vs Set vs Map: You must know the core contract differences. A List allows duplicates and maintains insertion order. A Set forbids duplicates. A Map stores key-value pairs. Questions often show code that adds duplicate elements and asks what the size will be. For a HashSet, the second add simply returns false and does not change the size. For an ArrayList, the size increments even with a duplicate.

ArrayList vs LinkedList: The exam tests performance characteristics. ArrayList gives O(1) random access (get by index), but O(n) insertion in the middle. LinkedList gives O(n) random access but O(1) insertion at beginning or middle (if you have the node reference). A typical question: "Which List implementation should you use if you frequently add elements at the beginning?" Answer: LinkedList.

HashMap vs TreeMap vs LinkedHashMap: HashMap offers O(1) average-time operations, unordered. TreeMap keeps keys sorted (by Comparable or Comparator). LinkedHashMap maintains insertion order. The exam asks you to predict the iteration order after adding elements. A common trap: using a HashMap and expecting insertion order — you cannot rely on it.

Generics and Wildcards: This is a heavily tested area. You must encode the PECS rule (Producer Extends, Consumer Super). Questions show method signatures like `void addAll(List<? super Number> list)` and ask what types of lists you can pass. The answer: you can pass a List<Number>, List<Object>, or List<Serializable>, but NOT a List<Integer>. Conversely, `void printAll(List<? extends Number> list)` accepts List<Integer>, List<Double>, List<Number>, but NOT List<Object>.

The diamond operator: The exam frequently tests where the diamond `<>` is valid. It is allowed on the right side of an assignment when the left side provides the type context. It is NOT allowed in anonymous classes (before Java 9 — on the exam, treat anonymous classes as requiring explicit type on the right). Trap: `List<String> list = new ArrayList<>();` is valid. `List<> list = new ArrayList<String>();` is NOT valid.

Raw types: Questions on raw types appear at least once. Example: `List raw = new ArrayList(); raw.add("hello"); String s = (String) raw.get(0);` This compiles with unchecked warnings but works. The exam asks why raw types are bad: they bypass compile-time type checking and can cause ClassCastException at runtime.

Collections utility methods: `Collections.sort(List<T> list)` requires that T implements Comparable. If T does not, the compiler throws an error. `Collections.binarySearch()` requires the list to be sorted first, otherwise the result is undefined. `Arrays.asList()` returns a fixed-size list backed by the original array — you can call `set(index, value)` but not `add()` or `remove()`. The exam will test this with an UnsupportedOperationException trap.

Autoboxing with collections: When adding primitives to a collection (e.g., `List<Integer>`), Java automatically boxes them (converts int to Integer). The exam tests your knowledge that `List<Integer>` cannot hold a primitive `int` directly — autoboxing happens implicitly. However, comparing `Integer` objects with `==` works only for values in the range -128 to 127 (the integer cache). Outside that range, `==` compares references, not values. Use `.equals()` for value comparison.

Null elements: Most collections allow null elements. HashSet allows one null (since HashMap allows one null key). ArrayList allows multiple nulls. TreeSet does NOT allow null — it will throw NullPointerException on insertion because it needs to compare elements for sorting. This is a common exam trap.

Legacy classes: Stack and Vector are legacy and synchronised. They are rarely used today. The exam may ask which modern counterpart replaces them (Deque for Stack, ArrayList for Vector).

To prepare, drill with flashcards for: List vs Set vs Map characteristics, ArrayList vs LinkedList, HashMap vs TreeMap, wildcard syntax (extends/super), diamond operator rules, Collections utility methods, and autoboxing pitfalls. Practice reading code snippets and predicting output. The majority of questions are "what is the output of this code" style.

## Step by step

1. **Step 1: Recognise the need for multiple objects** — Identify that your application needs to store more than one piece of related data. For example, a list of customer names or a mapping of product IDs to products. This is the fundamental reason to use arrays or collections.
2. **Step 2: Decide between array and collection** — If the number of elements is fixed and known at compile time (e.g., days of the week), use an array for simplicity and slight performance gain. Otherwise, choose a collection from the Java Collections Framework. For most applications, collections are the correct choice.
3. **Step 3: Choose the correct collection type** — Select the interface based on your requirements. If you need ordered, indexed access with possible duplicates, use List (e.g., ArrayList). If you need uniqueness, use Set (e.g., HashSet). If you need key-value lookups, use Map (e.g., HashMap). If you need LIFO or FIFO processing, use Deque (e.g., ArrayDeque).
4. **Step 4: Apply generics for type safety** — Declare the collection with a generic type parameter to specify what type of objects it will hold, e.g., `List<String> names = new ArrayList<>();`. This prevents accidentally adding objects of the wrong type and eliminates the need for casting when retrieving elements.
5. **Step 5: Add and manipulate elements** — Use the appropriate methods for your chosen collection. For List: `add()`, `remove()`, `get(index)`, `set(index, value)`. For Set: `add()`, `remove()`, `contains()`. For Map: `put(key, value)`, `get(key)`, `remove(key)`, `containsKey()`. For Deque: `push()`, `pop()`, `offer()`, `poll()`.
6. **Step 6: Use utility methods for common operations** — Leverage static methods from Collections (e.g., `Collections.sort()`, `Collections.binarySearch()`, `Collections.reverse()`) and Arrays (e.g., `Arrays.asList()`, `Arrays.sort()`) to perform sorting, searching, and conversions without writing custom loops.
7. **Step 7: Iterate or stream for bulk processing** — Use for-each loops or streams (Java 8+) to process all elements. For example, `for (String name : names) { System.out.println(name); }` or `names.stream().filter(n -> n.startsWith("A")).collect(Collectors.toList());`. This step is about efficiently working with the data once stored.

## Comparisons

### Array vs ArrayList

**Array:**
- Fixed size: cannot add or remove elements after creation
- Can hold primitives directly (int[], char[])
- Part of the Java language, not a class; uses [] syntax

**ArrayList:**
- Resizable: dynamically grows and shrinks as elements are added/removed
- Cannot hold primitives directly; uses wrapper classes with autoboxing
- Part of the java.util package; a generic class with methods like add(), remove()

### HashSet vs TreeSet

**HashSet:**
- Uses hash table; offers O(1) average-time for add, remove, contains
- Does not maintain any order; iteration order is unpredictable
- Allows null element (one null)

**TreeSet:**
- Uses a Red-Black tree; offers O(log n) for add, remove, contains
- Maintains elements in sorted order (natural or Comparator-defined)
- Does not allow null elements (throws NullPointerException)

### List vs Set

**List:**
- Allows duplicate elements
- Maintains insertion order (in typical implementations like ArrayList)
- Elements can be accessed by index (position)

**Set:**
- Forbids duplicate elements (uniqueness is guaranteed)
- Order is not guaranteed (except in LinkedHashSet and TreeSet)
- No index-based access; must iterate or use contains() to find elements

### HashMap vs TreeMap

**HashMap:**
- Uses hash table; O(1) average-time for get and put
- Does not guarantee any order of keys
- Allows one null key and multiple null values

**TreeMap:**
- Uses Red-Black tree; O(log n) for get and put
- Maintains keys in sorted order (natural or Comparator-defined)
- Does not allow null keys (throws NullPointerException)

## Diagram

_A flowchart showing the hierarchy of Java data storage: arrays versus the Collections Framework, with key interfaces and their common implementations._

```mermaid
flowchart TD
    A[Java Data Storage] --> B[Arrays]
    A --> C[Collections Framework]
    B --> D[Fixed Size, Fast Access]
    C --> E[List]
    C --> F[Set]
    C --> G[Map]
    C --> H[Queue / Deque]
    E --> I[ArrayList]
    E --> J[LinkedList]
    F --> K[HashSet]
    F --> L[TreeSet]
    G --> M[HashMap]
    G --> N[TreeMap]
```

## Common misconceptions

- **Misconception:** Arrays and ArrayLists are essentially the same thing, just ArrayLists have more features. **Reality:** Arrays are fixed-size, built-in data structures; ArrayLists are resizable, part of the Collections Framework. An array is a primitive concept; ArrayList is a generic class that uses an array internally. You cannot add or remove elements from an array; ArrayLists can grow and shrink. (Because both use square brackets in declaration (int[] vs ArrayList<>) and both store ordered elements, beginners conflate them. The fact that ArrayLists are implemented using arrays internally reinforces the confusion.)
- **Misconception:** You can put any type into a generic collection if you use the diamond operator. **Reality:** The diamond operator `<>` infers the type from the left side. If the left side says `List<String>`, the diamond creates an ArrayList of String, not Object. You cannot add an Integer to it. The diamond does not mean 'any type'. (The name 'diamond' sounds generic and vague. Beginners think it means 'whatever type fits', when it actually means 'the same type as declared on the left'.)
- **Misconception:** A HashSet maintains the order in which elements were inserted. **Reality:** HashSet does NOT guarantee any order. It uses a hash table, so iteration order can appear random and can change when the set is resized. If you need insertion order, use LinkedHashSet. If you need sorted order, use TreeSet. (Some collections like ArrayList preserve insertion order, so beginners assume all collections do. The name 'HashSet' does not hint at ordering behaviour, making it a common trap.)
- **Misconception:** Using a raw type like `List list = new ArrayList();` is fine because it compiles without error. **Reality:** Raw types compile with unchecked warnings and bypass generic type safety. They can lead to ClassCastException at runtime. They exist only for backward compatibility with pre-Java 5 code. The compiler emits warnings, and the exam considers them bad practice. (Beginners see that the code compiles and runs, so they assume it's acceptable. They don't understand that the warnings are serious indicators of potential runtime bugs.)
- **Misconception:** The `Arrays.asList()` method returns an ArrayList, so you can add and remove elements freely. **Reality:** `Arrays.asList()` returns a fixed-size List backed by the original array. You cannot add or remove elements — calling `add()` or `remove()` throws UnsupportedOperationException. You can only replace existing elements using `set()`. (The method name suggests it creates an ArrayList, but it actually returns a private inner class of Arrays that wraps the array. Beginners don't read the Javadoc and assume it behaves like a normal ArrayList.)
- **Misconception:** When you store an int in an Integer collection, you can compare values using `==` and it always works. **Reality:** Autoboxing converts int to Integer. Integer objects are cached for values between -128 and 127. For values outside this range, `==` compares object references, not values. You must use `.equals()` for safe value comparison. (Beginners are used to comparing primitives with `==`. Autoboxing makes Integer look like a primitive, so they forget that Integer is an object. The integer cache masks the problem for common small values.)

## Key takeaways

- An array is a fixed-size, low-level container; a List is a resizable, high-level collection that is almost always preferred for ordered data.
- A Set guarantees uniqueness; use HashSet for fast lookups, TreeSet for sorted order, and LinkedHashSet for insertion order.
- A Map stores key-value pairs with unique keys; HashMap offers O(1) average performance, TreeMap keeps keys sorted, LinkedHashMap preserves insertion order.
- Generics provide compile-time type safety, eliminating the need for casting and preventing ClassCastException with collections.
- The diamond operator `<>` must be used on the right side of an assignment only when the left side provides sufficient type context.
- Use `? extends T` when you only read items from a structure (Producer), and `? super T` when you only insert items (Consumer) — this is the PECS rule.
- `Arrays.asList()` returns a fixed-size list backed by the original array; you cannot add or remove elements from it.
- Always use `.equals()` to compare the values of Integer objects outside the -128 to 127 cache range, never `==`.
- TreeSet and TreeMap do not allow null elements because they need to compare elements for ordering.
- Raw types (List without generics) are allowed for backward compatibility but should never be used in new code due to lack of type safety.

## FAQ

**What is the difference between a List and a Set in Java?**

A List is an ordered collection that allows duplicate elements. A Set is an unordered collection that forbids duplicates. Use a List when you care about the position of elements, and a Set when you need to ensure uniqueness.

**Can I use primitive types like int in a collection like ArrayList?**

No, collections cannot hold primitive types directly. You must use wrapper classes like Integer, Double, or Boolean. Java automatically converts between primitives and their wrappers through autoboxing and unboxing.

**What does the diamond operator `<>` do in Java?**

The diamond operator allows the compiler to infer the type of a generic class from the context. For example, in `List<String> list = new ArrayList<>();`, the compiler knows the ArrayList is of type String. It reduces redundancy and was introduced in Java 7.

**Why does my HashSet not preserve the order I inserted elements?**

HashSet uses a hash table for storage, which does not maintain any specific order. If you need insertion order, use LinkedHashSet. If you need sorted order, use TreeSet.

**What is the difference between ArrayList and LinkedList?**

ArrayList is backed by a resizable array and provides O(1) random access by index, but O(n) time for inserting or removing elements in the middle. LinkedList is backed by a doubly-linked list and provides O(1) insertion/removal at both ends, but O(n) random access. Choose based on your access and modification patterns.

**When should I use a Map instead of two separate Lists?**

Use a Map when you need to look up a value by a unique key quickly (O(1) on average with HashMap). For example, mapping a customer ID to a customer object. Two separate Lists would require linear search to find the matching key, which is slow for large datasets.

**What is a raw type in Java and why is it bad?**

A raw type is a generic class used without type parameters, e.g., `List list = new ArrayList();`. It bypasses compile-time type checking, allowing you to add any type of object. This forces you to use casts when retrieving elements, potentially causing ClassCastException at runtime. Raw types exist only for backward compatibility.

## Check your understanding

1. **Which collection type would you use to store a list of employee names where duplicates are allowed and you need fast random access by index?**

   Answer: ArrayList (a concrete implementation of the List interface). ArrayList allows duplicates and provides O(1) random access by index.

2. **What happens if you call `add()` on a HashSet with an element that already exists in the set?**

   Answer: The `add()` method returns `false` and the element is not added. The size of the HashSet remains unchanged.

3. **Given `List<? extends Number> list = new ArrayList<Integer>();`, can you add an Integer to `list`?**

   Answer: No. With `? extends Number` (upper-bounded wildcard), you cannot add any element (except null) because the compiler does not know the exact type of the list. The list could be ArrayList<Integer>, ArrayList<Double>, etc.

4. **What is the output of this code: `List<Integer> nums = new ArrayList<>(); nums.add(128); nums.add(128); System.out.println(nums.get(0) == nums.get(1));`**

   Answer: The output is `false`. Autoboxing creates two separate Integer objects for the value 128 (outside the cache range -128 to 127), so `==` compares references, not values. The correct comparison would be `.equals()`.

5. **What exception is thrown when you call `add()` on the List returned by `Arrays.asList()`?**

   Answer: UnsupportedOperationException. The list returned by `Arrays.asList()` is fixed-size and backed by the original array; it does not support structural modifications like `add()` or `remove()`.

---

Interactive version with quiz and diagrams: https://courseiva.com/learn/oracle-ocp-java17/arrays-collections-and-generics
