Previous Page TOC Index Next Page Home


Day 5

Arrays, Conditionals, and Loops

by Laura Lemay

Although you could write Java programs using what you've learned so far, those programs would be pretty dull. Much of the good stuff in Java or in any programming language results when you have arrays to store values in and control-flow constructs (loops and conditionals) to execute different bits of a program based on tests. Today, you'll find out about the following:

Arrays

Arrays in Java are different than they are in other languages. Arrays in Java are actual objects that can be passed around and treated just like other objects.

Arrays are a way to store a list of items. Each slot of the array holds an individual element, and you can place elements into or change the contents or those slots as you need to.

Arrays can contain any type of element value (primitive types or objects), but you can't store different types in a single array. You can have an array of integers, or an array of strings, or an array of arrays, but you can't have an array that contains, for example, both strings and integers.

To create an array in Java, you use three steps:

  1. Declare a variable to hold the array.

  2. Create a new array object and assign it to the array variable.

  3. Store things in that array.

Declaring Array Variables

The first step to creating an array is creating a variable that will hold the array, just as you would any other variable. Array variables indicate the type of object the array will hold (just as they do for any variable) and the name of the array, followed by empty brackets ([]). The following are all typical array variable declarations:

String difficultWords[];
Point hits[];
int temps[];

An alternate method of defining an array variable is to put the brackets after the type instead of after the variable. They are equivalent, but this latter form is often much more readable. So, for example, these three declarations could be written like this:

String[] difficultWords;
Point[] hits;
int[] temps;

Creating Array Objects

The second step is to create an array object and assign it to that variable. There are two ways to do this:

The first way is to use the new operator to create a new instance of an array:

String[] names = new String[10];

That line creates a new array of Strings with ten slots, containing elements. When you create a new array object using new, you must indicate how many slots that array will hold.

Array objects can contain primitive types such as integers or booleans, just as they can contain objects:

int[] temps = new int[99];

When you create an array object using new, all its slots are initialized for you (0 for numeric arrays, false for boolean, '\0' for character arrays, and null for objects). You can also create and initialize an array at the same time. Instead of using new to create the new array object, enclose the elements of the array inside braces, separated by commas:

String[] chiles = { "jalapeno", "anaheim", "serrano",
    "habanero", "thai" };

Each of the elements inside the braces must be of the same type and must be the same type as the variable that holds that array. An array the size of the number of elements you've included will be automatically created for you. This example creates an array of String objects named chiles that contains five elements.

Accessing Array Elements

Once you have an array with initial values, you can test and change the values in each slot of that array. To get at a value stored within an array, use the array subscript expression:

myArray[subscript];

The myArray part of this expression is a variable holding an array object, although it can also be an expression that results in an array). The subscript expression specifies the slot within the array to access. Array subscripts start with 0, as they do in C and C++. So, an array with ten elements has ten array slots accessed using subscript 0 to 9.

