Week 2: ArrayList and the List interface, built from "what holds a list you cannot size in advance?"
By the end you can hold a collection that grows as you add to it, declare it by the interface it satisfies
rather than the class that implements it, and read the angle-bracket type that keeps the collection honest about
what it holds. This is the list your Schedule field has been already, named and understood.
The user story behind this. A department chair wants to add courses to a term's schedule one at a time and not have to know the final count in advance, so that building a schedule feels like making a list, not sizing an array before they have decided what is on it. The collection has to grow on demand.
Before you start
- [ ] I can declare a variable and call methods on the object it holds.
- [ ] I can loop over a collection with an enhanced-for.
- [ ] I have seen an array and know its length is fixed once it is created.
The problem (sit with this before reading on)
You want a schedule to hold courses, but you do not know how many until the chair is done adding them. An array makes you commit up front:
Course[] courses = new Course[5]; // why 5? what about the sixth?
Pick too small and the sixth course has nowhere to go; pick too big and you carry empty slots and a separate count of how many are real. The size is a decision you are forced to make before you have the information to make it.
Check yourself. In one sentence, what is the specific problem with new Course[5] when you do not yet know
how many courses there will be?
Atom 1: an ArrayList grows on demand
An ArrayList is a list that resizes itself as you add to it. You never set its size; you just add:
List<Course> courses = new ArrayList<>();
courses.add(intro);
courses.add(systems);
int howMany = courses.size(); // 2, and it grows as you add more
There is no capacity to guess. add makes room, size tells you how many are really there, and get(i) reads
the one at a position. The list holds exactly what you put in it and no empty slots.
Check yourself. After courses.add(intro); courses.add(systems);, what does courses.size() return, and
did you have to decide a size anywhere?
Atom 2: declare it by the interface, build it with the class
Look again at the declaration: the variable type is List, but the object is an ArrayList. That is on
purpose. List is an interface, a promise of what a list can do (add, get, size, iterate); ArrayList is one
class that keeps that promise, using an array inside that it grows for you. You write the field as List<Course>
so the rest of your code depends only on the promise, not on the particular implementation.
The payoff is that the implementation can change without the rest of your code noticing. Swap new ArrayList<>()
for another List and every method that took a List still works, because it only ever used the promise. You
met this idea in Week 1: Comparable was an interface, a promise of a method. List is a bigger promise, kept
by ArrayList.
Check yourself. The field is List<Course> but the object is new ArrayList<>(). In one sentence, what
does writing the type as the interface buy you?
Atom 3: the angle brackets keep the list honest
The List<Course> says this is a list of Course and nothing else. The compiler holds you to it: you cannot
add a String to a List<Course>, and when you read an element back you get a Course with no cast. That
angle-bracket type is generics, and the empty pair on the right, new ArrayList<>(), is the diamond, which says
build an ArrayList of the same type the left side already named.
You will see generics in depth later, when you build a structure that is generic over its element type. For now the one idea you need is that the angle brackets say what is inside, and the compiler enforces it, so a list of courses cannot quietly end up holding something else.
Check yourself. You have List<Course> courses. In one sentence, what does the compiler do if you write
courses.add("CSCD 211"), and why?
Atom 4: read it with an enhanced-for
Because a List knows how to be iterated, the enhanced-for reads it cleanly, the same loop your Schedule
already uses to total credits:
for (Course c : courses) {
System.out.println(c.title());
}
No index, no size, no get(i). The loop asks the list for each element in turn. This is the everyday way to
read a collection, and it is the loop a custom list you build later will have to support to feel like a real
list.
Check yourself (competency close). Finish in your own words: "An ArrayList solves the array problem
because . I declare the field as List rather than ArrayList so that , and the <Course> means ___."
What you collected
- An
ArrayListgrows on demand, so you never guess a size the way an array makes you. - Declare the field by the
Listinterface and build it withArrayList, so code depends on the promise, not the implementation. - The angle-bracket type, generics, says what the list holds, and the compiler enforces it.
- The enhanced-for reads any list element by element, no index needed.
Where this is going
You can now hold one fixed value safely (enums) and many values that grow (ArrayList), and you can compose and
protect them (composition and defensive copy). That is the whole Week 2 toolkit for building the catalog and the
schedule. Week 3 starts building a list of your own, from a single node up, so you understand from the inside
what ArrayList has been doing for you.