At this point you have enough information to write some extremely simple Java programs, but as you've seen they don't do very much. The really good stuff in Java, or in any programming language, results when you have arraysused to store
valuesand loop and conditional control-flow constructsused to execute different bits of a program based on a test criteria. Today, you'll find out about the following:
Arrays in Java are objects. Because arrays are objects they can be passed to other objects. In VJ++ programs, you treat arrays just like any other object.
Arrays are a means to store a list of related items. Each item you want to store in an array is held in an element. When you declare an array you give it a data type, variable name, and size. The data type
determines what kind of item the array will hold, the variable name is how you'll reference the array, and the size determines the number of elements in the array. Arrays can be used to store static information, like the days of the week, or variable
information, like user input from a Web page.
Any of the data types (primitive or objects) can be used to create an array, but an array declaration (and, therefore, the elements) can only contain one data type. For instance, you can have an array of integers, 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:
The first step to creating an array is to create a variable that will hold the array. Naming an array variable follows the same conventions as any other variable. The array name should indicate the type of item the array will hold. What makes an array
variable different (and, therefore, treated as an array variable) is the use of empty brackets. The following are all typical array variable declarations:
String sDaysOfTheWeek[];
Point pntHits[];
int iTemp[];
An alternate method of defining an array variable is to put the brackets after the data type instead of after the variable. So, the preceding three declarations could be written like this:
String[] sDaysOfTheWeek;
Point[] pntHits;
int[] iTemp;
It doesn't matter which one you use. Whichever way you do choose, keep it consistent. In all of the examples in this book, the first method of defining arrays will be used, where the brackets follow the array variable name rather than the data type.
The second step is to create an array object and assign it to the 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 sNames[] = new String[10];
That line creates a new array of Strings with 10 elements. When you create a new array object using this form (new), you must declare the array's size.
Array objects can contain primitive types such as integers or Booleans, just as they can contain objects:
int iTemp[] = new int[99];
When you create an array object using new, all its elements 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 all the elements of the array inside a set of braces, and separate each element with a comma:
String sDaysOfTheWeeks[] = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" };
Each of the elements you use to initialize the array must be of the same data type, as well as the same data type as the variable that holds that array. This means that if the data type is String, the elements must all be strings too.
At runtime, an array is automatically created; its size will be based upon the number of items used when it was declared. The preceding example creates an array of String objects named sDaysOfTheWeek that contains seven elements.
Once you have an array that is populated with initial values, you can use the values stored in the array elements just like you would any other variable. You can use an element as part of a conditional test, extract a value to use in a calculation or
to display on the screen, and change the value of the element. The difference between a variable (which can store only one value at a time) and an array variable is how you reference each element in the array to extract only the data contained in a
specific element.
To get at a value stored within an array, you must supply two pieces of information: the name of the array (surprise!) and the number of the element. The element number is called the array subscript. The subscript is enclosed in brackets and follows
the array name, like this:
sDaysOfTheWeeks[subscript];
Arrays in Java are zero-based. This means that the first subscripted element is 0 and the last is one less than the size of the array.
The sDaysOfTheWeek part of this expression is the variable holding the array object. The subscript expression specifies the element to access within the array. Array subscripts are zero-based, meaning they start with 0, as they do in C and C++. So, an
array with 10 elements has ten array elements accessed by using subscripts 0 to 9. If you're not used to working with arrays (or those in another language), the point of Java arrays being zero-based is very important in a number of ways, for example:
Note that all array subscripts are checked at compile-time and runtime to make sure that they are inside the boundaries of the array: greater than or equal to 0, but one less than the array's length. In Java it is impossible to access or assign a value
to an array element that is outside of the boundaries of the array. Note the following two statements, for example:
String[] sArr = new String[10]; sArr[10] = "eggplant";
sArr[10] doesn't exist. The largest element in the sArr array is 9, which is one less than its size of 10. Any Java program with the sArr[10] statement in it will produce a compiler error.
If the array subscript is calculated at run-time (for example, as part of a loop) and references an element outside the boundaries of the array, the Java interpreter also produces an error, or, to be technically correct, it throws an exception. You'll
learn more about exceptions later next week and on Day 19.
How can you keep from accidentally overrunning the end of an array in your own programs? You can test for the length of the array in your programs using the length data member; it's available for all array objects, regardless of data type:
int len = sArr.length // returns 10
To assign a value to a specific array element, put an assignment statement after the array name expression:
iMyAarray[1] = 15; sSentence[0] = "The"; sSentence[10] = sentence[0] \\ Now sSentence[10] now equals, by reference,"The";
An important thing to note is that an array of objects in Java is an array of references to those objects. (It is similar in some ways to an array of pointers in C or C++.) When you assign a value to an element 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 element to the other, except for primitive data type arrays, such as
ints or floats that do copy the values from one element 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 element subscript.
Java supports multidimensional arrays the same way as C-style multidimensional arrays. You can think of a multidimensional array as an array of arrays, and those arrays can contain arrays, and so on, for however many dimensions you need. Visually,
whereas a single dimension array would resemble a one-column table, a multidimensional array would resemble more of a matrix, where there would be multiple columns. When you declare your array, you supply a size for each of the columns, and when you need
to reference an element, you need to supply multiple subscripts, since a specific element can now be in any of the columns.
int iCoords[][] = new int[12][12]; iCoords[0][0] = 1; iCoords[0][1] = 2; iCoords[1][5] = 3;
See the following matrix for a depiction of what the double subscripted iCoords array might look like.
[sub0] | [sub1] | [sub. . .] | [sub12] | |
[subscript0] | 1 | |||
[subscript1] | 2 | |||
[subscript2] | ||||
[subscript3] | ||||
[subscript4] | ||||
[subscript5] | 3 | |||
[subscript6] | ||||
[subscript7] | ||||
[subscript8] | ||||
[subscript9] | ||||
[subscript10] | ||||
[subscript11] |
The sizing parameters in multidimensional arrays don't have to be equal, for example, int iCoords[][] = new int[12][5];. You can size a multidimensional array to suit whatever your needs require.
A block statement is a group of statements surrounded by braces ({}). You can use a block anywhere a single statement would go. However, the block creates a local scope for the statements contained inside it. This means that you can declare and use
local variables inside a block, and those variables will cease to exist after the statements inside the block are finished executing. For example, here's a block inside a method definition that declares a new variable iY. Because iY is now local to the
block, you cannot use iY outside the block in which it was declared:
void testblock() { int iX = 10; { // start of block int iY = 50; System.out.println("inside the block:"); System.out.println("iX:" + iX); System.out.println("iY:" + iY); } // end of block System.out.println("iX:" + iX); System.out.println("iY:" + iY); \\ this line will produce a compiler error }
block statements are not usually used alone in a method definition. Up to this point, you've mostly seen blocks surrounding class and method definitions, but another very common use of a block is in the control flow constructs you'll learn about in the
remainder of today's lesson.
The if conditional enables you to execute different sections of code based upon the result of a comparison test. The Java if conditional 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 (iX < iY) System.out.println("iX is smaller than iY");
An optional else keyword provides the statement to execute if the test is false:
if (iX < iY) System.out.println("iX is smaller than iY"); else System.out.println("iY is bigger");
The difference between if conditionals in Java and in C or C++ is that, in Java, the result of the test must return a Boolean value, either true or false. Unlike in C, where the result of the test can also return an integer.
if (bEngineOn == true ) System.out.println("Engine is already on."); else { System.out.println("Now starting Engine."); if (gasLevel >= 1) bEngineOn = true; else System.out.println("Low on gas! Can't start engine."); }
The preceding example uses the test (bEngineOn == 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 (bEngineOn)System.out.println("Engine is on."); else System.out.println("Engine is off.");
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 can result only 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 a conditional expression that returns either true or false, just like a test in an if statement. If the test evaluates to being true, the conditional operator returns the value of trueresult; if it's false, it returns the
value of falseresult. For example, the following conditional expression tests the values of iX and iY, returns the smaller of the two, and assigns that value to the variable iSmaller:
int iSmaller = (iX < iY) ? iX : iY;
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 than conditional operators are the assignment operators. See the precedence
chart in Day 3's lesson for a refresher on precedence of all the operators.
A common programming practice is to test a variable against some value. If its value doesn't match the test value, test it again against a different test value, and if it doesn't match that one, make another test, and so on. Using only if statements,
this can become unwieldy, due to how the test expression is formatted and how many different test expressions you need to evaluate. For example, you might end up with a set of if statements like the following:
if (cOper == '+') addargs(iArg1, iArg2); else if (cOper == '-') subargs(iArg1, iArg2); else if (cOper == '*') multargs(iArg1, iArg2); else if (cOper == '/') divargs(iArg1, iArg2);
In other languages, single quotes (') or double quotes (") can both be used to delimit a string. In Java (as in C), single and double quote delimiters mean different things. A double quote is used to delimit a string, whereas a single quote is used to convert the single character inside the quotes to a data type word (a single byte representation of the character). Note also that, because a byte can only represent (store) a single character, the character inside the quotes can be only a single character or an escape sequence, for example, '\n'.
This form of an if statement is called a nested if because each else statement in turn contains yet another if, and so on, until all the possible tests have been made.
A common shorthand mechanism for avoiding nested ifs allows you to group test expressions and actions together into a single statement. This mechanism is the switch or case statement. Java only uses the keyword switch, and it 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, a simple primitive data type (byte, char, short, or int) is compared with each of the case values in turn, starting with the first. If a test evaluates to true (the test equaling the value), the statement or statements between
the case and break is executed. If no match is found after evaluating all of the case statements, the default statement is executed. The default statement is optional, so if none of the case statements evaluate to true and default doesn't exist, the switch
statement completes without doing anything.
Note that the significant limitation of switch in Java is that the tests and values can be only primitive types, and then only primitive types that are castable to int. You cannot use larger primitive types (for example, long or float), strings, or
other objects within a switch; you also cannot test for any relationship other than equality. This limits the usefulness of switch to all but the simplest comparisons, whereas nested ifs can work with any kind of test on any data type.
Here is a simple example of a switch statement similar to the nested if shown earlier:
switch (cOper) { case '+': \\ remember this is not a string comparision, but instead it's a word addargs(iArg1, iArg2); break; case '*': subargs(iArg1, iArg2); break; case '-': multargs(iArg1, iArg2); break; case '/': divargs(iArg1, iArg2); break; }
Note the break statement included at the end of every case. A break is used to jump out of the switch statement without executing any of the other statements, including any more case statements. Unlike some other programming languages that have an
assumed break when they reach another case statement, Java will continue to execute the statements inside the switch until a break is found or until the switch ends. At the very least, this will slow down the execution of your program; at worst, statements
might be executed unintentionally, for example:
char cOper = '+'; switch (cOper) { case '+': \\this evaluates to true addargs(iArg1, iArg2); \\ this executes \\no break here case '*': \\ this doesn't get evaluated at all subargs(iArg1, iArg2); \\ this executes too \\ no break here case '-': \\ this doesn't get evaluated at all multargs(iArg1, iArg2); \\so does this break; \\ this breaks you out of the switch case '/': divargs(iArg1, iArg2); break; }
Sometimes, this falling-through effect might be exactly what you want to do, but most times 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 different test 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 statement it finds. For example, in the
following switch statement, the string "iX is an even number." is printed if iX has values of 2, 4, 6, or 8. All other values of iX print the string "iX is an odd number.".
switch (iX) { case 2: case 4: case 6: case 8: System.out.println("iX is an even number."); break; default: System.out.println("iX is an odd number."); }
The for loop, as in C, repeats a statement or block of statements a given number of times until the test condition is satisfied. 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 statement that defines a for loop has three parts:
The statements part of the for loop are 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 strString array to null strings:
String strArray[] = new String[10]; int iLoopIndex; for (iLoopIndex = 0; iLoopIndex < strArray.length; iLoopIndex++) strArray[iLoopIndex] = "";
Any of the definition parts (for example, iLoopIndex) of the for loop can be empty statements; that is, you can simply use a semicolon as a placeholder for the missing expression or statement, and that part of the for loop will be ignored. If you use a
null statement in your for loop, you might have to initialize or increment the 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 (iPrimeSeed = 4001; notPrime(iPrimeSeed); iPrimeSeed += 2) ;
for (iX = 0; iX < 10; iX++); \\ends the loop performing nothing System.out.println("Loop!"); \\outside loop, prints only once
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, a while, or a do loop is mostly a matter of your programming
style.
while and do loops perform exactly the same as in C and C++ except that in Java, the test condition must be a boolean.
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 (bCondition) { // Body of Loop Statements }
The bCondition is a boolean expression. If it returns true, the while loop executes the statements in the body of the loop and then tests the condition again, repeating until the condition is false. As shown in the following example, the most common
use of a while loop is to execute a block of statements, although a single statement can be used also.
Here's an example of a while loop that copies the elements of an array of integers (iArray1) to an array of floats (iArray2), casting each element to a float as it goes. There's a catch, though: If any of the elements in the first array are 0, the loop
will immediately exit at that point. To cover both conditions wherein all the elements have been copied or an element might be 0, you can use a compound test with the && operator:
int iCount = 0; while ( iCount < iArray1.length && iArray1[iCount] !=0) { iArray2[iCount] = (float) iArray1[iCount++]; }
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 generally considered the better of the two solutions.
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 for the body of the loop to 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 { // The Body of the Loop } while (condition);
Here, the statements that are executed with each iteration are the body of the loop part. 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 iX = 1; do { System.out.println("Looping, round " + iX); iX++; } while (iX <= 10);
Here's the output based upon the preceding code fragment:
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
You'll notice in our examples that the do loop also contains a while loop. In most cases the code you write will include these "do-while" loops.
In all the loops (for, while, and do), the loop ends when the condition you're testing 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 with the statement after the switch construct. 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.
Although nesting levels can be very deep, it would probably be unwise to nest further than five levels. A general practice is to nest only three or four levels deep, otherwise your Java code becomes unmanageable.
For example, remember the while loop that copied elements from an integer array into an array of floats? It did this until the end of the array or until a 0 was reached (while (iCount < iArray1.length && iArray1[iCount] !=0)). If you wanted
to, you could test for the latter condition inside the body of the while loop and then use a break to exit the loop:
int iCount = 0; while (iCount < iArray1.length) { if (iArray1[iCount] == 0) { break; } iArray2[iCount] = (float) iArray1[iCount++]; }
continue is similar to break except that instead of halting execution of the loop entirely, the loop starts over at the next iteration. In do and while loops, this means the execution of the block starts over again; in for loops, the loop index is
incremented and then the block is executed. continue is useful when you want to ignore subsequent statements and simply continue with the next iteration of the loop. With the previous example of copying one array to another, you can test for whether the
current element is 0 and, if it is, restart the loop, so that the resulting array won't contain a zero. Note that because you're skipping elements in the first array, you now have to keep track of two different array index counters:
int iCount1 = 0; int iCount2 = 0; while (iCount < iArray1.length) { if (iArray1[iCount] == 0) continue; iArray2[iCount2++] = (float) iArray1[iCount++]; }
Both break and continue can have an optional label that tells Java where to break to. Without a label, break jumps outside the current loop to (if one exists) a loop the next level out, or to the next statement outside the current loop. Whereas,
continue restarts the current loop. Using labeled breaks and continues enables you to break outside nested loops or to continue a loop outside the current loop.
To create a labeled loop, simple add a label statement followed by a colon before the initial definition part of the loop. Then, when you need to jump out to a specific level, use break or continue, followed by the name of the label:
lblOut: for (int iX = 0; iX <10; iX++) { while (iY < 50) { if (iX * iY == 400) break lblOut; ... } ... } \\ statements to be executed when the break lblOut is executed or the loops naturally exit.
In this fragment of code, the label lblOut identifies the outer loop. Then, inside both the for and the while loop, when the particular condition is met, a break lblOut causes the execution to break out of both loops, instead of just the while loop.
Here's another example. The following code fragment contains a nested for loop. If the summed values of the two counters is greater than four, both loops are exited.
lblBothOut: for (int iX = 1; iX <= 5; iX++) for (int iJ = 1; iJ <= 3; iJ++) { System.out.println("iX is " + iX + ", iJ is " + iJ); if ((iX + iJ) > 4) break lblBothOut; } System.out.println("end of loops");
Here's the output from this code:
iX is 1, iJ is 1 iX is 1, iJ is 2 iX is 1, iJ is 3 iX is 2, iJ is 1 iX is 2, iJ is 2 iX is 2, iJ is 3 end of loops
As you can see, the loop iterated until the sum of iX and iJ was greater than 4, and then both loops exited to the statement following the outermost for block, causing the final message to be printed.
Labels, like GOTO statements in other languages can lend themselves to abuse. We're not about to get trapped into the age-old argument of whether GOTOs are good or GOTOs are bad. Suffice it to say that if you can't think of any other way out of a coding situation, use Labels.
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 enables 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, creating instances and class variables, defining methods, and learning how objects of these class types can communicate with each other. Get
to bed early tonight, because tomorrow is going to be a wild ride.
Q: If arrays are objects, you use new to create them, and they have an data member length, where is the Array class? I didn't see it in the Java class libraries.
A: Arrays are implemented sort 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 might 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 neededusually 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 acceptable 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.