Chapter 3
More About Methods and an Introduction to Objects

Copyright © 2004 by Stuart Reges

3.1 Introduction

Chapter 2 discussed techniques for solving complex problems and global constants that can add to the flexibility of a program solution. This chapter explores a more powerful technique for obtaining such flexibility. You will learn how to use value parameters to create procedures that solve not just single tasks, but whole families of tasks. Creating such procedures requires an insight into problems called generalization. It requires looking beyond a specific task to find a more general task for which this is just one instance. The ability to generalize is one of the most important qualities of a software engineer, and the generalization technique you will study in this chapter is one of the most powerful features of Java.

After exploring value parameters, the chapter examines the process of producing programs. More specifically, it examines the steps involved in producing a program and the order in which those steps should be performed. The chapter ends with a case study of a table-producing program.

3.2 Value Parameters

The DrawFigure program in chapter 2 performs its task adequately, but there are several things wrong with it. For example, there are six different places where a for loop writes out spaces. This is redundant and should be consolidated into a single method that performs all space writing tasks.

Each space writing task requires a different number of spaces, so we need some way to tell the method how many spaces to use. We can so by passing a value that is known as a parameter. The idea is that instead of writing a method that performs just one version of a task, we write a more flexible version that solves a family of related tasks that all differ by one or more parameters.

Parameter (parameterize)

Any of a set of characteristics that distinguish different members of a family of tasks. To parameterize a task is to identify a set of its parameters.

In the case of a writeSpaces method, the parameter is the number of spaces to write. This number characterizes the different space writing tasks in your family. This number comes from outside the method, so we would indicate its information flow as follows:

             number
               |
               V
        +-------------+
        | writeSpaces |
        +-------------+
This diagram indicates that writeSpaces must be fed a number before it can perform its task. Once it is fed a number, it will perform the task. It is possible to write such a method in Java using a value parameter:

        public static void writeSpaces(int number) {
            for (int i = 1;  i <= number; i++)
                System.out.print(" ");
        }
The value parameter appears in the method header after the name and inside the parentheses we have been leaving empty. This method uses a parameter called number of type int. So how do you give a value to the parameter, feed something into the method?? You can no longer call the parameterized method by using just its name:

        writeSpaces();
You must now say something like:

        writeSpaces(20);
        writeSpaces(4);
        writeSpaces(2 * 3 + 4);
Every call on method writeSpaces must specify what number of spaces to write. As the third call above indicates, you can use any integer expression.

Computer scientists use the word parameter liberally to mean both what appears in the method declaration (the formal parameter) and what appears in the method call (the actual parameter).

Formal Parameter

The parameter that appears in the header of a method declaration and which is used to generalize the method's behavior.

Actual Parameter

A specific parameter that appears in a method call and which is used to perform a specific task from the family of tasks.

The term formal parameter is not very descriptive of its purpose. A better name would be "generalized parameter." In the method above, number is the generalized parameter that appears in the method declaration. It is a placeholder for some unspecified value. The values appearing in the method calls are the actual parameters, because each call indicates a specific task to perform. In other words, each call provides an actual value to fill the placeholder.

The word "argument" is often used as a synonym for "parameter," as in, "These are the arguments I'm passing to this method." Some people prefer to reserve the word "argument" for actual parameters and the word "parameter" for formal parameters.

Let's look at an example of how you might use this writeSpaces method. Remember that the DrawFigure program had the following method called drawTop:

        public static void drawTop() {
            for (int line = 1; line <= SUB_HEIGHT; line++) {
                System.out.print("|");
                for (int column = 1; column <= (line - 1); column++)
                    System.out.print(" ");
                System.out.print("\\");
                for (int column = 1; column <= 2 * (SUB_HEIGHT - line); column++)
                    System.out.print(" ");
                System.out.print("/");
                for (int column = 1; column <= (line - 1); column++)
                    System.out.print(" ");
                System.out.println("|");
            }
        }
Using the writeSpaces method we can rewrite this as follows:

        public static void drawTop() {
            for (int line = 1; line <= SUB_HEIGHT; line++) {
                System.out.print("|");
                writeSpaces(line - 1);
                System.out.print("\\");
                writeSpaces(2 * (SUB_HEIGHT - line));
                System.out.print("/");
                writeSpaces(line - 1);
                System.out.println("|");
            }
        }