Note that all array subscripts are checked to make sure that they are inside the boundaries of the array (greater than or equal to 0 but less than the array's length) either when your Java program is compiled or when it is run. It is impossible in Java to access or assign a value to an array slot outside of the boundaries of the array. Note the following two statements, for example:

String[] arr = new String[10];
arr[10] = "eggplant";

A program with that last statement in it produces a compiler error at that line when you try to compile it. The array stored in arr has only ten slots numbered from 0, the element at subscript 10 doesn't exist, and the Java compiler will check for that.

If the array subscript is calculated at run-time (for example, as part of a loop) and ends up outside the boundaries of the array, the Java interpreter also produces an error (actually, to be technically correct, it throws an exception). You'll learn more about exceptions later on next week and on Day 18.

How can you keep from overrunning the end of an array accidentally in your own programs? You can test for the length of the array in your programs using the length instance variable—it's available for all array objects, regardless of type:

int len = arr.length // returns 10

Changing Array Elements

To assign an element value to a particular array slot, merely put an assignment statement after the array access expression:

myarray[1] = 15;
sentence[0] = "The";
sentence[10] = sentence[0];

An important thing to note is that an array of objects in Java is an array of references to those objects (similar in some ways to an array of pointers in C or C++). When you assign a value to a slot in an array, you're creating a reference to that object, just as you do for a plain variable. When you move values around inside arrays (as in that last line), you just reassign the reference; you don't copy the value from one slot to another. Arrays of primitive types such as ints or floats do copy the values from one slot to another.

Arrays of references to objects, as opposed to the objects themselves, are particularly useful because it means you can have multiple references to the same objects both inside and outside arrays—for example, you can assign an object contained in an array to a variable and refer to that same object by using either the variable or the array position.

Multidimensional Arrays

Java does not support multidimensional arrays. However, you can declare and create an array of arrays (and those arrays can contain arrays, and so on, for however many dimensions you need), and access them as you would C-style multidimensional arrays:

int coords[][] = new int[12][12];
coords[0][0] = 1;
coords[0][1] = 2;

Block Statements

A block statement is a group of other statements surrounded by braces ({}). You can use a block anywhere a single statement would go, and the new block creates a new local scope for the statements inside it. This means that you can declare and use local variables inside a block, and those variables will cease to exist after the block is finished executing. For example, here's a block inside a method definition that declares a new variable y. You cannot use y outside the block in which it's declared:

void testblock() {
    int x = 10;
    { // start of block
      int y = 50;
      System.out.println("inside the block:");
      System.out.println("x:" + x);
      System.out.println("y:" + y);
    } // end of block
}

Blocks are not usually used in this way—alone in a method definition. You've mostly seen blocks up to this point surrounding class and method definitions, but another very common use of block statements is in the control flow constructs you'll learn about in the remainder of today's lesson.

if Conditionals

The if conditional, which enables you to execute different bits of code based on a simple test in Java, is nearly identical to if statements in C. if conditionals contain the keyword if, followed by a boolean test, followed by a statement (often a block statement) to execute if the test is true:

if (x < y)
    System.out.println("x is smaller than y");

An optional else keyword provides the statement to execute if the test is false:

if (x < y)
    System.out.println("x is smaller than y");
else System.out.println("y is bigger");

Technical Note: The difference between if conditionals in Java and C or C++ is that the test must return a boolean value (true or false). Unlike in C, the test cannot return an integer.

if (engineState == true ) 
    System.out.println("Engine is already on.");
else {
    System.out.println("Now starting Engine.");
    if (gasLevel >= 1)
        engineState = true;
    else System.out.println("Low on gas! Can't start engine.");
}

This example uses the test (engineState == true). For boolean tests of this type, a common shortcut is merely to include the first part of the expression, rather than explicitly testing its value against true or false:

if (engineState)
    System.out.println("Engine is on.");
else System.out.println("Engine is off.");

The Conditional Operator

An alternative to using the if and else keywords in a conditional statement is to use the conditional operator, sometimes called the ternary operator.

The conditional operator is a ternary operator because it has three terms.

The conditional operator is an expression, meaning that it returns a value (unlike the more general if, which only can result in a statement or block being executed). The conditional operator is most useful for very short or simple conditionals, and looks like this:

test ? trueresult : falseresult

The test is an expression that returns true or false, just like the test in the if statement. If the test is true, the conditional operator returns the value of trueresult; if it's false, it returns the value of falseresult. For example, the following conditional tests the values of x and y, returns the smaller of the two, and assigns that value to the variable smaller:

int smaller = x < y ? x : y;

The conditional operator has a very low precedence; that is, it's usually evaluated only after all its subexpressions are evaluated. The only operators lower in precedence are the assignment operators. See the precedence chart in Day 3's lesson for a refresher on precedence of all the operators.

switch Conditionals

A common programming practice in any language is to test a variable against some value, and if it doesn't match that value, to test it again against a different value, and if it doesn't match that one to make yet another test, and so on. Using only if statements, this can become unwieldy, depending on how it's formatted and how many different options you have to test. For example, you might end up with a set of if statements something like this or longer:

if (oper == '+')
  addargs(arg1, arg2);
else if (oper == '-')
   subargs(arg1, arg2);
else if (oper == '*')
   multargs(arg1, arg2);
else if (oper == '/')
   divargs(arg1, arg2);

This form of if statement is called a nested if, because each else statement in turn contains yet another if, and so on, until all possible tests have been made.

A common shorthand mechanism for nested ifs that you can use in some cases allows you to group tests and actions together in a single statement. This is the switch or case statement; in Java it's switch and behaves as it does in C:

switch (test) {
    case valueOne:     
            resultOne;
      break;
    case valueTwo:     
            resultTwo;
      break;
    case valueThree:   
            resultThree;
      break;
    ...
    default: defaultresult;
}

In the switch statement, the test (a primitive type of byte, char, short, or int) is compared with each of the case values in turn. If a match is found, the statement, or statements after the test is executed. If no match is found, the default statement is executed. The default is optional, so if there isn't a match in any of the cases and default doesn't exist, the switch statement completes without doing anything.

Note that the significant limitation of the switch in Java is that the tests and values can be only simple primitive types (and then only primitive types that are castable to int). You cannot use larger primitive types (long, float), strings, or other objects within a switch, nor can you test for any relationship other than equality. This limits the usefulness of switch to all but the simplest cases; nested ifs can work for any kind of test on any type.

Here's a simple example of a switch statement similar to the nested if shown earlier:

switch (oper) {
    case '+':
        addargs(arg1, arg2);
        break;
    case '*':
        subargs(arg1, arg2);
        break;
    case '-':
        multargs(arg1, arg2);
        break;
    case '/':
        divargs(arg1, arg2);
        break;
}

Note the break statement included in every line. Without the explicit break, once a match is made, the statements for that match and also all the statements further down in the switch are executed until a break or the end of the switch is found (and then execution continues after the end of the switch). In some cases, this may be exactly what you want to do, but in most cases, you'll want to make sure to include the break so that only the statements you want to be executed are executed.

One handy use of falling through occurs when you want multiple values to execute the same statements. In this instance, you can use multiple case lines with no result, and the switch will execute the first statements it finds. For example, in the following switch statement, the string "x is an even number." is printed if x has values of 2, 4, 6, or 8. All other values of x print the string "x is an odd number."

switch (x) {
    case 2:
    case 4:
    case 6:
    case 8: 
       System.out.println("x is an even number.");
       break;
    default: System.out.println("x is an odd number.");
}

for Loops

The for loop, as in C, repeats a statement or block of statements some number of times until a condition is matched. for loops are frequently used for simple iteration in which you repeat a block of statements a certain number of times and then stop, but you can use for loops for just about any kind of loop.

The for loop in Java looks roughly like this:

for (initialization; test; increment) {
    statements;
}

The start of the for loop has three parts:

The statement part of the for loop is the statements that are executed each time the loop iterates. Just as with if, you can include either a single statement here or a block; the previous example used a block because that is more common. Here's an example of a for loop that initializes all the values of a String array to null strings:

String strArray[] = new String[10];
int i; // loop index
for (i = 0; i < strArray.length; i++)
    strArray[i] = "";

Any of the parts of the for loop can be empty statements, that is, you can simply include a semicolon with no expression or statement, and that part of the for loop will be ignored. Note that if you do use a null statement in your for loop, you may have to initialize or increment any loop variables or loop indices yourself elsewhere in the program.

You can also have an empty statement for the body of your for loop, if everything you want to do is in the first line of that loop. For example, here's one that finds the first prime number higher than 4000:

for (i = 4001; notPrime(i); i += 2)
    ;

Note that a common mistake in C that also occurs in Java is to accidentally put a semicolon after the first line of the for loop:

for (i = 0; i < 10; i++);
    System.out.println("Loop!");

Because the first semicolon ends the loop with an empty statement, the loop doesn't actually do anything. The println function will be printed only once, because it's actually outside the for loop entirely. Be careful not to make this mistake in your own Java programs.

while and do Loops

Finally, there are while and do loops. while and do loops, like for loops, enable a block of Java code to be executed repeatedly until a specific condition is met. Whether you use a for loop, a while, or a do is mostly a matter of your programming style.

while and do loop, are exactly the same as in C and C++ except their test condition must be a boolean.

while Loops

The while loop is used to repeat a statement or block of statements as long as a particular condition is true. while loops look like this:

while (condition) {
    bodyOfLoop;
}

The condition is a boolean expression. If it returns true, the while loop executes the statements in bodyOfLoop and then tests the condition again, repeating until the condition is false. I've shown the while loop here with a block statement, because it's most commonly used, although you can use a single statement in place of the block.

Here's an example of a while loop that copies the elements of an array of integers (in array1) to an array of floats (in array2), casting each element to a float as it goes. The one catch is that if any of the elements in the first array is 0, the loop will immediately exit at that point. To cover both the cases wherein all the elements have been copied and an element is 0, you can use a compound test with the && operator:

int count = 0;
while ( count < array1.length && array1[count] !=0) {
    array2[count] = (float) array1[count++];
}

Note that if the condition is initially false the first time it is tested (for example, if the first element in that first array is 0), the body of the while loop will never be executed. If you need to execute the loop at least once, you can do one of two things:

The do loop is considered the better solution of the two.

do...while Loops

The do loop is just like a while loop, except that do executes a given statement or block until the condition is false. The main difference is that while loops test the condition before looping, making it possible that the body of the loop will never execute if the condition is false the first time it's tested. do loops run the body of the loop at least once before testing the condition. do loops look like this:

do {
    bodyOfLoop;
} while (condition);

Here, the bodyOfLoop part is the statements that are executed with each iteration. It's shown here with a block statement because it's most commonly used that way, but you can substitute the braces for a single statement as you can with the other control-flow constructs. The condition is a boolean test. If it returns true, the loop is run again. If it returns false, the loop exits. Keep in mind that with do loops, the body of the loop executes at least once.

Here's a simple example of a do loop that prints a message each time the loop iterates:

int x = 1;
do {
    System.out.println("Looping, round " + x);
    x++;
} while (x <= 10);

Here's the output of these statements:

Looping, round 1
Looping, round 2
Looping, round 3
Looping, round 4
Looping, round 5
Looping, round 6
Looping, round 7
Looping, round 8
Looping, round 9
Looping, round 10

Breaking Out of Loops

In all the loops (for, while, and do), the loop ends when the condition you're testing for is met. What happens if something odd occurs within the body of the loop and you want to exit the loop early? For that, you can use the break and continue keywords.

You've already seen break as part of the switch statement; it stops execution of the switch, and the program continues. The break keyword, when used with a loop, does the same thing—it immediately halts execution of the current loop. If you've nested loops within loops, execution picks up in the next outer loop; otherwise, the program merely continues executing the next statement after the loop.

For example, take that while loop that copied elements from an integer array into an array of floats until the end of the array or until a 0 is reached. You can test for that latter case inside the body of the while and then use a break to exit the loop:

int count = 0;
while (count < array1.length) {
    if (array1[count] == 0) {
        break;
    }
    array2[count] = (float) array1[count++];
}

continue is similar to break except that instead of halting execution of the loop entirely, the loop starts over at the next iteration. For do and while loops, this means the execution of the block starts over again; for for loops, the increment expression is evaluated and then the block is executed. continue is useful when you want to special-case elements within a loop. With the previous example of copying one array to another, you can test for whether the current element is 0 and restart the loop if you find it so that the resulting array will never contain zero. Note that because you're skipping elements in the first array, you now have to keep track of two different array counters:

int count1 = 0;
int count2 = 0;
while (count < array1.length) {
    if (array1[count] == 0) 
       continue;
    array2[count2++] = (float)array1[count++];
}

Labeled Loops

Both break and continue can have an optional label that tells Java where to break to. Without a label, break jumps outside the nearest loop (to an enclosing loop or to the next statement outside the loop), and continue restarts the enclosing loop. Using labeled breaks and continues enables you to break outside nested loops or to continue a loop outside the current loop.

To use a labeled loop, add the label before the initial part of the loop, with a colon between them. Then, when you use break or continue, add the name of the label after the keyword itself:

out:
    for (int i = 0; i <10; i++) {
        while (x < 50) {
            if (i * x == 400)
                break out;
            ...
        }
        ...
    }

In this snippet of code, the label out labels the outer loop. Then, inside both the for and the while loop, when a particular condition is met, a break causes the execution to break out of both loops.

Here's another example: the following program contains a nested for loop. Inside the innermost loop, if the summed values of the two counters is greater than four, both loops exit at once:

foo:
    for (int i = 1; i <= 5; i++)
        for (int j = 1; j <= 3; j++) {
            System.out.println("i is " + i + ", j is " + j);
            if ((i + j) > 4)
                break foo;
        }
System.out.println("end of loops");

Here's the output from this program:

i is 1, j is 1
i is 1, j is 2
i is 1, j is 3
i is 2, j is 1
i is 2, j is 2
i is 2, j is 3
end of loops

As you can see, the loop iterated until the sum of i and j was greater than 4, and then both loops exited back to the outer block and the final message was printed.

Summary

Today, you learned about three main topics that you'll most likely use quite often in your own Java programs: arrays, conditionals, and loops.

You learned how to declare an array variable, create and assign an array object to that variable, and access and change elements within that array.

Conditionals include the if and switch statements, with which you can branch to different parts of your program based on a boolean test.

Finally, you learned about the for, while, and do loops, each of which enable you to execute a portion of your program repeatedly until a given condition is met.

Now that you've learned the small stuff, all that's left is to go over the bigger issues of declaring classes and creating methods within which instances of those classes can communicate with each other by calling methods. Get to bed early tonight, because tomorrow is going to be a wild ride.

Q&A

Q: If arrays are objects, and you use new to create them, and they have an instance variable length, where is the Array class? I didn't see it in the Java class libraries.

A: Arrays are implemented kind of weirdly in Java. The Array class is constructed automatically when your Java program runs; Array provides the basic framework for arrays, including the length variable. Additionally, each primitive type and object has an implicit subclass of Array that represents an array of that class or object. When you create a new array object, it may not have an actual class, but it behaves as if it does.

Q: Does Java have gotos?

A: The Java language defines the keyword goto, but it is not currently used for anything. In other words, no, Java does not have gotos.

Q: I declared a variable inside a block statement for an if. When the if was done, the definition of that variable vanished. Where did it go?

A: In technical terms, block statements inside braces form a new lexical scope. What this means is that if you declare a variable inside a block, it's only visible and usable inside that block. Once the block finishes executing, all the variables you declared go away.

It's a good idea to declare most of your variables in the outermost block in which they'll be needed—usually at the top of a block statement. The exception might be very simple variables, such as index counters in for loops, where declaring them in the first line of the for loop is an easy shortcut.

You'll learn more about variables and scope tomorrow.

Q: Why can't you use switch with strings?

A: Strings are objects, and switch in Java works only for the primitive types byte, char, short, and int. To compare strings, you have to use nested ifs, which enable more general expression tests, including string comparison.

Q: It seems to me that a lot of for loops could be written as while loops, and vice versa.

A: True. The for loop is actually a special case of while that enables you to iterate a loop a specific number of times. You could just as easily do this with a while and then increment a counter inside the loop. Either works equally well. This is mostly just a question of programming style and personal choice.

Previous Page TOC Index Next Page Home