by Bryan Morgan
Programmers who have experience writing code in C or C++ will find that Java borrows heavily from these languages. The languages are so similar that the Java language specification (from Sun Microsystems) actually mentions that in any case where Java syntax is not specifically explained, the ANSI C specification should be consulted. Although the previous chapters introduced the Visual J++ development environment and object-oriented programming, you still need to learn basic Java syntax before you can write full-featured programs. This chapter introduces the elements of the Java language. Then you will be ready to create Java programs, as you will do in Chapter 6 "Creating Programs with Java."
Like all programming languages, Java executes commands in the form of statements that tell the computer what to do. Programmers can use three basic forms of variables to perform work within a statement:
As you have already seen, a set of statements grouped together can be used to form a method, which in turn (when grouped with variables) can form a Java class. This chapter starts out by describing the data types, operators, and control flow statements of the Java language, then discusses arrays and classes, and concludes with the creation of a class.
Every Java program is made up of statements that are used to accomplish tasks. All code used to form a statement is case sensitive in Java, which means that you can use two similar variables (for example, I and i) to perform some task without receiving a compiler error. Like statements in C and C++, every Java statement must end with a semicolon (;). Leaving a semicolon off the end of a statement can lead to horrific amounts of compiler errors (depending on which compiler you use). Even if you have never worked with a language that terminates statements in this way, you can be sure that over time, terminating statements with semicolons will become an automatic task.
The following statements are all legal Java statements:
Font theFont = new Font("Helvetica", Font.BOLD, 20);
String String1, String2;
if (evt.target instanceof Button) theList.addItem(theText.getText());
import java.awt.Button;
package visual.jplusplus;
Notice that four of the five statements consist of a single line of text terminated with a semicolon. These statements are known as single statements. The if statement is known as a compound statement because it actually uses more than one line to accomplish its task. In this case the if statement checks to see if some condition has been met. If the condition is true, then some text is added to a list box on a screen.
Compound statements are statements that contain operations within a block that can be surrounded by braces ({...}). Examples of compound statements are the control flow statements (such as if...then...else, do...while, and for).
In some cases, a statement simply declares a variable or provides the compiler with information. It does not return any information to the program for later use. Examples of statements that do not return data are
float Frequency;-Declares a floating-point variable named Frequency.
import sun.debug.*;-Imports all of the classes within the sun.debug package.
paint(graph);-Calls a method named paint and passes the variable graph as an argument.
No truly useful program can possibly be written, however, without using statements that return values. These statements are also known as expressions. Here are some examples of useful expressions:
float Frequency = 5.538;-Declares a floating-point variable named Frequency and initializes it to 5.538.
if (checkVal = 999)...-Compares the variable checkVal to the number 999. If this comparison is true, it returns a boolean true to the if statement.
return true;-Exits a method and returns a value of true to the calling method.
All modern programming languages provide ways for programmers to leave helpful notes, reminders, or documentation within their source code. These reminders are known as comments. Comments are not actually statements because they are skipped by the compiler and do not exist at runtime. Although the compiler ignores comments, you-the software developer-should not ignore them. Java provides developers with three separate ways to add comments to their code. (With so many options, you have no excuse for not properly documenting your software!) The following symbols are used to denote comments in Java:
Two things should be noted about comments:
For instance, the following lines show some invalid comments:
int I = 3; /* We left one hanging! -->*/ /* //This is a valid comment, but the code is ignored! int I = 3;
The next two snippets are perfectly valid comments (although not particularly helpful):
int I= 3; /* Set I = 3 */
and
/*
Created by Bryan Morgan
Special thanks to:
Dave Matthews Band,
Oasis,
and, of course ... caffeine
*/
You should always make sure that your comments are for someone else (or for you at a later date) to understand.
The Java language specification details exactly which characters can and cannot be used to create identifiers (such as variable names, method names, and so on). The basic rules for identifiers are
The Java language specification lists a group of keywords that
can be used only for their intended purpose as listed in the specification.
These keywords are listed in Table 5.1.
| Abstract | continue | for | new | switch |
| boolean | default | goto | null | synchronized |
| break | do | if | package | this |
| byte | double | implements | private | threadsafe |
| byvalue | else | import | protected | throw |
| case | extends | instanceof | public | transient |
| catch | false | int | return | true |
| char | final | interface | short | try |
| const | finally | long | static | void |
Veteran C and C++ programmers should feel comfortable with Java's syntax. C++ programmers, in particular, have a very short learning curve because Java's object-oriented features are so similar to those of C++. However, C and C++ both use keywords to provide compiler directives. The following are some examples of these directives:
#ifdef
#define
#pragma
#include
Java was intentionally designed to remove the more complicated or redundant features of C++ without sacrificing any of its power. In doing so, some features of other languages were borrowed, and many other features were ignored. For instance, C and C++ use the #include directive to specify header files containing variable, structure, and function specifications. Java source files contain only Java classes. All code used to create class members is contained within that class, which means that you don't have to maintain separate header files. C or C++ programmers that fault Java for deleting certain syntactical elements should remember that Java is still a full-featured, extremely powerful language. In short, anything that can be coded in C/C++ can be written with Java.
As was discussed in Chapter 3 "Object-Oriented Programming with Java," Java classes can contain member variables that coexist with class member methods. Variables can also be created within a method, and a variables scope extends within that method only. Variables can be any one of three types: an array, a class, or a Java primitive data type. Programming with array and class variables is discussed later in the chapter; this part of the discussion focuses on the eight primitive data types included with the Java language.
You can use these data types in a program without allowing for
the coding overhead associated with Java classes. (All Java classes
must specifically be created and imported by the programmer. Otherwise,
they are unknown entities to the Visual J++ compiler.)
| Note |
If you have ever done cross-platform software development, you probably know that data type sizes can change between platforms. For instance, in a 16-bit Windows program written in C, an int is a 2-byte value. For a UNIX program written in C, an int is a 4-byte value. Java, as part of its platform-independent philosophy, guarantees that all primitive data types will be the same size regardless of the underlying platform. |
Table 5.2 describes the eight basic data types.
| Data Type | Description |
| boolean | Boolean value that can be either true or false |
| byte | 8-bit signed value |
| char | 16-bit Unicode character |
| double | 64-bit floating-point value |
| float | 32-bit floating-point value |
| int | 32-bit signed value |
| long | 64-bit signed value |
| short | 16-bit signed value |
These primitive data types can be used to declare a variable for use within a program. Once the variable has been declared, the programmer can use it immediately without worrying about allocating or deallocating memory for that variable.
As you can see in Table 5.2, a boolean variable can be either true or false. Unlike many other programming languages, character values are not simply ASCII values. Instead, they are Unicode values. Because the ASCII character set restricts a character to one of 255 values, it is practically useless for displaying languages such as Chinese with its hundreds or thousands of possible characters. A Unicode character is a 16-bit value and can store one of 64K different values.
Like C/C++, Java allows programmers to use character escape codes when performing string operations. For example, you can use the following statement to print the string "Hello World!" to the screen:
System.out.println("Hello World!");
Now assume that you want to place a tab character at the end of this string so that your text would appear properly formatted on the screen. The best way to do so is by including a special character escape code at the end of that string. (The escape code for a tab character is "\t".) To print the string correctly with a tab out to the screen, use the following statement:
System.out.println("Hello World!\n");
The escape codes that can be used in Java are
At first glance, representing a backslash as "\\" or a double quote as "\" may seem odd. However, in a statement such as
System.out.println("Howdy,"pardner"");
the compiler would throw an error saying it expected a ");" after the second double quote because the second double quote closes a string literal value. The escape code enables the compiler to understand the following:
System.out.println("Howdy, \"pardner\"");
When this statement is executed, the following would appear on the user's screen:
Howdy, "pardner"
Nearly every Java data type can be converted to any other Java data type. The only exception to this rule is that a boolean value can not be converted (or "cast," as it is commonly called) to any other value. A general rule of thumb is that when going from a smaller bit-value type to a larger bit-value type (such as from byte to float), you don't need to make an explicit cast. An example of this type of cast is
byte TinyBite; float BigChomp; TinyBite = 17; BigChomp = 42.568; BigChomp = TinyBite;
These statements are perfectly legal. At the end of these statements, the value of BigChomp would be 17.00.
To go from a larger bit-value type to a smaller bit-value type, some data will be lost. Therefore, Java requires you to explicitly cast the type so that the compiler is sure that you know what you are doing. Here is the syntax to explicitly cast a type:
(new type name) oldvalue;
Here is an example of an explicit typecast:
byte TinyBite; float Bigchomp; TinyBite = 17; BigChomp = 42.568; TinyBite = (byte) BigChomp;
Following the execution of these statements, TinyBite would be equal to 42.
Now that you know about Java statements, variables, methods, and classes, you are ready to use operators in Java in order to perform operations on variables. Examples of the types of operations that can be performed are bit-shifts, mathematical operations, and logical comparisons.
All Java statements and expressions are made up of two things: operands and operators. To compare a Java statement to an English sentence, operands are the nouns (or direct objects) and operators are the verbs. For example, if X is divided by Y (X / Y), X and Y are the operands and the division sign is the operator. Operators assign values, do arithmetic, and perform comparisons. Every operator uses two operands (one on either side of it). The result of the operation is then forwarded for use in the next operation. The following statements illustrate operators at work:
int i = 2 * x; /* Two operators: "=" and "*" */ x += y; /* One operator: "+=" */ if ((x == 3) = True) ... /* Four operators: "(...)" (twice) and "==" and "=" */
The Java language specification describes the exact order in which multiple operations are evaluated. The evaluation order is discussed throughout this section.
The most commonly used assignment operator is the operator =, which takes the operand on its right side and assigns it to the operand on its left side. The following shows the assignment operator being used:
x=3; y=x; String name="Billy Bob";
| Note |
One interesting bit of information concerning operators is that the result of an operation becomes the operand of the next operation. For instance, in (x + y) + (a * b), the (x + y) operation occurs first, the (a * b) operation occurs second, and then the results of these two operations occurs third. This sequence applies also to multiple assignment operators within the same statement. Therefore, the statement a = b = c = d = e = f = g is a perfectly valid statement that assigns the value of the variable g to the variables a, b, c, d, e, and f. |
The remainder of the assignment operators borrow from C/C++. They are as follows:
Note that an operation such as a = a + b is not illegal. The assignment operators listed above simply make your code more compact and perform the same operations in a fewer number of keystrokes. In fact, these assignment operators are often referred to as the shorthand assignment operators.
The purpose of the comparison operators is to perform comparison operations between two operands. Typical operations include greater than, less than, and equal to. These operators are as follows:
| Caution |
When using the == operator to test for equality, make sure to always use two equals signs! A common coding mistake is to attempt to perform a comparison using one equals sign (=), which actually performs an assignment. Therefore, the code "if (x = 3)..." will always return true because when x is assigned 3, it returns a boolean True. The correct way to perform this comparison is "if (x == 3)..." |
Comparison operators are commonly used in control flow statements such as if-then-else branches or for loops. These statements are discussed later in this chapter.
Programmers with some training in digital logic will be familiar with computer circuitry such as AND, NAND, OR, and NOR gates. The premise behind these circuits is that two signals are input through the circuit to produce one output. For instance, an AND gate will take two binary 1s and produce a 1. It will also take a 1 and a 0 and produce a 0.
The Java language provides logical operators that perform exactly the same function on two operands. These operators are
The following code snippets illustrate the usage of the logical operators:
boolean a = true; boolean b = false; boolean result; result = a && b; // Returns false, both a and b are tested result = a | b; // Returns true result = a || b; // Returns true, b is never evaluated result = a ^ b; // Returns true result = !a; // Returns false result = !b; // Returns true result = !(a && b) // Return true result = ((a ^ b) ^ b) && a; // Returns true
Notice that in the last two example statements, more than one operation actually occurs. In the next to last example, a & b is evaluated first and returns false. The NOT operator is then applied to it and returns true.
The final example statement has three separate evaluations. The internal parentheses-(a ^ b)-is evaluated first, which returns true. This expression is then XORed with b and returns true. This result is then ANDed with a to return a true result.
Regardless of your level of computer programming experience, you are undoubtedly familiar with most or all of the arithmetic operators (except for perhaps the modulus operator). Here is a list of arithmetic operators:
The modulus operator may be new to you and therefore requires some explanation. In short, a modulus operation returns the remainder of the division of two operands. Therefore, 14 mod 7 = 0 because 14 divided by 7 returns 2 (with no remainder). Likewise, 100 mod 40 = 20 because 100 divided by 40 returns 2 with a remainder of 20. The following example snippets illustrate the use of these operators.
int I = 3; int J = 10; int result; result = I * J; // result = 30 result = I + J; // result = 13 result = I / J; // result = 0 result = J % I; // result = 1
Notice that in the I / J operation that the result is actually 0 because both I and J are integer values. When a floating-point value is stored to an int in Java, the result is truncated.
One additional use of the addition operator (+)in Java is to add strings together. Using the addition operator, the statement
Name = "George" + " Washington";
would result in the Name variable equaling "George Washington". This is much simpler than using methods to concatenate two strings together and greatly improves the readability of code. Also note that the "+=" assignment operator can be used to add a string together such as in the following sample code:
Name = "George"; Name += " Washington";
These two statements would return the same result as the previous single-line statement returned.
The final set of operators are the bitwise operators, which perform what engineers lovingly refer to as "bit fiddling." In other words, these operators do not operate on the operand's decimal or string values; instead, bitwise operators work at the actual binary level (that is, a byte value of 8 isn't treated as an 8-it's treated as a 00001000.) Before discussing what the operators actually are, the following examples should help explain their purpose.
To perform a bitwise AND, the binary values of two operands are ANDed together, bit by bit. Keeping in mind that 0 AND 0 = 0, 0 AND 1 = 0, and 1 AND 1 = 1, then:
00001000 AND 00000100 = 00000000
Likewise
10001001 AND 11101000 = 10001000
To perform a bitwise OR, the binary values of two operands are ORed together, bit by bit. Keeping in mind that 0 OR 0 = 0, 0 OR 1 = 1, and 1 OR 1 = 1, then:
00001000 OR 00000100 = 00001100
and
10001001 OR 11101000 = 01110001
A bit shift is exactly what it sounds like. All bits are moved over one position in the direction of the shift. The bit on the end that is "bumped off" is shifted around to the opposite end of the chain. For instance, if 00001000 were left shifted, the value would become 00010000.
The Java operators for bitwise operations were borrowed directly from C and C++. They are as follows:
The bitwise operators are extremely useful in operations in which a binary value may represent a number of different channels or inputs and a variety of these values need to be manipulated in some way.
Just as mathematical operations are evaluated in terms of precedence, operators as a whole are also evaluated by the Visual J++ compiler from highest to lowest precedence. The Java operators are evaluated in the following order (from highest to lowest):
. [] () ++ - ! ~ instanceof * / % + - <;<; >;>; >;>;>; <; >; <;= >;= == != &; ^ | &;&; || ?: = op= ,
It is necessary to define an operator evaluation order so that the compiler will know which portions of a statement to execute first. As an example, the following uses the +, ==, and || operators in a single statement:
boolean Value = x+y==6 || x+y==5;
In this statement, the + operator is evaluated first, followed by ==, and then ||, according to the operator evaluation order shown earlier. If some order was not defined for Java, the compiler would simply evaluate statements from left to right. In this situation, you could receive a far different result than the desired one.
Before examining control flow statements in Java, take note that any operators not mentioned above are identical to their corresponding operators in C or C++. Examples of operators not discussed in the following sections are the ternary (?:) and bitwise complement (-) operators.
The Java language supports the exact same type of control flow statements that are found in C and C++. These are the if conditional statement, the while, do-while, and for loops, and the switch statement. Some subtle differences do exist between the Java and C implementation, however. In some cases these differences give the J++ programmer additional control; in other cases J++ is more restrictive than C or C++.
The basic concept behind a control flow statement is controlling the flow of a program. Although the simple statements discussed so far execute only one line of code at a time, control flow statements contain multiple statements within a block of code surrounded by braces ({ ... }). In general, all of these "block" statements check for a boolean condition in order to execute the block of code. In the case of loops, the condition is checked each time through the loop. Once the condition returns false, the loop exits. Remember that the condition being checked must return a boolean value. Some languages, such as C, allow this conditional check to simply return any value (an integer, float, and so on). Java forces this condition to be either true or false.
One other difference between the C language syntax and Java's is that any variable declared for use within a loop cannot be used outside of that loop's statement block. Consequently, the final statement in these pseudo-statements is invalid:
for (int i = 0; i<100; i++)
{
.
.
.
}
i = 5;
One programming construct that is no doubt familiar to programmers is the if statement. This statement can be found in virtually every programming language. The if statement has two basic steps:
The syntax for the if statement is as follows:
if (boolean condition)
{
/* block of statements */
}
else if (boolean condition)
{
/* block of statements */
}
else if (boolean condition)
{
/* block of statements */
}
.
.
.
else
{
/* block of statements */
}
The else clause at the end if is optional. This clause is used to provide a default statement block should all of the preceding ifs prove to be false. In some cases, no default processing is required. (The same principle also applies to else if... statements.) Notice also that the block of statements within each clause is surrounded by braces ({...}). In situations where the block of statements is a single line, the braces are not required. The removal of the braces surrounding a block of code helps reduce the size of the code you have to stare at on the screen.
The if statement is demonstrated with two samples: one invalid and one valid. The following sample is invalid because the condition returns an integer value, not a boolean:
int i = 75;
int j = 120;
if (i * j)
System.out.println("Greater than zero!");
else
System.out.println("Equals zero!");
In C, i * j returns an integer value, which satisfies a condition. In Java, a boolean must be returned here. Therefore, the proper way to test this statement to see if it is greater than zero is
int i = 75;
int j = 120;
if (i * j > 0)
System.out.println("Greater than zero!");
else
System.out.println("Equals zero!");
Listing 5.1 makes full use of the if statement
to execute entire blocks of code. This listing uses the String
and Float classes, which have not been covered yet. Unlike
all other classes, a String object can be assigned a
value without the programmer needing to explicitly allocate memory
for it. The Float class is useful for converting floating-point
values to other data types.
| Note |
You will learn later in this chapter that for every primitive data type, Java also provides a corresponding class. These classes provide helper methods that are useful when dealing with the primitive data types. While C and C++ simply include helper functions such as floattostr() as global functions, everything in Java must be in a class. Therefore, classes that correspond to each data type are part of the java.lang package. |
In addition to noting the class types when you read Listing 5.1, you will see that you can already begin to build a fairly powerful group of statements. Control flow statements allow you to accomplish many complicated tasks in a relatively small amount of code.
Listing 5.1. Calculating the area of a shape.
String shape;
int x = 10;
int y = 5;
int radius = 25;
float area;
float circumference;
shape = "Circle";
if (shape.equals("Square"))
{
area = x * x;
circumference = 4 * x;
System.out.println("The area is " + Float.toString(area) + "\n");
System.out.println("The circumference is " + Float.toString(circumference)
+ "\n");
}
else if (shape.equals("Circle"))
{
area = (float)(3.14 * radius * radius);
circumference = (float)(3.14 * 2 * radius);
System.out.println("The area is " + Float.toString(area) + "\n");
System.out.println("The circumference is " + Float.toString(circumference)
+ "\n");
}
else if (shape.equals("Rectangle"))
{
area = x * y;
circumference = (2 * x) + (2 * y);
System.out.println("The area is " + Float.toString(area) + "\n");
System.out.println("The circumference is " + Float.toString(circumference)
+ "\n");
}
else
System.out.println("Don't known how to compute!!\n");
Listing 5.1 uses the if statement to test the equality of a String variable and a character string value. If the two are equal, then the area and circumference are computed and printed out to the console.
The while loop is used to continually execute a statement block while a condition is true. If the condition is initially false, the statement block is never executed. This is what separates the while loop from the do-while loop. In a do-while loop, the statement block is executed once before the condition is initially tested.
The while loop in Java looks like the one found in C:
while (boolean condition)
{
/* block of statements */
}
Listing 5.2 uses a while loop to continually print the area of a rectangle out to the console as long as that area stays less than 200.
Listing 5.2. Incrementally calculating the area of a square.
int x = 5;
int y = 5;
float area = (float)0.0;
while (area < 200.0)
{
area = x * y;
System.out.println("The area is " + Float.toString(area) + "\n");
x++;
y++;
}
This while loop should print out until x and y each are 15. At that point 15 * 15 = 225 and the loop will end. In other words, the output will be
The area is 25 The area is 36 The area is 49 The area is 64 The area is 81 The area is 100 The area is 121 The area is 144 The area is 169 The area is 196 The area is 225
The do...while loop performs a similar function to the while loop. The only difference is that the do...while loop tests the condition at the end of the statement block. Therefore, even if the condition is false, the loop will execute at least one iteration instead of simply exiting before executing the statements within the loop. The do...while loop looks like this:
do
{
/* block of statements */
}
while (boolean condition);
Listing 5.3 uses the do...while loop to print out the area of a rectangle. (This listing is similar to Listing 5.2.)
Listing 5.3. Using a do...while loop to calculate area.
int x = 15;
int y = 15;
float area = 0.0;
do
{
area = x * y;
System.out.println("The area is " + Float.toString(area) + "\n");
x++;
y++;
}
while (area < 200.0);
This loop will print out the following:
The area is 225
The for loop is used to loop through a block of statements as long as a condition is true. This loop is, in concept, the same as a while loop except that a for loop allows the programmer to specify how to increment values within the loop. A basic for loop has three parts:
The for loop separates each of these parts with a semicolon as follows:
for (init; condition; operation)
{
/* block of statements */
}
Listing 5.4 produces the exact same result as Listing 5.2. The only difference is that it uses a for loop to produce these results.
Listing 5.4. Using a for loop to calculate area.
for(int x = 5, int y = 5, float area = 0.0; area < 200.0; x++, y++)
{
area = x * y;
System.out.println("The area is " + Float.toString(area) + "\n");
}
This for loop should print out until both x and y are 15. At that point 15 * 15 = 225, and the loop will end. In other words, the output will be as follows:
The area is 25 The area is 36 The area is 49 The area is 64 The area is 81 The area is 100 The area is 121 The area is 144 The area is 169 The area is 196 The area is 225
Note that multiple operations occur in the different parts of the for loop. These operations are legal operations separated by the comma operator.
In some situations, one variable may require testing for 1, 2, or maybe 200 values. Although it is possible to perform if...else statements over and over again, Java provides a switch statement that is nearly identical to the switch found in C. The switch statement checks one variable for a variety of values. If any of these values are equal, J++ will execute the statement block associated with that value. Unlike C, however, this variable must be a "small" data type such as int, short, char, or byte. The syntax for the switch statement is
switch (test_expression)
{
case Value1:
/* block of statements */
break;
case Value2:
/* block of statements */
break;
case Value3:
/* block of statements */
break;
...
default:
/* block of statements */
}
The break keyword forces the execution to break out of the switch statement so that the values below do not even need to be evaluated. This break is optional. If you want to continue to check for conditions, omit the break. Listing 5.5 illustrates how to use the switch statement.
Listing 5.5. Checking for a condition using the switch statement.
char FirstLetter;
FirstLetter = 'b';
switch (FirstLetter)
{
case 'a':
System.out.println("Your name begins with an a.");
break;
case 'e':
System.out.println("Your name begins with an a.");
break;
case 'i':
System.out.println("Your name begins with an a.");
break;
case 'o':
System.out.println("Your name begins with an a.");
break;
case 'u':
System.out.println("Your name begins with an a.");
break;
default:
System.out.println("Your name doesn't begin with a vowel.");
}
The switch statement is convenient in some circumstances. However, the fact that this statement is limited to only evaluating ints, booleans, bytes, and chars keeps it from being useful all of the time. Remember that any operation that can be performed with a switch can also be performed with an if. In addition, the if statement can deal with any data type as long as the condition it checks returns a boolean value.
As you saw in the switch statement, sometimes you want to force the execution of your code to go in a specific direction. Java provides several statements that can be used to exit a loop or method or cause the execution of your program to jump to another location in your code.
The break keyword is used to "break out" of a loop or switch. Whenever the break keyword is encountered, execution automatically drops out of the loop and continues with the next line of code immediately following the end of the loop. The break keyword can also be used in conjunction with a label (see the explanation that follows) to force the execution to jump to the labeled statement.
The continue keyword is also used with loops. However, instead of forcing the loop to automatically end, continue causes only this iteration to end. Therefore, if your code is on iteration 51 (out of 100) and it encounters the continue keyword, the loop automatically jumps to iteration 52, ignoring any statements that remain in the body of the loop. The continue keyword can also be used in conjunction with a label to force the execution to jump to the labeled statement.
The return keyword's usage is identical to its usage in C or C++. This keyword forces the method to exit immediately. When used with an operand, the return keyword returns the operand as that method's result. Keep in mind that the type of the returned value must match the return type of the method.
Finally, the label keyword can be used with any statement in this manner:
label: statement
This label can then be used by a break or continue to cause program execution to jump to the labeled statement.
The label keyword concludes this discussion of the basic elements of the Java language. By now you should have a good understanding of the following topics:
You are now ready to tackle the final topics that will allow you to create an actual Java class in this chapter and a real, live Java program in Chapter 6 The remainder of this chapter discusses the usage of arrays in Java and, finally, how to put all of these elements together into a single Java class that can be used in any Java application.
On the surface, arrays in Java look identical to arrays in C. Individual array elements are accessed using an identifier within brackets such as [n], and all elements in an array must be of the same data type. Programmers familiar with C and C++ should remember that, in these languages, array syntax is in reality another way of doing pointer arithmetic. In C and C++ the zeroth element in an integer array is actually an integer pointer to an address in memory. The first element in an integer array is pointed to by the memory address plus one and so on. In other words, C/C++ compilers simply treats array variables as pointers. Treating arrays as pointers unfortunately allows the programmer to easily overrun the limits of an array's size, which results in an extra burden being placed on the programmer to make sure array index values are valid. "Overrunning" memory in this way also opens the door for security breaches of a system.
Java's designers recognized these pitfalls and changed the way arrays are handled. In Java, arrays are first class objects that can be created and passed to other methods as the objects they are. Like all other objects, memory for an array must be allocated before the array can be used. Failure to specifically create a new array object will result in an error. As the next section explains in more detail, the following two steps are required before you can use an object:
The most common syntax used to declare array variables is as follows:
Data type variable_name[];
The brackets after the variable name tell the compiler that an array will be allocated at some later point, and all elements in the array will be of the specified data type. Another way to write this statement is
Data type[] variable_name;
The following sample statements declare array variables of various data types:
int[][] numbers; float degrees[]; Color rainbow[]; String[] people; Button FormElements[];
As you can see, any type of data can be stored in an array. Although an array variable has been declared, it has not yet been instantiated. It is possible to instantiate the array variable either by allocating a new array of objects (filled with default values) or by filling the array with a group of values.
The new operator is used to assign an array object to the array variable. The new operator is discussed in detail in the next section, "Working with Objects"; using this operator will allocate the requested array of objects and assign that array to the variable you have declared. The following statements use the new operator to instantiate arrays for the variables listed previously:
numbers = new int[10][10]; degrees = new float[360]; rainbow = new Color[7]; people = new String[1000]; FormElements = new Button[10];
You can also use the = assignment operator to declare and create the array within a single statement. The following statements demonstrate this technique:
int[][] numbers = new int[10][10]; float degrees[] = new float[360]; Color rainbow[] = new Color[7]; String[] people = new String[1000]; Button FormElements[] = new Button[10];
When an array is created using the new operator, the array is filled with default values if the data type is a primitive data type. This value depends on the data type of the array, but it is a blank value (such as 0 for numbers, '\0' for char, and so on). However, for actual objects stored in an array (such as the array of Color objects created above), the array is filled with nulls. Each individual array element then needs to be set to an instantiated object. If this procedure is omitted, you will receive a "null pointer exception" at runtime if a null array element is accessed.
As mentioned earlier in this chapter, arrays can also be created by specifying a group of values to fill the array with. This method accomplishes two things at once:
These values are grouped within braces({...}) and must be separated by commas. The following statements initialize the array variables declared previously:
int[][] numbers = {{1, 2, 3, 4, 5}, {6, 7, 8, 9, 10}};
float degrees[] = {1.7, 4.1, 9.8, 11.2};
Color rainbow[] = {Color.red, Color.green, Color.blue};
String[] people = {"Katie Berry", "Baby Blankenbeckler", "The President"};
Button b1 = new Button();
Button b2 = new Button();
Button b3 = new Button();
Button FormElements[] = {b1, b2, b3};
Array elements can be accessed using an index value in the array. The following statements show array elements being accessed and used:
int x = numbers[0][5]; degrees[2] = 5.67; rainbow[0] = Color.black; String name = people[2]; Button btnExit = FormElements[0];
An important thing to remember when using arrays in Java is that they are zero based. Unlike C, if the array is of known size at compile time and you accidentally step past its upper limit, the compiler will flag this error and report it to you. In C, most compilers ignore errors of this type; they eventually show up as segmentation faults (or GPFs, to Windows programmers) at runtime. As an example, the following statements would generate a compile time error:
float degrees[] = new float[5];
degrees = {0.0, 1.0, 2.0, 3.0, 4.0};
float temp = degrees[8];
That example may seem obvious. If only five elements were allocated, trying to access the eighth element should cause a compilation error. However, suppose that the array was dynamically allocated at runtime, as in the following example:
int ArraySize = GetUserInput(); float degrees[] = new float[ArraySize]; float temp = degrees[8];
If the GetUserInput() method returned a value of 50, then the assignment in the last statement would execute just fine. However, if the GetUserInput() method returned a value of 3, then the final assignment would cause an exception. This error could not be caught at compile time because the ArraySize variable is assigned dynamically at runtime.
During the discussion of arrays, I mentioned that arrays are actually objects "beneath the surface" (unlike arrays in C, which are simply pointers to memory locations). In fact, arrays have properties, such as length, that can be examined at runtime. Like all other objects, they are created using the new operator. Although objects were introduced in Chapter 3 this section focuses on combining your knowledge of the Java class with your understanding of the syntax of the Java language.
As you will recall, classes in Java are made up of member variables and methods. A class can inherit from a parent class (known as the superclass) and can also choose to implement any number of interfaces. An interface is an object that specifies a related group of methods, but does not provide an implementation. Instead, the class that implements the interface must also provide the behavior for the methods. As a review, remember that the syntax for a Java class declaration is as follows:
Modifier class className [extends ParentClass] [implements Interfaces]
{
/* Provide class variables and methods */
}
The Modifier can be any of the following keywords:
The data type of the variables within the class can be any one of the eight primitive data types introduced in this chapter. In addition, the data type of a variable can be any Java class that was imported into this class's source file. Methods declared as members of a class can either be new to that class or can override a method that exists in the parent class. If the class implements an interface, all methods from that interface must be implemented by the class. As a refresher, the modifiers for variables and methods are as follows:
For more information on any of the keywords listed above, see Chapter 3
When a Java object is used as a variable in a method, its available member variables and methods can be accessed using the object name, the dot (.) operator, and the member being accessed. The syntax is as follows:
objectname.variablename
or
objectname.methodname(arguments)
If the variable or method being used by a class belongs to that class, the objectname is not required. Before discussing scope issues, examine Listing 5.6 to see variables and methods being accessed.
Listing 5.6. Accessing variables within a Java class.
class Computer
{
public int Price;
public Color CaseColor;
public boolean Tower;
public String Manufacturer;
public void create(String Company, int dolares)
{
int size;
Manufacturer = Company;
CaseColor = new Color(255, 255, 255);
Tower = true;
Price = dolares;
CaseColor = CaseColor.brighter();
CaseColor = CaseColor.darker();
size = Manufacturer.length();
}
}
Listing 5.6 illustrates a simple class named Computer. This class inherits from no other class and implements no interfaces. However, it does contain four member variables and one member method. Notice that the member variables can be accessed anywhere from within the class. When the brighter(), darker(), and length() methods are called, the objectname must be specified because CaseColor and Manufacturer variables are class data types themselves. If you omit the objectname from these method calls, the compiler will automatically think that these methods are members of the Computer class.
Assume now that you have another class called Laptop that derives from the Computer class in Listing 5.6. If a method in the Laptop class accesses the Price variable, it does not have to specify Computer.Price because the compiler is smart enough to realize that the Laptop class inherited the Price member variable. Listing 5.7 illustrates this concept.
Listing 5.7. Inheriting from the Computer class.
class Laptop extends Computer
{
float Weight;
int ScreenArea;
public float PricePerPound()
{
return (Price/Weight);
}
}
What happens if the Laptop class supplies its own Price variable? Which one will be accessed if another object uses the Laptop class? The answer is that if the objectname is not specified, then the variable or method is assumed to have come from the current class. If the variable or method is not found in the current class, the superclass (the class inherited from) is then examined. In cases where a method name or variable name is used in both the parent and child class, the super keyword is used to access the superclass's member. This technique is illustrated in Listing 5.8.
Listing 5.8. Accessing a superclass's member method.
class Laptop extends Computer
{
float Weight;
int ScreenArea;
public float PricePerPound()
{
return (Price/Weight);
}
public void create(String Company, int dolares, float Pounds)
{
super.create(Company, dolares);
Weight = Pounds;
}
}
Notice that by using the super keyword, we were able to reuse the functionality in the Computer superclass. Another way to write the Laptop.Create() method follows:
public void create(String Company, int dolares, float Pounds)
{
int size;
Manufacturer = Company;
CaseColor = new Color(255, 255, 255);
Tower = true;
Price = dolares;
CaseColor = CaseColor.brighter();
CaseColor = CaseColor.darker();
size = Manufacturer.length();
Weight = Pounds;
}
The code above has the same effect as the create() method in Listing 5.8. However, using the super.create() method call directly means that if the Computer.create() method is ever changed, it will be changed for all of its children. This reuse of code helps illustrate the beauty of object-oriented programming.
In a code snippet given earlier in the chapter, buttons were created using the following lines of code:
Button b1 = new Button(); Button b2 = new Button(); Button b3 = new Button();
In Listing 5.8, however, you created a new Color object like this:
CaseColor = new Color(255, 255, 255);
Notice that the two methods that are used here accept completely different parameters. The Button() method takes no arguments, whereas the Color() method accepts the Red, Green, and Blue color values that constitute an RGB color. Despite these differences, these two methods perform the same basic task; they both create new objects.
The type of method used to create an object is known as a constructor. A constructor for a new class can be created by creating a method that has the following properties:
Because design decisions occasionally require multiple ways to create an object, Java classes are allowed to have multiple constructors as long as each one accepts different arguments. For instance, the Color class supplies several different constructors. One of these constructors accepts three separate RGB color values; one constructor accepts a single integer and then breaks the first 24 bits of the integer into three separate values. The final Color constructor creates a color based on floating-point values that may range from 0 to 1.
The new operator has already been used several times in this chapter, but so far I haven't explained it. The new operator performs several tasks in Java:
Listing 5.9 rebuilds the Computer class that was created in Listing 5.6. The class created in Listing 5.6 contained a method named create() that performed the same function that a constructor should.
Listing 5.9. Creating a constructor for the Computer class.
class Computer
{
public int Price;
public Color CaseColor;
public boolean Tower;
public String Manufacturer;
Computer(String Company, int dolares)
{
int size;
Manufacturer = Company;
CaseColor = new Color(255, 255, 255);
Tower = true;
Price = dolares;
CaseColor = CaseColor.brighter();
CaseColor = CaseColor.darker();
size = Manufacturer.length();
}
}
Now that the Computer class contains a constructor method, we could allocate a new Computer this way:
Computer MyTool = new Computer("DELL", 2500);
| Note |
Although constructors are a convenient way for the class designer to initialize a class's values, constructor methods are not required. If a class is created with the new operator, and no constructor exists, memory for the object is still allocated. However, all variables within the constructor will be set to their default values (0 for numbers, false for booleans, null for objects, and so on). |
The new operator must be used in conjunction with an object's constructor. Likewise, a constructor must be called in conjunction with the new keyword and cannot be called as a normal method except for one special case.
That special case occurs when an object needs to call the constructor of the superclass. This occurrence is common, and in most cases is recommended. By calling the constructor of the superclass first, you can ensure that any initialization that the constructor needs to perform will be accomplished. Then, after it has been called, you can initialize your class as you see fit. To call the superclass's constructor, use the super keyword with the parameters of that class's constructor. The super keyword has to be used because, as you recall, a constructor cannot be called by name as a standard method call. In Listing 5.8, the create() method of the Laptop class called the create() method of the superclass Computer. Listing 5.10 modifies the Laptop class by adding a constructor and calling the superclass's constructor within the Laptop constructor.
Listing 5.10. Calling a superclass's constructor.
class Laptop extends Computer
{
float Weight;
int ScreenArea;
public float PricePerPound()
{
return (Price/Weight);
}
Laptop(String Company, int dolares, float Pounds)
{
super(Company, dolares);
Weight = Pounds;
}
}
Although the Laptop constructor does have some initialization of its own to do, it first calls the constructor inherited from the Computer class. This constructor initializes all of the Computer class's member variables. The only function left for the Laptop class is to initialize its Weight variable.
Now that you know how to create an object, you may be wondering how to destroy that object when you are finished with it. Programmers in C, C++, Delphi, and any other language that allows the programmer to allocate memory for objects will be glad to learn that Java does not require you to deallocate that memory when you are done with an object. In fact, Java does not have a "deallocate," "free," or "destroy" keyword because this language uses a garbage collection scheme to determine when an object is no longer being used. For more information on garbage collection in Java, and the Windows Virtual Machine for Java in particular, see the section "Taking Out the Trash Using Garbage Collection" in Chapter 4 "Understanding the Java Base Platform." Java does allow programmers to perform cleanup tasks, however, using a finalize() method.
The finalize() method will be called whenever an object is deallocated by the Java Virtual Machine. Please note that only the JVM really knows when an object is truly going to be deallocated. Therefore, don't assume that a finalize() method has been called just because an object "went out of scope," or is no longer in use. A finalize() method can be created in any class using the following syntax:
void finalize()
{
/* Insert cleanup code here! */
}
Unlike constructors (which can be called by any method as long as they are public), finalize() cannot be called like any normal method. Instead, the finalize() method is called by the Java Virtual Machine when the object is freed from memory. The vast majority of classes do not even supply a finalize() method, but it can be useful in some instances to verify that an object has been removed from memory.
Assume for the moment that you are writing an application that will display colors on a screen. For this application, you have created several classes including a Pixel class and a PixelColor class. The Pixel class represents a pixel on the screen and includes a method named Draw(). Here is a snippet of the imaginary Pixel class:
class Pixel
{
...
public void Draw(PixelColor);
...
}
The PixelColor class represents the color of a specific pixel. This PixelColor class performs various operations; it also contains a Pixel object that represents the actual pixel that will be set to this PixelColor. The following example shows a portion of the PixelColor class:
class PixelColor extends Color
{
...
Pixel thePixel;
...
}
Now suppose that you want to call the Pixel.Draw() command and pass the current PixelColor object to it. You wouldn't be able to do so using what you have learned so far. The following code snippet would not compile because it tries to pass a class name, not a created object:
thePixel.Draw(PixelColor);
Fortunately, Java provides the this keyword for exactly this purpose. A class can use the this keyword to refer to itself.
Another possible situation in which the this keyword could be used is when a constructor wants to call another constructor within the class. (Remember, an object's constructor can't be called by name within that object's own methods!) A class's own constructors often need to be called within classes that supply multiple constructors that differ only by one or two arguments. Instead of duplicating code in each constructor, one "base" constructor can perform initializations required by all of the other constructors. Then each individual constructor can call the base constructor before continuing with its own initialization. The this keyword is also required when a local variable within a method has the same name as a class member variable. In addition, the this keyword can always be used to refer to the class instance variable, although you should try to avoid this problem altogether by simply giving method variables names that won't conflict with class variables. Listing 5.11 demonstrates these situations.
Listing 5.11. Using the this keyword to refer to an object.
class PixelColor extends Color
{
int red;
int green;
int blue;
boolean flashing;
PixelColor(int red, int green, int blue, boolean flashing)
{
this(red, green, blue);
this.flashing = flashing;
}
PixelColor(int red, int green, int blue)
{
this.red = red;
this.green = green;
this.blue = blue;
flashing = false;
}
}
Because the constructors accept arguments whose names are the same as the class member variables, this must be used to differentiate between the two. The this keyword is also used to call the PixelColor (int, int, int) constructor from within the PixelColor (int, int, int, boolean) constructor.
Every now and then, you may be required to determine the class of an object at runtime. You can use either of two methods using Java. The first method can be applied to any Java class, regardless of its derivation. This method uses the instanceof operator, which returns a boolean value and is used in the following manner:
boolean_variable = object instanceof classname
For example, the following statement could be used to determine if an object variable is of type Frame:
isFrame = ScreenBox instanceof Frame;
Another possible way to determine the class of an object requires that the object be derived from the Object class found in the java.lang package. Nearly all classes in the standard Java libraries are derived from Object, and this class provides some extremely useful functionality. However, it is not required that a new class derive from the Object class.
The java.lang.Object class contains a method named getClass() that returns a class of type java.lang.Class. The Class class (pardon the redundancy) includes a method called getName() that will return a String containing the name of the class. The following statement uses this method to determine a class's name:
String classname = myClass.getClass().getName();
Keep in mind that nearly all classes available in the Visual J++ class library derive from the Object class. Therefore, functionality found in this class will be available to you when you use the majority of the Visual J++ classes.
Earlier in the chapter you learned how to typecast variables from one primitive data type to another, using the syntax:
variable2 = (typename) variable1;
The process required the type of variable2 to be the same as the typename used within the parentheses. The type of an object can also be cast to another type with one restriction: Both objects must share a common class through inheritance. If both objects share a common class, the member variables within the class on the left side of the assignment operator will be set equal to the member variables within the class on the right side of the assignment operator. For instance, if you derived a class named PDA and inherited it from Computer, you could typecast a PDA class to a Laptop class like this:
PDA myNewton; Laptop myLaptop; ... myNewton = (Computer) myLaptop;
After the above statements have been executed, all of the member variables in myNewton will be set equal to those like variables in myLaptop.
The Java language is similar in many respects to C++. The majority of the operators, keywords, and syntax were borrowed from C++, although many additional, more esoteric features were left out for simplicity's sake. Like C++, Java uses the concept of a class to encapsulate variables and methods. Unlike C++, a class can inherit only from one other class (called single inheritance). Java also provides the concept of an interface so that classes can, in effect, inherit functionality from multiple sources. Interfaces are groups of methods that are not implemented. When a class implements an interface, the class implements a "contract" with the interface by implementing all of its methods.
Java operators can be broken into several primary groups: comparison, logical, arithmetic, assignment, and bitwise. Like C++, Java includes a for loop, do...while loop, while loop, if statement, and switch statement. These statements are used to control code flow.
Arrays are dynamically allocated objects that contain rows and columns of data. An array's size can be determined at runtime, but an element within an array must be initialized before that element can be accessed. Memory for objects is also allocated at runtime using the new keyword and, in some cases, a class's constructor. Memory for all objects in Java does not need to be freed up because of the garbage collection performed by the Java Virtual Machine.
All the primary elements of the Java language have now been introduced. Chapter 6will formally acquaint you with the Visual J++ compilation process. The knowledge gained in this book's first five chapters will be used throughout the remainder of the book to build Java applets and applications using Visual J++.