Notice that we call writeSpaces three different times, specifying how many spaces we want in each case. Similarly, we could also modify the drawBottom method from the DrawFigure program to simplify it.

3.2.1 The Mechanics of Value Parameters

When the computer activates a method, it initializes its value parameters. For each value parameter it first evaluates the expression passed as the actual parameter and then uses the result to initialize a local variable whose name is given by the formal parameter. This is best explained by example.

public class ValueParameterExample { public static void main(String[] args) { int first = 8; int second = 10; System.out.print("*"); writeSpaces(first); System.out.println("*"); System.out.print("!"); writeSpaces(second); System.out.println("!"); System.out.print("'"); writeSpaces(30); System.out.println("'"); System.out.print("<"); writeSpaces(first * second - 30); System.out.println(">"); } // Writes "number" spaces on the current output line to System.out public static void writeSpaces(int number) { for(int i = 1; i <= number; i++) System.out.print(" "); } } In the first two lines of method main the computer finds instructions to allocate and initialize two variables:

              +---+            +----+
        first | 8 |     second | 10 |
              +---+            +----+
The next three lines of code:

        System.out.print("*");
        writeSpaces(first);
        System.out.println("*");
produce an output line with 8 spaces bounded by asterisks on either side. You can see where the asterisks come from, but look at the method call that produces the spaces. When the computer activates the block for method writeSpaces, it must set up its value parameter. To set up the value parameter, it first evaluates the expression being passed as the actual parameter. The expression is simply the variable first, which has the value 8. Therefore, the expression evaluates to 8. The computer uses this result to initialize a local variable whose name is that of the formal parameter, number. Thus, you get:

              +---+            +----+          +---+
        first | 8 |     second | 10 |   number | 8 |
              +---+            +----+          +---+
The net effect of this process is that you have a local copy of the variable first. On exiting the method, the computer deallocates the value parameter:

              +---+            +----+
        first | 8 |     second | 10 |
              +---+            +----+
You finish this line of output with the println that appears after the method call. The next line starts by writing an exclamation mark at the left margin and then writeSpaces is called again, this time with the variable second as its actual parameter. The computer evaluates this expression obtaining the result 10. This value is used to initialize number. Thus, this time it creates a copy of variable second:

              +---+            +----+          +----+
        first | 8 |     second | 10 |   number | 10 |
              +---+            +----+          +----+
Because number has a different value this time (10 instead of 8), the method produces a different number of spaces. After this execution the computer again deallocates the local objects:

              +---+            +----+
        first | 8 |     second | 10 |
              +---+            +----+
The output line is finished with the println after the method call and the computer starts the third line of output. It writes a single quote mark at the left margin and then activates method writeSpaces again. This time it uses the integer literal 30 as the expression, which means it makes the value parameter number into a copy of it:

              +---+            +----+          +----+
        first | 8 |     second | 10 |   number | 30 |
              +---+            +----+          +----+
Again, the method will behave differently because of the different value of number. Finally, for the fourth call to method writeSpaces, the computer must evaluate a complex expression with arithmetic operators:

        first * second - 30 = (8 * 10) - 30 = 80 - 30 = 50
The computer uses this result to initialize number:

              +---+            +----+          +----+
        first | 8 |     second | 10 |   number | 50 |
              +---+            +----+          +----+
Thus, now number is a copy of the value described by this complex expression. Therefore, the total output of this program is:

* * ! ! ' ' < >

3.2.2 Limitations of Value Parameters

When a value parameter is set up, a local variable is created and is initialized to the value being passed as the actual parameter. The net effect is that the local variable is a copy of the value coming from the outside. Since it is a local variable, it can't influence any variables outside the method. Consider this method:

        public static void doubleNumber(int number) {
            System.out.println("Initial value of number = " + number);
            number *= 2;
            System.out.println("Final value of number = " + number);
        }
Suppose you have the same variables in method main with the same starting values as you did in the previous section:

              +---+            +----+
        first | 8 |     second | 10 |
              +---+            +----+
What happens if the computer executes this statement?

