1 Creating Lists in Java: the Linked List class
1.1 Integer versus int
1.2 Importing Linked List
2 Operations on Lists: Adding and Counting Elements
3 Creating List Data
4 Iterating Over Java Lists
4.1 Preferred Technique: Element-Based Loops
4.2 Alternative Technique: Index-Based Loops
4.3 Which form of loop should we use?
5 Practice Problems Over Lists and For-Loops

Java’s Built-In Lists

Kathi Fisler

Lists are built into Java. There are several variations of them. We will start with the LinkedList class in this course. Since you know lists conceptually, we’ll do this introduction largely through examples.

1 Creating Lists in Java: the LinkedList class

First, let’s create an empty linked list of strings, called Words:

  LinkedList<String> Words = new LinkedList<String>();

The <String> tells Java what type of data will go into the list. Since lists could contain any kind of data, Java needs to know what type of data is in any particular list to help it check types at compile time. If instead we wanted a list of numbers (say containing the points earned on each question in a quiz), we would write

  LinkedList<Integer> QuizPoints = new LinkedList<Integer>();

1.1 Integer versus int

Notice that we used <Integer> instead of <int> (as this course has used all along for numbers). The type given to a LinkedList must be the name of either a class or an interface. In Java, Integer is a class that holds a number, whereas int is just a number by itself (without a class). Operations on ints are faster than the corresponding ones on Integer (since there is no class involved), but if you need to treat a number as a class, you must use Integer instead.

1.2 Importing LinkedList

We have to tell Java whenever your code uses its built-in classes (beyond simple kinds of data like String). You’ve already experience the idea of import when you configured the Tester. Similarly, to use LinkedList class, we have to import the class definition into our program. So a complete file with our two list definitions would look like:

  import java.util.LinkedList;

  

  class Examples {

    LinkedList<String> Words = new LinkedList<String>();

    LinkedList<Integer> QuizPoints = new LinkedList<Integer>();

  }

Naturally, you can use LinkedList inside more than just the Examples class (we will do so shortly); we’re putting the lists in the Examples class here to give a simple way to play with the lists for now.

2 Operations on Lists: Adding and Counting Elements

The methods for adding elements to the front of a list and getting the length of a list are called addFirst and size, respectively. Here’s an example of of them, written as a series of interactions in DrJava.

  > import java.util.LinkedList

  > LinkedList<String> Words = new LinkedList<String>();

  > Words

  []

  > Words.size()

  0

  > Words.addFirst("pie")

  > Words.size()

  1

  > Words.addFirst("corn")

  > Words.size()

  2

  > Words

  [corn, pie]

If you instead wanted the final list to have pie first and corn second (ie, adding puts new elements on the end of the list), you would use the add method instead of addFirst. We’re using addFirst today as it matches the behavior of cons from Racket.

Something to note here is that the list methods in Java modify the original list (which was not true in Racket–cons left the original list intact, and made it the programmer’s responsibility to save the new list). Once you add an element to a list in Java, you no longer have access to the list without the element.

[Side note: If you want to see the full list of operators provided on LinkedLists, check out the official LinkedList documentation.]

What does the sequence above suggest is the return type of the addFirst method? Nothing prints, so it seems to return void. Try the following interaction:

  Words.addFirst("milk").addFirst("tofu")

Java raises an error that "No method in void has name ’addFirst’". In Java, addFirst does indeed return void, so you cannot chain together expressions as you might have been used to in Racket.

Both of these traits of addFirstmodifying the original list and returning void—have interesting implications for how we program. We will discuss these issues as they come up throughout the course. For now, the implication is that you can’t create lists using the same pattern as you did if you are coming from Racket.

3 Creating List Data

So how do you create lists in Java? You use a sequence of separate calls to addFirst, such as:

  Words.addFirst("pie");

  Words.addFirst("tofu");

Where do these lines go? Java requires these to be inside a method. They cannot lie in a class without being in a method. Specifically, you CANNOT do the following:

  class Examples {

    LinkedList<String> Words = new LinkedList<String>();

    LinkedList<Integer> QuizPoints = new LinkedList<Integer>();

  

    //THIS DOESN'T WORK!!

    Words.addFirst("pie");

    Words.addFirst("tofu");

  }