        doubleNumber(first);
It evaluates the expression being passed and obtains the result 8. Thus, it creates a local variable called number with the initial value 8:

              +---+            +----+          +---+
        first | 8 |     second | 10 |   number | 8 |
              +---+            +----+          +---+
The computer starts by writing out the current value of this local variable:

        Initial value of number = 8
This value is then doubled to 16. Since it is a local variable, this process does not effect the variables in method main:

              +---+            +----+          +----+
        first | 8 |     second | 10 |   number | 16 |
              +---+            +----+          +----+
The third statement of the method reports this new value:

        Final value of number = 16
When the method is done executing, the computer deallocates number to again obtain:

              +---+            +----+
        first | 8 |     second | 10 |
              +---+            +----+
Thus, the local manipulations of the value parameter do not change these variables on the outside. The use of copies is an important property of value parameters. A variable passed as the actual parameter to a value parameter is guaranteed not to change as a result of manipulations to the value parameter. We will, however, see that the implications are different when we work with objects instead of primitive data like ints.

3.2.3 More Value Parameter Details

So far the discussion of value parameter syntax has been informal. It's about time that we wrote down more precisely what syntax we are using to declare static methods.

public static void <name>(<type> <name>, ..., <type name>) { <statement or variable declaration>; <statement or variable declaration>; ... <statement or variable declaration>; } This template indicates that we can declare as many value parameters as we want inside the parentheses that appear after the name of a method in its header. We use commas to separate different parameters.

As an example of a method with multiple parameters, let's consider a variation of writeSpaces. It is convenient that we can tell it a different number of spaces to write, but it always writes spaces. What if we want 18 asterisks or 23 periods or 17 question marks? We can generalize the task even further by having the method take two parameters: both a character and a number of times to write that character.

        public static void writeChars(char ch, int number) {
            for (int i = 1;  i <= number; i++)
                System.out.print(ch);
        }
Recall that character literals are enclosed in single quotation marks. Using this method we can write code like the following:

        writeChars('=', 20);
        System.out.println();
        for(int i = 1; i <= 10; i++) {
            writeChars('>', i);
            writeChars(' ', 20 - 2 * i);
            writeChars('<', i);
            System.out.println();
        }
        writeChars('=', 20);
        System.out.println();
which produces the following output:

==================== > < >> << >>> <<< >>>> <<<< >>>>> <<<<< >>>>>> <<<<<< >>>>>>> <<<<<<< >>>>>>>> <<<<<<<< >>>>>>>>> <<<<<<<<< >>>>>>>>>><<<<<<<<<< ==================== You can include as many parameters as you want when you define a method. Each method call must provide exactly that number of parameters. They are lined up sequentially. For example, consider the first call on writeChars in the code fragment above versus the header for writeChars. Java lines these two up in sequential order (the first actual parameter going into the first formal parameter, the second actual parameter going into the second formal parameter):