You have two possible places to put code that builds lists: either in the Examples class construtor, or inside one of your test methods. Here’s a sample class that shows both:

  class Examples {

    LinkedList<String> Words = new LinkedList<String>();

    LinkedList<Integer> QuizPoints = new LinkedList<Integer>();

  

    Examples(){

      Words.addFirst("pie");

      Words.addFirst("tofu");

    }

  

    boolean testQuizSize(Tester t) {

      QuizPoints.addFirst(85);

      QuizPoints.addFirst(92);

      return t.checkExpect(QuizPoints.size(), 2);

    }

  }

Which is better? As a general rule of thumb, put code in the constructor if you want to use it across multiple test cases. If you need a list for a specific test, create the list within the test method. But remember that the Java list operators modify the list contents, so you can’t set up a list in the constructor, then modify it different ways in two different test cases. We will talk about how to deal with this issue in more detail in the coming weeks.

4 Iterating Over Java Lists

Let’s write a method to sum up the numbers in a LinkedList of integers. Java does not have an operator corresponding to rest in Racket. This changes how we write programs over lists: in particular, we cannot simply call a function recursively on the rest of a list. Instead, you need to use something called a for loop. There are two forms of these loops in Java. We’ll show you each in turn.

4.1 Preferred Technique: Element-Based Loops

The cleaner loop for computing over every element in a list looks as follows:

  int sum (LinkedList<Integer> numList) {

    int result;

  

    result = 0;

    for ( Integer n : numList ) {

      result = result + n;

    }

    return result;

  }

To understand the structure of this code, it helps to step back and see the pieces of the construct. In the following code skeleton, substitute single expressions for terms in all-caps, and substitute computations for descriptions in square-brackets:

  RETURN-TYPE aMethod(LinkedList<TYPE> SPECIFIC-LIST) {

    RETURN-TYPE result;  // a local variable (field) in the class

  

    result = [the result when the list is empty]

    for ( TYPE VARNAME : SPECIFIC-LIST ) {

      result = [expression to build new result from current

                value of result and VARNAME];

    }

    return result;

  }

For those who took 1101, this is just the accumulator-style of programs that you did in the last segment of that course. Here, we are simply adapting that style to Java. Java takes care of the work that the recursive call used to do. Whatever computation you used to pass to the accumulator parameter, you now use to update the result within the loop.

Getting used to these loops just requires a bit of practice.

4.2 Alternative Technique: Index-Based Loops

Java (and indeed many programming languages) also provides another way of iterating over lists one element at a time. The other approach uses another variable to hold a position (or index) into a list, then extracts the contents of the list at each index in turn. Here is the same sum program written using index-based loops:

  int sumIndexed (LinkedList<Integer> numList) {

    int result; // will hold the result of the computation

    int index;  // will hold positions in the list

  

    result = 0;

    for (index = 0 ; index < numList.size() ; index = index+1) {

      result = result + numList.get(index);

    }

    return result;

  }

What’s going on here?

Most of the differences between this version and the previous one lie in working with the index variable. We recommend that you first get comfortable with element-based loops before you worry about index-based loops.

4.3 Which form of loop should we use?

As a general rule, use the element-based loop if you can. Only use an index-based loop if you (a) need to refer to more than one position in the list at a time, or (b) need to skip over elements based strictly on their position in the list.

The element-based loop is tighter. It avoids creation of the extra variable for the index. It avoids confusion between the index and the contents of a list (a mistake which many, many programmers, even experienced ones, often make). Cleaner and less error-prone should win the day when you are selecting programming constructs.

5 Practice Problems Over Lists and For-Loops

Here are two practice problems you can try over lists and for-loops. We will post additional ones to the website for those who are new to Java and for-loop syntax.

Here are the solutions for both problems. Notice how we have filled in the pattern from before: the type of result matches the return type of the function, and the result gets updated by what we do to process each individual Dillo in the list:

  int countLongStrings(LinkedList<String> theStrs) {

    int result;

  

    result = 0;

    for ( String s : theStrs ) {

      if (s.length() > 5) {

        result = result + 1;

      }

    }

    return result;

  }

  LinkedList<Dillo> reviveAllDillos(LinkedList<Dillo> theDs) {

    LinkedList<Dillo> result;

  

    result = new LinkedList<Dillo>();

    for ( Dillo d : theDs ) {

      result.add(new Dillo(d.length, true)) ;

    }

    return result;

  }