        writeChars(                     '=',      20);
                                         |         |
                                         |         |
                                         V         V
        public static void writeChars(char ch, int number) {

3.2.4 Value Parameters versus Global Constants

How does this new technique relate to what you already know? The most important technique you learned in chapter 2 for creating program flexibility is the use of global constants. By using such constants, you make it easy to modify a program. The value parameter provides much of the same flexibility, and more. Consider the writeSpaces procedure. Suppose you wrote it using a global constant:

        public static final int NUMBER_OF_SPACES = 10;
This gives you the flexibility to produce a different number of spaces, but has one major limitation. The constant can change only from execution to execution. It cannot change within a single execution. In other words, you can execute the program once with one value, and then execute it again with a different value. But you can't use different values in a single execution of the program using a global constant.

Value parameters are more flexible. Because you specify the value to be used each time you call the method, you can use several different values in a single program execution. As you have seen, you can call it many different times within a single program execution and have it behave differently every time. At the same time, however, the value parameter is more work for the programmer than the global constant. It makes your method headers and method calls more tedious, not to mention making the execution (and, thus, the debugging) more complex.

Therefore, you will probably find occasion to use each technique. The basic rule is to use a global constant when you only want to change the value from execution to execution. If you want to use different values within a single execution, use the value parameter.

3.4 Methods that Return Values

Value parameters allow you to send a value into a method, but how do you get a value back out? Java provides a mechanism for returning a value from a method using a special "return" statement. For example, here is a method that takes a distance specified as a number of feet and that returns the corresponding number of miles.

    public static double miles(double feet) {
        return feet / 5280.0;
    }
First, notice that in the header for the method the familiar word "void" has been replaced with the word "double." When you declare a method that returns a value, you have to tell Java what kind of value it will return. In fact, the keyword "void" simply means "no return value". We can update our syntax template for static methods once more to include the fact that the header includes a return type (void for none):

public static <type> <name>(<type> <name>, ..., <type name>) { <statement or variable declaration>; <statement or variable declaration>; ... <statement or variable declaration>; } The syntax of the return statement is:

return <expression>; When Java encounters a return statement, it evaluates the given expression and immediately terminates the method, returning the value it obtained from the expression. It is possible to have more than one return statement within a method, although this won't make much sense until chapter 4 when we can do something called conditional execution using if and if/else statements. It is an error for a Java method with a non-void return type to terminate without executing a return.

It is possible to use a return statement even in a void method. In that case, there is no value to return, so the syntax is simply:

        return;

3.3.1 The Math Class

Many Java classes have static methods that return values. In particular, the Math class has a number of methods that perform standard arithmetic operations. For example, it has a method called sqrt that computes the square root of a number. The method has the following header:

        public static double sqrt(double a)
Keep in mind that this method is defined inside a class called Math. If we want to make use of that method in one of our own classes, we have to tell Java where to find it. We do so with the dot notation, referring to this as Math.sqrt. So we might write a program like the following: public class WriteRoots { public static void main(String[] args) { for (int i = 1; i <= 20; i++) System.out.println("sqrt(" + i + ") = " + Math.sqrt(i)); } } which produces the following output:

sqrt(1) = 1.0 sqrt(2) = 1.4142135623730951 sqrt(3) = 1.7320508075688772 sqrt(4) = 2.0 sqrt(5) = 2.23606797749979 sqrt(6) = 2.449489742783178 sqrt(7) = 2.6457513110645907 sqrt(8) = 2.8284271247461903 sqrt(9) = 3.0 sqrt(10) = 3.1622776601683795 sqrt(11) = 3.3166247903554 sqrt(12) = 3.4641016151377544 sqrt(13) = 3.605551275463989 sqrt(14) = 3.7416573867739413 sqrt(15) = 3.872983346207417 sqrt(16) = 4.0 sqrt(17) = 4.123105625617661 sqrt(18) = 4.242640687119285 sqrt(19) = 4.358898943540674 sqrt(20) = 4.47213595499958 Notice that we are passing a value of type int to Math.sqrt, but the header says that it expects a value of type double. Remember that if Java is expecting a double and gets an int, it converts the int into a corresponding double.

The Math class also defines two constants that are frequently used, e and pi. Following the Java convention, we use all uppercase letters for their names and refer to them as Math.E and Math.PI.

Below is a short list of some of the most useful methods from the Math class.

Useful Methods in the Math Class
Method Description Example
abs absolute value Math.abs(-308) returns 308
cos cosine (radians) Math.cos(Math.PI) returns -1.0
exp exponent base e Math.cos(1) returns 2.7182818284590455
log logarithm base e Math.log(Math.E) returns 1.0
max maximum of two values Math.max(45, 207) returns 207
min minimum of two values Math.min(3.8, 2.75) returns 2.75
pow power (general exponentiation) Math.pow(2, 4) returns 16.0
random random value Math.random() returns a random between 0.0 and 1.0
sin sine Math.sin(0) returns 0.0
sqrt square root Math.sqrt(2) returns 1.4142135623730951

You can see a complete list of methods defined in the Math class by checking out the api documentation for your version of Java. The acronym "api" stands for "Application Program Interface" or "Application Programming Interface." The api describes how to make use of the standard libraries that are available to Java programmers. It can be a bit overwhelming to read through the api documentation because the Java libraries are vast. So wander around a bit if you are so inclined, but don't be dismayed that there are so many libraries to choose from in Java.

3.3 Overloading of Methods

It is often the case that we want to create slight variations of the same method with different parameter passing. For example, we might have a method drawBox that allows you to specify a particular height and width but you might want to also have a version that draws a box of default size. In other words, sometimes you want to specify these values:

        drawBox(8, 10);
and other times you want to just tell it to draw a box with the standard height and width:

        drawBox();
Older programming languages required you to come up with different names for these. One might be called drawBox and the other might be called drawDefaultBox. Coming up with new names for each variation becomes tedious. Fortunately Java allows us to have more than one method with the same name as long as they have different parameters. This is a process called overloading and the primary requirement for overloading is that the different methods that you define have different method signatures.
Method Signature

The name of a method along with its number and type of parameters.

Method Overloading

Defining two or more different methods that have the same name but different method signatures.

For the drawBox example the two versions would clearly have different method signatures because one has 2 parameters and the other has 0 parameters. It would be obvious from any call on the method which one to use. If you see 2 parameters, you execute the version with 2 parameters. If you see 0 parameters, you execute the version with 0 parameters.

It gets more complicated when overloading involves the same number of parameters, but this turns out to be one of the most useful applications of overloading. For example, the println method is actually a series of overloaded methods. We can call println passing it a String, or passing it an int, or passing it a double and so on. This is implemented as series of different methods all of which take one parameter but one version takes a String, another version takes an int, another version takes a double and so on. Obviously you do slightly different things to print one of these kinds of data versus another, which is why it's useful to have these different versions of the method.

The Math class also has several overloaded methods. For example, there is a version of the absolute value method (Math.abs) for ints and another for doubles. The rules that govern which method is called are complex, so we won't cover them here. The basic idea, though, is that Java tries to find the method that is the best fit. For the most part, you don't have to think much about this. You can just let Java choose for you and it will generally make the right choice.

3.4 Introduction to Objects

We've spent a considerable amount of time discussing the primitive types in java and how they work, so it's about time that we started talking about objects and how they work. It would be nice if Java had a consistent object model, but it doesn't. That means that we, unfortunately, have to learn two sets of rules if we want to understand how our programs operate.

3.4.1 String Objects

Strings are one of the most useful and the most commonly used types of objects in Java, so we definitely want to see how they work. They don't make the best example of objects, though, because there are a lot of special rules that apply only to strings, so in the next section we'll look at a second kind of object to see something that is a little more typical.

Strings have the special property that there are literals that represent String objects. We've been using them in println statements since chapter 1. What we haven't discussed is that these literal values represent objects of type String (in other words, instances of the String class). For example, in the same way that you can say:

        int x = 8;
you can say:

        String s = "hello there";
In other words, we can declare variables of type String and can use the assignment statement to give a value to these variables. But strings are stored internally in a different way. For the int variable, we've drawn pictures like the following:

          +---+
        x | 8 |
          +---+
For the String variable, we'll draw this picture instead:

          +---+    +---------------+
        s | --+--> | "hello there" |
          +---+    +---------------+
In the String case we have two different values stored in memory. We have the String object itself, which appears on the right side of this picture. We also have a variable called s which stores a reference to the String object (represented in this picture as an arrow).

This will take some time to get used to because these are really two different approaches to storing data. These approaches are so common that computer scientists have technical terms to describe them. The system for the primitive types like int is known as value semantics and those types are often referred to as value types. The system for strings and other objects is known as reference semantics and those types are often referred to as reference types.

Value Semantics

A system in which values are stored directly and copying involves the creation of independent copies of values. Types that use value semantics are called value types.

Reference Semantics

A system in which references to values are stored and copying involves copying references. Types that use reference semantics are called reference types.

It will take us a while to explore all of the implications of this difference. The key thing to remember is that when you are working with objects, you are always working with references to data rather than the data itself. We will pick up on this theme in later sections of this chapter, but for now let's explore some of the other special properties of strings and other objects.

Unlike primitive types, objects like strings all have certain methods that can be called on that object. For example, the String class has a length method that tells you how many characters are in the string. If the length method were static, you would call it by saying something like:

        length(s)
Instead, you put the name of the variable first and then the method with a dot in between:

        s.length()
An interesting aspect of these methods is that when we deal with two different objects, we can get two different results even when we're calling the same method. Each string has its own length. For example, suppose that we have initialized two string variables as follows: code:

        String s1 = "hello";
        String s2 = "how are you?";
We can use a println to examine the length of each string:

        System.out.println("Length of s1 = " + s1.length());
        System.out.println("Length of s2 = " + s2.length());
which produces the following output:

        Length of s1 = 5
        Length of s2 = 12
This makes perfect sense because this is how our world works. If you ask two different people how old they are or what their birthday is, you don't expect to get the same answer even though you ask the same question of each. Objects capture this complexity, allowing us to create several different objects that all respond to the same methods, but which behave differently.

Another useful String method is the substring method. It takes two integer arguments representing a starting and ending index. Each character of the string is assigned an index starting with index 0. For example, for our variable s1 that refers to the String "hello" the indexes would be:

        h    e    l    l    o
        |    |    |    |    |
        0    1    2    3    4
It may seem intuitive to consider the letter "h" to be at position 1, but there are advantages to starting with an index of 0 and it's a convention that was adopted by the designers of the C language that has been followed also by the designers of C++ and Java, so it's a convention you'll have to learn to live with. For our longer string s2 the positions would be:

        h    o    w         a    r    e         y    o    u    ?
        |    |    |    |    |    |    |    |    |    |    |    |
        0    1    2    3    4    5    6    7    8    9   10   11
Notice that the spaces in the string have positions as well, as in positions 3 and 7 above. When you call the substring method, you provide two of these indexes, the index of the first character you want and the index just past the last index that you want. For example, if you want to pull out the individual word "how" from the string above, you'd ask for:

        s2.substring(0, 3)
Remember that the second value that you pass to the substring method is supposed to be one beyond the end of the substring you are forming. So even though there is a space at position 3 in the original string, we don't get it from this call on substring. Instead we get all characters just before position 3.

This means that sometimes you will give a position to substring at which there isn't a character. The last character in the string that s2 refers to is at index 11 (the question mark). If you want to get the substring "you?" including the question mark, you'd ask for:

        s2.substring(8, 12)
There is no character at position 12 in s2, but this call asks for characters starting at position8 that come before position 12, so this actually makes sense.

Below is a table of some of the most useful methods in the String class.

Useful Methods in the String Class
Method Description Example
charAt(index) character at a specific index s1.charAt(1) returns 'e'
endsWith(text) whether or not string ends with some text s1.endsWith("llo") returns true
indexOf(character) index of a particular character (-1 if not present) s1.indexOf('o') returns 4
length() number of characters s1.length() returns 5
startsWith(text) whether or not string starts with some text s1.startsWith("hi") returns false
substring(start, stop) characters from start index to just before stop index s1.substring(1,3) returns "el"
toLowerCase() a new string with all lowercase letters s1.toLowerCase() returns "hello"
toUpperCase() a new string with all uppercase letters s1.toUpperCase() returns "HELLO"

3.4.2 ArrayList Objects

Another important class we will be learning about is the ArrayList class. An ArrayList object can be used to store a list of other objects like strings. We won't be ready to do much with ArrayList objects until chapter 4, but it provides a good example to study to understand the basics of manipulating objects.

To create an ArrayList object you have to use the keyword "new":

        ArrayList list1 = new ArrayList();
This is actually a call on a special kind of method known as a constructor. Constructors always have the same name as the class. Sometimes you will need to pass a value to the constructor. For example, the ArrayList class has a second constructor that takes an integer that specifies an initial capacity for the list:

        ArrayList list2 = new ArrayList(100);
So you have the option of specifically saying what you want that initial capacity to be or you can leave off the integer and you'll get an ArrayList with the default capacity. ArrayList objects will "grow" if necessary, but passing this initial capacity can make your code run slightly faster.

Given the two declarations above, we will have two ArrayList objects. Remember that the objects are stored separately from the ArrayList variables. So the picture we'd draw of memory looks like this:

              +---+    +---------------------+
        list1 | --+--> | an ArrayList object |
              +---+    +---------------------+

              +---+    +---------------------+
        list2 | --+--> | an ArrayList object |
              +---+    +---------------------+
To underscore the fact that variables and objects are stored separately, consider what would happen if you now execute the following code:

        ArrayList list3 = list2;
This declares a third ArrayList variable but doesn't include a third call on new. That means that we still have just the two ArrayList objects even though we now have three ArrayList variables:

              +---+    +---------------------+
        list1 | --+--> | an ArrayList object |
              +---+    +---------------------+

              +---+    +---------------------+
        list2 | --+--> | an ArrayList object |
              +---+    +---------------------+
                                 ^
              +---+              |
        list3 | --+--------------+
              +---+
The variables list2 and list3 both refer to the same ArrayList object. This situation doesn't arise when you use value types like int. This happens only with objects.

Let's look at a complete program to explore this further. We need to be able to actually do something with our ArrayList objects, so we need to discuss some of its methods. ArrayList objects respond to a method called "add" that tells it to add a value to the list. For example, you might say:

        list1.add("hello");
        list2.add("howdy");
Another manipulation you can perform on an ArrayList is to print it by calling System.out.print or System.out.println. You can print the ArrayList by itself, as in:

        System.out.println(list1);
Or you can form a string literal with string concatenation:

        System.out.println("list1 = " + list1);
So let's look at a complete program that has two list objects and three list variables and that performs some calls on add followed by calls on println to examine what is in each list.

import java.util.*; public class ArrayListExample1 { public static void main(String[] args) { // declare variables and construct objects ArrayList list1 = new ArrayList(); ArrayList list2 = new ArrayList(); ArrayList list3 = list2; // fill up the lists list1.add("string 1 for 1"); list1.add("string 2 for 1"); list2.add("string 1 for 2"); list2.add("string 2 for 2"); list3.add("string 1 for 3"); list3.add("string 2 for 3"); // see what is in each list System.out.println("list1 = " + list1); System.out.println("list2 = " + list2); System.out.println("list3 = " + list3); } } There is something new at the beginning of this class file called an import declaration. Java classes are stored in what are known as packages. You collect together a set of related classes into a single package. The ArrayList class is stored in a package known as java.util, short for "Java utilities." We haven't needed an import statement yet because Java automatically imports every class stored in a package called "java.lang". The java.lang package includes basic classes that most Java programs would be likely to use (e.g., System, String, Math). Because Java does not automatically import java.util, we have to do it ourselves.

The import declaration allows you to import just a single item from a package, as in:

        import java.util.ArrayList;
Some people prefer to specifically mention each class that you are importing. I tend to use the asterisk to import every class from the package.

This program produces output that might be surprising:

list1 = [string 1 for 1, string 2 for 1] list2 = [string 1 for 2, string 2 for 2, string 1 for 3, string 2 for 3] list3 = [string 1 for 2, string 2 for 2, string 1 for 3, string 2 for 3] The manipulations using variable list1 are fairly straightforward. We construct an ArrayList object that list1 refers to and we add two things to it. We then use a println to see its contents and we find the two things we added. The manipulations using variables list2 and list3 are more complex. Remember that we have a single ArrayList object that both variables refer to. When we use list2 to add two things, they both go into this object. When we use list3 to add two things, they go into the same object. That is why the println statements indicate a list with four items. The two variables were operating on the same list.

If you're interested in learning more about ArrayList objects, you will find full documentation of its methods in the Java api. We will also be exploring them in great detail in a later chapter.

3.4.3 Null

Java has a special value called "null" that means "no object." There are many uses for null, although most of these require more programming knowledge than we have covered so far. One straightforward use is to initialize variables without constructing objects. For example, you might say:

        ArrayList list = null;
This gives a value to the variable without yet allocating an object. We often indicate this by putting a slash in the memory location for the variable:

             +---+
        list | / |
             +---+

3.4.4 Objects and Value Parameters

Many programming languages provide more than one kind of parameter passing. In particular, there is a style of parameter passing known as "reference parameters" that is included in Pascal, C++ and C#. Java does not have reference parameters, but when you're dealing with objects, you have something that is almost the same.

Consider what happens when you define a static method that uses a reference type for its parameter:

        public static void manipulate(ArrayList list) {
            list.add("hello");
            list.add("howdy");
        }
Remember that the value parameter creates copies of whatever is passed to it. With the value types like int, any changes that we made to the parameter had no effect on any variable passed to it. The situation is a more complicated for objects because of their reference semantics. Suppose that we have defined an ArrayList variable and we pass it to this method:

        ArrayList test = new ArrayList();
        manipulate(test);
Does the method end up adding anything to the object? The answer is yes, even though we are using a value parameter that creates a copy. Think of what is happening in the computer's memory. When we declare our variable test, we end up with this situation:

             +---+    +---------------------+
        test | --+--> | an ArrayList object |
             +---+    +---------------------+
When we call the manipulate method, Java makes a copy of the variable test and stores this in the parameter called list. But the variable test isn't itself an object. It stores a reference to an object. So when we make a copy of it, we end up with this situation in memory:

             +---+    +---------------------+
        test | --+--> | an ArrayList object |
             +---+    +---------------------+
                                 ^
             +---+               |
        list | --+---------------+
             +---+
We now have two variables that refer to the same ArrayList object. So it doesn't matter that list is a copy, because it's referring to the same object as the original. So any calls on add using list will change the object that test refers to.

There is one other case worth considering. What if the manipulate method had been written this way instead:

        public static void manipulate(ArrayList list) {
            list.add("hello");
            list.add("howdy");
            list = null;
        }
We add two things to the list just as in the old version, but then we turn around and reset the variable list to be null. Does this affect our variable test? The answer is no. Because list is a local copy, changing its value has no effect on our variable test:

             +---+    +---------------------+
        test | --+--> | an ArrayList object |
             +---+    +---------------------+

             +---+
        list | / |
             +---+
The bottom line is that when you pass an object as a parameter, you can change the object itself but you can't change the variable that points to the object.

It's as though you have a piece of paper on which you've written down the address to your home. You might write down the address to your home on a second piece of paper and give that to someone else. Because you have your own piece of paper telling you where your home is, nobody will be able to take that away from you even if they change what is written on their piece of paper. So you'll always know where you live. But if they know where your home is, they can go there and do whatever they want to it. In Hollywood terms, imagine a villain saying, "I know where you live," meaning, "I can go to your house and cause trouble."

3.5 Programming Problems

  1. Write a program that produces Christmas trees as output. It should have a method with two parameters: one for the number of segments in the tree and one for the height of each segment. For example, the tree on the left below has 3 segments of height 4 and the one on the right has 2 segments of height 5.

                 *                        *
                ***                      ***
               *****                    *****
              *******                  *******
                ***                   *********
               *****                    *****
              *******                  *******
             *********                *********
               *****                 ***********
              *******               *************
             *********                    *
            ***********                   *
                 *                     *******
                 *
              *******
    
  2. Write a program that produces several calendars. It should have a method that produces a single month's calendar like the one below given parameters that specify how many days are in the month and what the date of the first Sunday is (31 and 6, respectively, in the example).

             Sun  Mon  Tue  Wed  Thu  Fri  Sat
            +----+----+----+----+----+----+----+
            |    |    |  1 |  2 |  3 |  4 |  5 |
            |  6 |  7 |  8 |  9 | 10 | 11 | 12 |
            | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
            | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
            | 27 | 28 | 29 | 30 | 31 |    |    |
            +----+----+----+----+----+----+----+
    
  3. A certain bank offers 12.7% interest on loans compounded annually. Create a table that shows how much money a person will accumulate over a period of 25 years assuming an initial investment of $1000 and assuming that $100 is deposited each year after the first. Your table should indicate for each year the current balance, the interest, the new deposit, and the new balance.

  4. Write a program that produces a table showing all of the factors of the integers between 1 and 50. For example, the factors of 30 are 1, 2, 3, 5, 6, 10, 15, and 30.

  5. Write a program that shows the total number of presents received on each day according to the song "The Twelve Days of Christmas," as indicated below.

    Day Presents Received Total Presents
    111
    223
    336
    4410
    5515
    .........


    Stuart Reges
    Last modified: Fri Oct 1 15:02:55 PDT 2004