As discussed in earlier chapters, Java programs contain two types of variables:
MyClass myVariable; String myString;
MyInterface myVariable;
Once you have declared a reference type variable, it can be assigned values as follows:
if (myObject == null) myObject = new MyClass(); ... myObject = null; // done with the object, free it up
myObject = new MyClass();
As discussed in Chapter 2, new allocates memory for the object, and assigns the address of that memory location to the reference variable. Parameters can be specified with the new operator, like this:
myObject = new MyClass(100, true, "My Name");
In this case, both the Java compiler and the Java runtime will search the class MyClass for a constructor with the number and type of parameters (signature) that matches those specified—one integer, one boolean, and one String. If found, this constructor is called. Otherwise, an error occurs.
Once you have declared and instantiated an object reference, you can perform operations on the object, including these:
myObject.limit = 100;
myObject.setLimit(100);
if (myObject == myOtherObject) ... else if (myObject != myYetAnotherObject) ... myNewObject = (myObject==myOtherObject) ? myObject : myOtherObject;
In summary, variables in Java are either primitive (or base) data types like integer or object references. These object references are, in fact, pointers to instances of classes. Arrays, which are covered here, are cases of object references that are used heavily in Java programs, and so have special support in the language.
Note |
There are no explicit pointers or pointer arithmetic in Java. This is unlike RPG, where pointers and pointer arithmetic is supported. Java just contains reference type variables, which are like pointers in that they hold memory addresses. However, you cannot increment, decrement, or otherwise mathematically manipulate these memory addresses; they can only be assigned and compared. This gives you most of the benefits of pointers but with added security, ease of use, and strong type checking. |
As a seasoned programmer, you know you need a raise. Sorry—we meant to say arrays. Both RPG and Java support arrays, but with some noticeable differences. Table 6.1 highlights the differences and similarities between the two languages in terms of their support for arrays.
RPG |
Java |
---|---|
One dimension only |
Multiple dimensions (no limit) |
Fixed in size at compile time |
Size set at runtime, but fixed once it is set |
Declaration time initialization (a compile-time array) |
Declaration time initialization |
Runtime initialization (a runtime array) |
Runtime initialization |
Pre-runtime initialization from a file |
|
(a pre-runtime array) |
No built-in support for initializing from a file |
Older-style "tables" |
Supplied Hashtable class for fast-lookup arrays, or new JDK 1.2 Arrays class in java.util with binary search methods |
Multiple occurring data structures |
Arrays of objects |
Dynamic memory APIs for sizable arrays |
Supplied Vector class for sizable arrays |
There is similar functionality in both languages, especially when we include not only Java arrays, but the additional utility classes Java supplies in the java.util package: Vector, Hashtable, and Arrays. Really, we have to give the edge to Java on this, though, because, as you will see, it has more array functionality and capability than RPG. The biggest difference is Java's support for multi-dimensional arrays. On the other hand, RPG has its little-used but wonderful support for pre-runtime arrays, which are initialized from a file just as the program first starts running. However, you will learn how you might code this functionality into Java yourself. RPG also has its "mature" support for tables, which have fallen out of favor since the introduction of arrays and multiple occurring data structures in RPG.
To refresh your memory, tables are like array fields but have names starting with "TAB" and are only indexable using the LOOKUP operation. Multiple occurring data structures are data structures that can be repeated, which make them effectively arrays of structures. Like tables, though, and unlike arrays, there is no explicit way to index into a multiple occurrence data structure. Rather, you have to implicitly index into it by using the OCCUR operation to identify and set the "current occurrence." While Java does not have an exact syntactic match for either tables or multiple occurrence data structures, you will soon see that hashtables are a good functional match for tables, and arrays of objects are a good functional match for multiple occurring data structures.
Java has explicit syntax for arrays, and generally they could be used for all of your requirements for repeating items of the same type. Indeed, as of JDK 1.2.0 (also called "Java 2"), you have even greater array support, in the form of a set of static helper methods in the Arrays class in java.util. This includes methods for searching, sorting, comparing, and filling (with the same value) arrays. However, Java also gives two other options beyond arrays: vectors and hashtables. Vectors are growable and shrinkable one-dimensional arrays. Hashtables are key-plus-value pairs, designed for easy storage of a value by key and efficient retrieval of a value by key. Arrays, the Arrays class, vectors, and hashtables are covered in this chapter.
If all this is not enough, you also got a whole batch of new options in JDK 1.2.0. There are a number of new classes in java.util for specialty data structures (well, that's what we call them). Specifically, there is support for lists, maps, stacks, and sets, as shown in Table 6.2.
Class |
Description |
---|---|
ArrayList |
A resizable array. It Supposedly replaces the Vector class, but most people still use Vector. |
HashMap |
A fast-access list of key-value pairs. It supposedly replaces Hashtable, but most people still use Hashtable. |
HashSet |
A list of unordered but unique elements. |
LinkedList |
A doubly linked list of unordered elements. |
Stack |
A last-in, first-out list of elements. Actually, it has been available since Java 1.0.0. |
TreeMap |
A very efficient way to store elements via a unique sorted key. |
TreeSet |
A list of unordered but unique elements, indexed by a sorted key. It uses a TreeMap. |
These classes are not covered here because you don't usually need them; arrays, vectors, and hashtables can serve all your needs. Their main role is for efficiency. If you do find yourself coding a very large array, vector, or hashtable, and you are concerned about the performance of finding or inserting items into it, it will be time to go to the JDK documentation and research which of the classes in Table 6.2 increases that performance.
Having completed this fast introduction, let's take a closer look.
In Java, the syntax for declaring an array variable is essentially the same as declaring any variable, but with the added notation of square brackets to identify it as an array versus a simple scalar variable. Here is a simple example:
int numberOfSalesByItem[];
This shows you how to declare an array named numberOfSalesByItem, whose elements will all be of type int. Perhaps this is an array of sales volumes indexed by item number. The fact that it is an array is indicated by the square brackets, []. Thus, the presence of brackets in Java is the same as the presence of the DIM keyword in RPG IV: it denotes an array. Note that these square brackets can come after the name, or after the type:
int [ ] numberOfSalesByItem;
Both are legal, and there is no clear consensus on which is better. We generally prefer to place the brackets after the name, but even we aren't always consistent in our code. Notice also the addition of white space (blanks) before and inside the brackets. This is just to highlight once again that Java ignores white space.
How many dimensions are in this array? One. You know this because there is only one pair of brackets, regardless of where they exist. What is the size of this array (how many elements)? You don't know yet!
Here is the tricky part compared to defining arrays in RPG: we have not created an array at all here—we have only created an object reference variable that will eventually refer to an array object. Yes, arrays in Java are in fact objects. So, even though the type is primitive (int), this is an object reference variable because the added brackets made it an "array" type. Really, all array variables have a type that is a class you never see, a special Array class that Java supplies. The type you specify is not for the variable itself, but rather for all the elements in the array.
You will soon learn how to actually create the array object. For now, here is another example:
int numberOfSalesByItemByPerson[][];
This defines numberOfSalesByItemByPerson as a two-dimensional array variable that again will contain elements of type int. Perhaps it will contain sales volumes indexed by item number and salesperson ID. You know it is two-dimensional because there are two pairs of square brackets; each pair indicates a dimension. That is the rule in Java.
Here is an interesting example for you:
char[] accountCodes[];
This defines accountCodes as a two-dimensional array whose elements are all of type char (character). Perhaps this will contain account codes indexed by country and type of expense (note that indexing is always via integer indexes). As you see, you can mix the bracket pairs between the type and the name, and the rule remains the same: the total number of bracket pairs equals number of dimensions. We do not recommend this style, however!
Finally, just as you can declare multiple scalar variables at once, you can declare multiple arrays at once:
boolean arrayOne[], arrayTwo[][], arrayThree[][][];
This declares three arrays of type boolean. The first (arrayOne) is one-dimensional, the second (arrayTwo) is two-dimensional, and the third (arrayThree) is three-dimensional. Alternatively, if you put brackets on the type, all names become arrays. This declares three one-dimensional arrays of type boolean:
boolean[] arrayOne, arrayTwo, arrayThree;
In Java, there is a big difference between declaration and creation. We cannot overstate that in Java, arrays are objects. Not only must you declare them, you must also create them using the familiar new operator. Until you do so, the array variable contains the special value null. Any attempt to reference declared array variables that are not yet created will result in a compile time error. The following lines expand the previous examples to create arrays:
int numberOfSalesByItem[] = new int[1000]; int numberOfSalesByItemByPerson[] = new int[1000][100]; char accountCodes[][] = new char[20][20];
The main difference here is the use of the new operator to create the arrays at declaration time. Notice the syntax of the new operator for arrays:
In the example, numberOfSalesByItem contains 1,000 elements; numberOfSalesByItemByPerson contains 1,000 rows by 100 columns, giving it 100,000 elements; and accountCodes contains 20 elements in both dimensions, giving it 400 elements.
You do have a choice here. You can use the new operator at declaration time, just shown, or you can declare the array in one statement and create the array with the new operator in another statement, as shown here:
boolean arrayOne[], arrayTwo[][], arrayThree[][][]; arrayOne = new boolean[100]; arrayTwo = new boolean[100][200]; arrayThree = new boolean[100][200][300];
It's your choice. Either way, after the new operation has been done, you have actual array objects whose elements can be assigned, referenced, and compared. You will learn how to do this shortly.
The examples specified an integer literal for the length (or size, which is the same thing) of the array. In RPG III, you must specify a literal number in positions 36 through 39 of the Extension specification. In RPG IV, you must use a literal number, constant, or built-in function for the DIM keyword. The key is that the number, which represents the size of the array (its number of elements), must be known at compile time. This is different than Java, where the size of the array is determined at runtime. You can use any variable or expression you wish for the size of the array. Once the array is created, its size is fixed, but at least you can defer creation until you have done some calculations to determine how big to create it. Since you can declare it first and instantiate it later, even arrays declared at the class level as instance variables can defer their creation until later, possibly deep inside a method, after determining how big the array should be.
This is very, very good. It means, in many cases, you can first determine exactly how many elements the array should hold, and then use that number to create the array with the new operator, with exactly the correct number of elements. So, there's no wasted space. It is for this capability the Java engineers decided to implement arrays as objects.
Both RPG and Java support compile-time arrays. In these arrays, the elements are initialized at the same time as the array is declared. The syntax of initializing array elements as part of the declaration clearly differs between the two languages. In RPG IV, with compile-time arrays, you define the array with its dimension on the D-spec and initialize it using the **CTDATA keyword following the RPG source code, as shown in Listing 6.1.
Listing 6.1: A Compile-Time Array in RPG IV
D days S 3A DIM(7) PERRCD(7) CTDATA D idx S 9 0 C 1 DO 7 idx C days(idx) DSPLY C ENDDO C EVAL *INLR = *ON **CTDATA days MONTUEWEDTHUFRISATSUN
Note that the name of the array is specified after the **CTDATA keyword. Compare this to RPG III, where compile-time arrays are identified by the per-record number in columns 33 to 35 of the E-spec, and the data is identified with ** instead of **CTDATA. The main D-spec keywords used with compile-time arrays are listed in Table 6.3.
D-Spec Keyword |
Explanation |
---|---|
DIM |
The number of elements the array is to contain |
CTDATA |
Compile-time data records |
PERRCD |
Number of elements per record |
All RPG IV arrays use the DIM keyword. It tells the compiler the number of elements in the array. As mentioned earlier, RPG supports only one dimension, so only one parameter is supplied. (However, it is designed to allow multiple parameters in the future, in the event RPG IV steps up to supporting multi-dimensional arrays). The example in Listing 6.1 shows an array of seven elements named days. The CTDATA keyword indicates to the compiler that the array is a compile-time array, and therefore, the compiler should be looking for the **CTDATA days record at the bottom of the RPG source code. Finally, the PERRCD keyword tells the compiler that each line (record) of data consists of seven elements. In this case, the one data record supplies the data for all seven elements in the array. Since each element is three characters long and the one data record is 21 (7*3) characters long, you get the following:
days(1) = 'MON' days(2) = 'TUE' days(3) = 'WED' days(4) = 'THU' days(5) = 'FRI' days(6) = 'SAT' days(7) = 'SUN'
Compare this to what Java offers in terms of compile-time arrays. Here is the same example in Java:
String days[] = {"MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"};
Notice that in Java, character variables are only of one length, while the supplied String class is used for strings of multiple characters. (String objects are covered in detail in Chapter 7.) This example uses an array of String objects. The syntax shown here is the special syntax Java supports for defining, creating, and initializing arrays in one step. This is the only way you can initialize compile-time arrays in Java. In this case, the initial values are supplied between curly braces, with each element's initial value separated by commas. The literal values supplied must be consistent with the declared element type of the array. String literals are always surrounded by quotes. Also, don't forget the ending semicolon, as always.
When you specify initial values within an array declaration in this way, Java performs the new operation and defines the array size for you. The array size is implicitly determined by the number of comma-separated elements in the initialization part. These types of compile-time arrays are often used to hold constant data, and so are defined as final. Also, since they are unchanging, we can live with one instance per class versus per object, and so make them static as well to save memory (as we do with all constants in Java):
final static String days[] = {"MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"};
If you have used arrays in any language, then you already know what runtime arrays are. These are perhaps the most common type of array. With runtime arrays, you initialize the values of the elements in a separate step from declaring the array.
Thus, as the name implies, runtime arrays are loaded (or initialized) at runtime during program execution. Your program code initializes them by assigning values to the elements, typically in a loop that stops at the size of the array. Listing 6.2 shows how this is done in RPG IV.
Listing 6.2: A Runtime Array in RPG IV
D factorials S 9P 0 DIM(10) D total S 9P 0 INZ(1) D idx S 9P 0 D value S 9P 0 C 1 DO 10 idx C EVAL total = total * idx C EVAL factorials(idx) = total C ENDDO C 1 DO 10 idx C EVAL value = factorials(idx) C value DSPLY C ENDDO C EVAL *INLR = *ON
This example first declares the array factorials to contain integer values of length 9, with 0 decimal points and size 10. (Note that you can tell this is a runtime array in RPG IV because it does not use the keywords PERRCD, CTDATA, FROMFILE, or TOFILE.) It then declares a working sum total field named total and the array index named idx, and another working field named value that will display each array element's value. Finally, it loops through the array, computing and then storing into each element the factorial of its index number. For example, entry 3 will contain 6 (3 * 2 * 1). The final loop through the array is simply to display each element's value.
As you can see, in RPG, array indexing starts at 1. In RPG IV, array elements are referenced using arrayName(index) notation, versus the RPG III syntax of arrayName,index. Note also that blanks are not important in RPG IV EVAL statements, so this is valid:
EVAL factorials ( idx ) = total
The Java equivalent of this example is shown in Listing 6.3. This code accomplishes the same result as the RPG code. Notice the difference in the indexing of the array. In RPG IV, you use parentheses, while Java uses square brackets. Using array elements in your source code is no different than using any other regular field, except that with arrays, you have to index them. An important difference that you might have noticed between the RPG and Java examples is that you initialize the index field to 1 in RPG and to 0 in Java. In Java, the index of the first element of an array is 0, and the last location is the size of the array -1. Officially, RPG arrays are one-based and Java arrays are zero-based. Therefore, to loop through all the elements of the array, the RPG program uses the loop index value 1 to 10, whereas Java uses 0 to 9.
Listing 6.3: A Runtime Array in Java
int idx; int total = 1; int factorials[] = new int[10]; for (idx=0; idx < 10; idx++) { if (idx > 0) total = idx * total; factorials[idx] = total; System.out.println(factorials[idx]); }
Again, in both RPG and Java, the size of the array is fixed. While it is fixed at compile time for RPG, it is fixed at runtime for Java. Once set, however, it is set for good. If for any reason the index value being used is less than one or greater than the size of the array, RPG generates a runtime error, index out of bound. Similarly, if it is less than zero or greater than or equal to the size of the array, Java generates an IndexOutOfBoundsException error and terminates the program.
Since arrays in Java are objects, Java supplies arrays with a length public instance variable in order to retrieve the size of the array (that is, the number of elements). Knowing this, the previous example could have (and should have) been coded as this:
for (idx=0; idx < factorials.length; idx++)
Notice that you specify the name of the array first and follow it with a dot, and then specify the instance variable length to retrieve the length of the array. Personally, we wish the variable was named size, not length. Indeed, it shouldn't be a variable at all but a method named getSize. Oh, well.
RPG IV has something similar to the length variable in Java arrays. It is the %ELEM built-in function, which returns the declared size of a given array. You can use it instead of hard-coding array sizes. So, for example, you could change the hard-coded limit of 10 on the DO loop in Listing 6.2 to this:
D arraySize S 9P 0 INZ(%ELEM(factorials)) C* 1 DO 10 idx C 1 DO arraySize idx
Now that you have seen the different types of arrays and the different ways to declare and initialize them, let's look at a few examples of array manipulations. Each value of an array is called an array element. To access an array element, you specify the name of the array followed by a comma and the index number for RPG III, with the index number between parentheses for RPG IV, and with square brackets for Java. Since Java also supports multi-dimensional arrays, you specify as many sets of square brackets as there are dimensions in the array. For example, if array numberOfSalesByIndexByPerson is two-dimensional, then to access the first element in it, you specify numberOfSalesByIndexByPerson[0][0].
It is important to think of multi-dimensional arrays as arrays of arrays. For two dimensions, each element of the first dimension is like another array. When using compile-time initialization, each element's initial value for the first dimension will be a valid initialization value for another array. You will then have nested sets of curly bracket values. Furthermore, when using the length variable, you use it per dimension. Listing 6.4 is an example of initializing a two-dimension array in Java using compile-time initialization syntax, and then looping through the array to print out the values.
Listing 6.4: A Two-Dimensional Compile-Time Array in Java
class TestMultiArray { public static void main(String args[]) { int ctArray[][] = {{ 1 , 2 , 3 }, { 4 , 5 , 6 }, { 7 , 8 , 9 }}; for (int xIdx=0; xIdx < ctArray.length; xIdx++) { for (int yIdx=0; yIdx < ctArray[xIdx].length; yIdx++) { System.out.print(ctArray[xIdx][yIdx] + " "); } // end inner for-loop System.out.println(); } // end outer for-loop } // end main method } // end TestMultiArray class
This example illustrates a 3x3 array, using a compile-time array and assigning it the values one to nine. Note that curly braces surround each row of the array's initial values. Because there are two dimensions, two levels of braces are needed, and each set is comma-separated, just as each element inside is comma-separated. The body of the code has two different loops to go through the three rows and three columns in the matrix. Notice that ctArray.length is used for the length of the first array dimension, as usual, but the second dimension uses ctArray[xIdx].length. This is because you want to determine the length of the "sub array" that is the column for the xIdx row. Again, a two- dimensional array is really an array of arrays.
The example simply loops nine times, printing all of the elements of the array. The statement following the inner loop is a print of a new line in order to force the output to a separate line. The following output is produced by running the example:
>java TestMultiArray 1 2 3 4 5 6 7 8 9
Listing 6.5 is the same example as a runtime array, which gives the same result as output.
Listing 6.5: A Two-Dimensional Runtime Array in Java
class TestMultiArrayRT { public static void main(String args[]) { int rtArray[][] = new int[3][3]; int value = 1; for (int xIdx=0; xIdx < rtArray.length; xIdx++) { for (int yIdx=0; yIdx < rtArray[xIdx].length; yIdx++) { rtArray[xIdx][yIdx] = value++; // assign and increment System.out.print(rtArray[xIdx][yIdx] + " "); } // end inner for-loop System.out.println(); } // end outer for-loop } // end main method } // end TestMultiArrayRT class
Are you getting the hang of it? This example creates the same loop, but uses it to initialize as well as print out the value of each element. Note the use of value++. As you might recall, it assigns the current contents of variable value to the left-hand side, then increments that variable by one.
Think you could handle expanding this example to three dimensions? Or using compile-time arrays? Any guess as to what the initialization statement might look like? Listing 6.6 gives a look.
Listing 6.6: A Three-Dimensional Compile-Time Array in Java
class TestThreeArray { public static void main(String args[]) { int ctArray[][][] = { { { 1 , 2 , 3 }, { 4 , 5 , 6 }, { 7 , 8 , 9 } }, { { 10, 11, 12}, { 13, 14, 15}, { 16, 17, 18} }, { { 19, 20, 21}, { 22, 23, 24}, { 25, 26, 27} } }; for (int xIdx=0; xIdx < ctArray.length; xIdx++) { for (int yIdx=0; yIdx < ctArray[xIdx].length; yIdx++) { for (int zIdx=0; zIdx < ctArray[yIdx].length; zIdx++) { System.out.print(ctArray[xIdx][yIdx][zIdx] + " "); } // end inner for-loop System.out.print(" "); } // end middle for-loop System.out.println(); } // end outer for-loop } // end main method } // end TestThreeArray class
The output of this is the following (keeping in mind that it's difficult to show three dimensions):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
As mentioned earlier, Java uses references (or, if you like, pointers) to point to objects. Array references are no different than any other Java object. For example, you can point to one array object with an array reference, and later point to another array object using the same reference. Listing 6.7 illustrates this.
Listing 6.7: Proving that Java Array Variables Are Object Reference Variables
class TestArrayObjects { public static void main(String args[]) { int arrayOne[][] = { { 1 , 2 }, { 3 , 4 } }; int arrayTwo[][] = { { 5 , 6 }, { 7 , 8 } }; int tempArray[][]; tempArray = arrayOne; for (int xIdx = 0; xIdx < tempArray.length; xIdx++) { for (int yIdx = 0; yIdx < tempArray[xIdx].length; yIdx++) System.out.print(tempArray[xIdx][yIdx] + " "); System.out.println(); } tempArray = arrayTwo; for (int xIdx = 0; xIdx < tempArray.length; xIdx++) { for (int yIdx = 0; yIdx < tempArray[xIdx].length; yIdx++) System.out.print(tempArray[xIdx][yIdx] + " "); System.out.println(); } } // end main method } // end TestArrayObjects class
This example declares and initializes two arrays, arrayOne and arrayTwo. It then declares, but does not create, a third array, tempArray, of the same dimensions as the first two. The body of the code first assigns tempArray to arrayOne and loops through to print the contents, and then reassigns tempArray to arrayTwo and loops through again, printing the contents. This results in the following output:
1 2 3 4 5 6 7 8
You might be inclined to think that each assignment of tempArray to another array results in a copy of the contents from one array to another. This is not the case, specifically because arrays are in fact objects in Java, and array variables are, therefore, reference variables. Thus, assigning a reference variable is the same as changing the object it points to in memory. So, the tempArray assignments simply change the array to which the tempArray variable points, with no copying of array contents. This means changes made to the array elements of tempArray will also affect the original array to which it points. That's because after an array assignment, a statement like tempArray = arrayTwo results in one array in memory, but with two variables referencing the array.
If you did want to copy the contents of an array, you would first have to instantiate a new instance of an array for the target using the new operator, then loop through both arrays, copying the contents. While this is easy enough to code, there is actually an easier way to create a duplicate copy of an array. In fact, there are two easier ways. The first is to use the clone method that arrays have, which returns a copy of the array, like this:
int arraySrc[] = {1,2,3,4}; int arrayTgt[] = (int[])arraySrc.clone();
Note how this casts the result to an integer array using (int[]). Yes, you can cast objects, and arrays are objects. Casting objects is discussed in more detail in Chapter 9, but for now, just remember you must use this syntax to cast the result of clone to an array of the target array type.
The second option is to use the Java-supplied static method arraycopy in the System class to handle this for you. It takes as input a source array, an index position from which to start copying, a target array, an index position in the target array where the copying starts to go, and a length indicating how many elements to copy. Here is an example for a single-dimensional array:
int arraySrc[] = {1,2,3,4}; int arrayTgt[] = new int[4]; System.arraycopy(arraySrc, 0, arrayTgt, 0, 4);
These both work well for single-dimensional arrays, but for multi-dimensional arrays, you must be careful to call them per dimension, in the right order. For those, we recommend you simply use your own code loop instead, for peace of mind.
RPG supports pre-runtime arrays, whereas Java does not. Nevertheless, since we are comparing arrays between RPG and Java, we'll touch on this to make our comparison complete. If you know at compile time what data you want stored in your array, then the answer is clear as to which RPG array type to use—compile-time arrays are the best choice (for both languages, actually). If your data is not known at compile time, the answer will be to use either pre-runtime or runtime arrays. What's the difference?
If the initialization data being read resides in a file on disk, use pre-runtime arrays, since the main feature of this kind of an array is to read the data from a file at the start of runtime (i.e., at load time) and write it back to the file at termination time. However, if the source of the data is coming from the keyboard or any other input medium, then your best bet is a runtime array. Listing 6.8 is an example of a pre-runtime array in RPG IV.
Listing 6.8: A Pre-Runtime Array In RPG
FSTATE UF F 70 DISK D STATEARR S 2A DIM(20) D PERRCD(1) D FROMFILE(STATE) D TOFILE(STATE)
This example illustrates how you can load the two-character prefix of a state into a pre-runtime array. The main keywords used with pre-runtime array are listed in Table 6.4.
D-Spec Keyword |
Explanation |
---|---|
DIM |
Specify the number of dimensions. |
FROMFILE(filename) |
Read from this file at program load time. |
PERRCD |
Indicate the number of elements per record in the file. |
TOFILE(filename) |
Write to this file at termination of the program. |
The list shows that, as with compile-time arrays, you use the DIM keyword to define the dimension and number of elements in the array, and the PERRCD keyword to tell the compiler the number of elements per record in the file. The two other keywords, FROMFILE and TOFILE, specify the file name from which you want to load the data to the array you are defining, and optionally the file to which you want to write the data at termination. As you see in the example, other than specifying these extra keywords, the actual definition of the array is the same as a compile-time array. You first define the file STATE, which holds the data with which the array will be initialized. Following that, on the D-spec, you define the array STATEARR as a 20-element array of two characters each, using the DIM keyword.
While Java does not have built-in support for RPG-style pre-runtime arrays, it is possible to code your own. You do this by defining a class that will simulate the main keywords that RPG supports for pre-runtime arrays, namely, FROMFILE, TOFILE, and DIM. Assume a PERRCD of one, for simplicity. Listing 6.9 shows one possible implementation.
Listing 6.9: Simulating a Pre-Runtime Array in Java
import java.io.*; public class PreRuntimeArray { private String array[]; private String fromFile, toFile; private int size; public PreRuntimeArray(String fromFile, String toFile, int size) { this.fromFile = fromFile; this.toFile = toFile; this.size = size; try // open and read input file { BufferedReader instream = new BufferedReader( new InputStreamReader(new FileInputStream(fromFile) )); array = new String[size]; // instantiate array for (int idx=0; idx < size; idx++) array[idx] = instream.readLine(); instream.close(); } // end try catch (IOException exc) { System.out.println("Error Reading File " + fromFile + ": " + exc.getMessage()); } } // end of constructor public String[] getArray() { return array; } public void writeToFile() { if (toFile != null) { try // open and write output file { PrintWriter outstream = new PrintWriter( new FileOutputStream(toFile) ); for (int idx=0; idx < size; idx++) outstream.println(array[idx]); outstream.flush(); outstream.close(); } // end try catch (IOException exc) { System.out.println("Error Writing File " + toFile + ": " + exc.getMessage()); } } // end if toFile != null } // end writeToFile public void finalize() { writeToFile(); } } // end of class PreRuntimeArray
Listing 6.9 supplies a class that takes in as its constructor the name of the from-file (FROMFILE), the name of the to-file (TOFILE), and the size of array to create (DIM). The size of the array also indicates how many records to read from the from-file. Note the file names are for flat files or stream files, such as you find on Windows or in the Integrated File System, versus a relational database such as DB2/400. (We leave support for DB2/400 to you to add after reading Chapter 13.) This example also does not implement the PERRCD capability of RPG IV, in the interest of brevity. Instead, it assumes only one array entry per file record. You can add support for multiple entries per record yourself after reading Chapter 7 and learning how to substring String objects.
The example class will instantiate the array, open the from-file, and read the records from it into the array, all in the constructor. (Reading and writing local flat files are discussed in Chapter 14; for now, just take our word for it that what is coded here will work.) Users of the class can subsequently retrieve a reference to the array by calling getArray. Note this returns a String array or null if the open of the from-file failed in the constructor.
Finally, the code supplies a writeToFile method that, when called, will write out the contents of the array to the to-file named in the constructor. (We saved the name in an instance variable so it could be accessed later by any of our methods.) Again, the details of writing to a flat file are discussed in Chapter 14, so we won't dissect this here. Note, however, that if the user of the class passed null for the toFile parameter to the constructor, then nothing would be written out.
The example also supplies a finalize method. As you will learn in Chapter 9, this special method will be called by the Java runtime whenever an object of this class is reclaimed by the garbage collector. You supply it and code it to call writeToFile method. This way, if your users forget to call writeToFile, it will still be called automatically for them. Note that you should also put in logic to ensure you don't write out the file twice, for performance reasons.
How would you use this class? Listing 6.10 provides an illustration.
Listing 6.10: Using the Pre-Runtime Array Simulation in Java
public static void main(String args[]) { PreRuntimeArray daysPRA = new PreRuntimeArray("days.txt","daysNew.txt",7); String days[] = daysPRA.getArray(); if (days != null) { for (int idx=0; idx.writeToFile(); } // end main
The main method in Listing 6.10 was actually coded in the same class, as a test case for the class. Thus, you can simply call the class from the command line to see the results. We create an instance of the new class and pass it a from-file named days.txt, a to-file named daysNew.txt, and an array size of seven. Since we did not fully qualify the file names, Java will look in the current directory for them. We have created our input from-file to contain this:
MON TUE WED THU FRI SAT SUN
If this file is found successfully in the constructor code, then, after instantiation, the object will contain a populated String array with the contents of the file. To test that it worked, we call getArray to get the array object, and then loop through the array, printing each element to the console. Finally, we call writeToFile to write out the array contents to the to-file (daysNew.txt). If we run this class from the same directory containing the days.txt input file, we get the expected output:
MON TUE WED THU FRI SAT SUN
Indeed, we also find a new file in our directory, daysNew.txt, which contains this data. This file was created and populated by the call to writeToFile.
This example reads String values from the file. If you wanted to support integer values, say, you would convert the read-in String values to integers using the Integer class discussed in Chapter 5. Specifically, you would use the static method parseInt to convert the String object to an int value. To write the int values back out as String literals, you would use the static method toString, which takes the integer value as input and returns a String object. To read numeric data with decimals, use the BigDecimal class in java.math, passing the read-in String to the constructor. To write out a BigDecimal object in text format, use its toString method.
This example is coded in such a way that the user has to pass in the size of the array to the constructor. A nice enhancement would be to not ask for the size, but rather have the constructor programmatically determine it by scanning through the file first, counting and then creating an array of the appropriate size, and reading the records into it.
So far, the multi-dimensional arrays have been rectangular, meaning they have the same number of elements in each dimension. Java's multi-dimensional arrays are actually arrays of arrays, however, so each dimension does not have to be the same length. In fact, each element in a given dimension, if it is another array, can have differing numbers of elements. This is an unbalanced array. The main restriction in unbalanced arrays is for the dimensions to be specified left to right. If you are declaring, say, an array of 10 elements, and each element of the array is actually another array of unknown length, you can do that as follows:
int myArray[][] = new int[10][];
In this case, you specify the first left dimension as 10, but you leave the second unknown dimension to be declared or created after in the body of the code. This example is perfectly legal in Java. However, you cannot do the opposite and specify the second dimension before the first, like this:
int myArray[][] = new int[][10]; // NOT VALID!
This is the left-to-right rule in Java. It is legal to specify the left dimension and leave the right, but not legal the other way. The fact that multi-dimensional arrays in Java are arrays of arrays explains this restriction. You create the left array first, and as you proceed in the code, you create the rest, left to right. The following example uses unbalanced arrays:
boolean ubArray[][] = new boolean[3][]; for (int idx = 0; idx< ubArray.length;idx++) { ubArray[idx] = new boolean[idx+1]; }
This example creates an array of size three for the first dimension, and unknown for the second dimension. It loops three times, building up the second dimension of the array. The body of the for loop uses the new operator to create the second dimension. This is, in fact, a new array for each element. The result of this is an array containing the following values:
false false false false false false
As you can see, it starts with the first row having only one element, the second row having two elements, and finally the third row with three elements. This is a result of using idx+1 as the size parameter when creating the new array for each row.
When and why might you use such a thing? Well, imagine you run a store with five departments, and each department has differing numbers of salespeople. To track the employee IDs of each salesperson by department, you could declare a two-dimensional array, with the first dimension's size being the number of departments, and the second dimension's size being unique per department. This will save some space, but it could be argued that the extra effort in maintaining it is not worth it.
RPG not only has arrays of single values, it also has multiple-occurring data structures (MODS), which effectively are arrays of data structures. For example, suppose you have MODS of departments. Each data structure might contain the following:
For example, look at Listing 6.11. Notice the new RPG IV unsigned data type, which is a nice match for data that is only positive integer numbers. While we don't show it here, to position to a given entry in a multiple occurring data structure, use the OCCUR op-code. The full file on the disk included with this book ( DEPART.IRP) includes procedures to populate the Department MODS with sample data and to display the data in each occur- rence to the console.
Listing 6.11: Multiple Occurring Department Data Structures in RPG IV
D* Track information about each department DDepartment DS OCCURS(5) D Dept_Nbr 5U 0 D Dept_Name 10A D Dept_Mgr_Id 10U 0 D Dept_Nbr_Emps 5U 0 D Dept_Emps 10U 0 DIM(20)
How do you do this in Java? Well, you don't have data structures in Java, per se. You do have classes, however, and classes really are just data structures that also contain methods, as discussed in Chapter 5. So, you would create a class for an individual department, then in another class, you would declare an array of department objects and instantiate each department element. The constructor for the Department class would probably take as a parameter an open database connection object, use it to read the values for the next department record from the database, and populate the instance variables with the results.
Let's look at an example, but since database access isn't covered until Chapter 13, that part is not shown yet. First, take a look at the Department class in Listing 6.12. Notice that the variables are made private, with "getter" methods supplied to retrieve the values. That is good practice, as it allows you to change the attributes of those variables later and minimize the impact on other code using the class.
Listing 6.12: A Department Class in Java
public class Department { private int deptNbr; private String deptName; private int deptMgrId; private int deptNbrEmps; private int deptEmps[]; public Department() // constructor { // read next department record from database // populate above instance variables from database // instantiate deptEmps array once we know how many in dept } public int getDeptNbr() { return deptNbr; } public String getDeptName() { return deptName; } public int getDeptMgrId() { return deptMgrId; } public int getDeptNbrEmployees() { return deptNbrEmps; } public int[] getDeptEmployees() { return deptEmps; } } // end class Department
The other class that creates the array of Department objects is shown in Listing 6.13. For arrays of objects, you have to instantiate the array object itself, as usual. You also have to instantiate the objects in each element in the array, as usual for objects whether they are in an array or not. The full example on disk of the Department and Departments classes contain methods to populate the Department objects with dummy data and display the contents of each Department object to the console, for testing purposes. You can run the Departments class from the command line to see the results.
Listing 6.13: An Array of Department Objects in Java
public class Departments { private Department depts[] = new Department[5]; // create array public Departments() // constructor { // open database connection. Not shown for (int idx = 0; idx < depts.length; idx++) { // create Department object for each array element. // note passing DB connection is not shown depts[idx] = new Department(); } } } // end class Departments
There are a number of other things in this example that we would turn into separate classes as well. For example, we would make a class named EmployeeID that would hold the six-digit employee ID per employee. This way, we could easily change that to seven digits later and not affect anyone. We'd also have a class named Employee and an array of Employee objects in our Department class, instead of just employee IDs.
In RPG, you often search tables or arrays using the LOOKUP operation. This is nice functionality that saves you writing your own redundant search algorithm every time. Here is an example of using LOOKUP in RPG IV:
C searchString LOOKUP days(idx) 26 C IF %EQUAL = '1' C 'found it!' DSPLY C ELSE C 'not found' DSPLY C ENDIF
This example searches for an element in the array. If found, the EQ indicator is turned on, and the %EQUAL built-in function returns a one. To find the previous or next element when no match is found, the LO or HI indicators can be set, and %FOUND will be set to 1 if a non-equal match is found. For this though, the array must be sorted. (For the full example, see the file LOOKUP.IRP on the included disk.)
It would be nice if Java gave similarly capabilities, would it not? Well, if you are using Java at a JDK level prior to 1.2.0, the unfortunate answer is that Java does not. You have to code your own search algorithm, as shown in Listing 6.14.
Listing 6.14: A Helper Method for Searching Integer Arrays
public static int searchArray(int searchArray[], int searchArgument) { int matchIndex = -1; for (int idx=0; idx
This method is static because it doesn't depend on any instance variables, but instead takes what it needs as parameters. This example will only work with an integer array, and as such, an integer search argument. This is because it declares the two parameters to the method to be of type int, and so only type int parameters can be passed. To support arrays of other types, like the other seven primitive data types, you would create overloaded versions of this method. Recall that overloaded methods are methods with the same name but different numbers or types of parameters. So, you could copy this searchArray method and produce versions that accept a float array plus float search argument, a double array plus double search argument, and so on. This is something you might want to do if you need to search arrays often.
If you use JDK 1.2.0 or higher, a series of such helper methods is already supplied for you in the class Arrays in the package java.util. Table 6.5 lists the methods (all static) in this class. (Note that this is not a complete list.) The binarySearch method is like RPG's LOOKUP op-code. Similarly, fill is like RPG's MOVEA op-code, and sort is like RPG's SORTA op-code.
Method |
Description |
||
---|---|---|---|
binarySearch |
Provides overloaded methods for searching the given array for the given key. There are methods for seven of the primitive types, but not boolean, and for arrays of objects. The array must already be sorted. It returns the zero-based index position of the match if found; else, it returns (-(insertion point)-1), where insertion point is where the item should be inserted. |
||
equals |
Compares two given arrays, returning true if they have the same number of elements and the element values are the same. There are overloaded methods for all eight primitive types plus arrays of objects. |
||
fill |
Fills all elements of a given array with a given value. There are overloaded methods for all eight primitive types plus arrays of objects. |
||
sort |
Sorts the given array into ascending sequence. There are overloaded methods for all eight primitive types plus arrays of objects. |
||
Note that all the array methods work on objects and primitive types. How is this possible, you might wonder? What possible type will work for all classes instead of just the one that is specified on that overloaded version of the methods? The answer is Object, a class in the java.lang package. It turns out that if you specify a type of Object for a method parameter, you can actually pass in objects of any class. You will see why this is so in Chapter 9, but for now, it is a very useful truth you should remember.
Another thing to think about is, given an array of objects, how can Java sort it? How does it know how to compare any two object elements, in order to sort them? It does not know what it means for one object to be greater than another object. The answer is that it leaves this decision up to the objects themselves! When comparing two objects, Java will try to call a particular method named compareTo in one of the objects, passing in the second object as a parameter. That method has to return a negative number, zero, or a positive number if the second object is less than, equal, or greater than the target object. So, to sort an array of objects of your class type, your class must have that method in it. Most Java-sup- plied classes, like String, already supply this method. For you to supply it in your classes, you will have to implement an interface named Comparable, but you will have to wait until Chapter 9 to learn about interfaces.
You might have noticed there are no search methods, only binarySearch methods. This is a bit of surprise for us, too, and it means if you have arrays that are not sorted, you will have to write your own search methods as shown in Listing 6.14. However, the good news is that it is easy to sort an array using the sort methods.
You might also have noticed that these methods all work only on single-dimensional arrays. That is true, but multi-dimensional arrays are actually arrays of arrays, so with only a little bit of extra work, you could call these methods on all the dimensions of a multi-dimensional array. For example, the following will fill all entries of a 1010 integer array with -1:
import java.util.*; // for Arrays class . . . int myArray[][] = new int[10][10]; for (int rows=0; rows < myArray.length; rows++) Arrays.fill(myArray[rows],-1);
These methods are all pretty straightforward to use, so we just show in Listing 6.15 an example of using the sort method, followed by the binarySearch method.
Listing 6.15: Testing, Sorting, and Searching Arrays in Java
int sortedArray[] = {48,25,12,34,10,52,70,16,35}; Arrays.sort(sortedArray); for (int idx=0; idx < sortedArray.length; idx++) System.out.print(sortedArray[idx] + " "); System.out.println(); int newpos = Arrays.binarySearch(sortedArray,15); if (newpos < 0) { newpos = -newpos - 1; System.out.println(newpos); }
This simple example declares a compile-time array of unsorted integers, sorts the array, and prints the sorted results to verify it worked. Note that System.out.print (versus System.out.println) prints the given data without advancing the console by a new line. Finally, binarySearch is used on the array and the number 15 to see what we get back. The results of running this code snippet is the following:
10 12 16 25 34 35 48 52 70 2
Why did we get a two back for the index position of 15? Because while not found, that is the zero-based position of the next highest element. This is similar to using LOOKUP in RPG on an ascending array, and specifying a HI indicator.
In RPG, you can have alternating arrays or tables. These allow you to define two arrays that are complementary, such as an array of employee IDs and an array of employee names. Then, you can do a LOOKUP operation on the primary array, say on an employee ID, and get back the position of the matching entry in the alternate array, say the employee name. There is nothing like this in Java, so in the worst case, you have to do the second search yourself, using the results of the first search. However, in reality, you can combine these arrays into a single array of objects, where the objects are of a class that contains both pieces of information. For example, you might have an Employee class that contains both the employee ID and the employee name. If you supply a compareTo method, you can use the binarySearch method from above on the array of Employee objects and get the resulting Employee object, which contains both the employee ID and the employee name. There is an example of this on the included disk, in Employee.java.
Finally, RPG has a very handy XFOOT op-code for summing the contents of a numeric array. Although Java does not have any such function, we bet you could write one!
Got your fill of arrays? Sort of? Then let's search for something new.…
The previous sections discussed single- and multi-dimensional arrays. One important restriction in arrays that applies to both RPG and Java is that their length, once set, can never change. In Java, you can dynamically declare an array, which is more powerful than what is allowed in RPG, as it allows you to defer creation until you know the length. However, once you create an array, you cannot change its length. In other words, an existing array has a fixed length and cannot grow or shrink.
As you tackle more complex applications, you sometimes do not know at any time, whether at compile time or runtime, how many elements you will want to store. To help with this, Java supplies a utility class named Vector in its java.util package. Java has vectors as an alternative when you need to use single-dimensional arrays that can grow or shrink dynamically. As you will see later in this section, RPG supports this as well, but more coding is required to dynamically allocate and manipulate storage.
Note |
Vectors are adjustable arrays that can increase or decrease their size in response to the number of elements they need to store. Vectors use the term "size" instead of "length" to define the number of elements. |
Variables of the Vector class are (you guessed it) objects in Java, just as arrays are. However, unlike arrays, they have no hard-wired language support such as special syntax for instantiating and indexing. The Vector class is just a regular class, just as you could write on your own. Luckily, though, the Java engineers took the time to write it and include it in their utility package. The Vector class contains many methods to help you add, remove, and search elements. Listing 6.16 illustrates a simple Vector with three elements.
Listing 6.16: A Simple Use of the Vector Class
import java.util.*; // import all classes in java.util package class TestVector { public static void main(String args[]) { Vector myFirstVector = new Vector(); myFirstVector.addElement(new String("Ford")); myFirstVector.addElement(new String("GM")); myFirstVector.addElement(new String("Chrysler")); for (int idx = 0; idx < myFirstVector.size(); idx++) System.out.println(myFirstVector.elementAt(idx)); } } // end TestVector class
This example first defines and creates the Vector variable myFirstVector. It then adds elements to the variable. Notice the use of the String class as the elements are added. (Not to worry; we have not covered strings yet. They are discussed in the next chapter.) For now, all you need to know is that the new keyword with the String class creates a String object, and the addElement method adds that string to the Vector. For example, the first case sends the string "Ford" as the parameter to the String class constructor to build a new string object with that value. The reference of that object is used as the input parameter to the addElement method of the Vector class. This same thing happens three more times, adding three strings to the Vector, after which the for loop prints the content of the Vector. The loop ends when idx reaches myFirstVector.size(), which in this case is 3.
Note |
Vectors can only contain objects, not primitive type values. To use primitive values, you must create an instance of their wrapper classes, such as addElement(new Integer(5)) |
The example in Listing 6.16 uses two methods that the Vector class supports: the addElement method and the size method. There are many more methods that Vector supports. The most commonly used ones are listed in Table 6.6. For a complete and up-to-date list of these methods, look up the online documentation in the JDK.
Method |
Description |
---|---|
addElement(Object) |
Adds the specified object to the end of this vector and increments the size. |
capacity() |
Returns the current capacity of this vector. Capacity is the potential size of a vector, versus its actual size or element count. |
clone() |
Returns a copy of this vector. All elements are copied. |
contains(Object) |
Tests if the specified object is an element in this vector. |
copyInto(Object[]) |
Copies the elements of this vector into the specified array. |
elementAt(int) |
Returns the element at the specified index (zero-based). |
elements() |
Returns an enumeration of the elements of this vector. (Chapter 9 discusses enumerators.) |
ensureCapacity(int) |
Increases the capacity to the given number. |
firstElement() |
Returns a reference to the first element. |
indexOf(Object) |
Searches for the first occurrence of the given parameter, testing for equality using the equals method on each element. |
indexOf(Object, int) |
Searches for the first occurrence of the first parameter, beginning the search at the zero-based index specified in the second parameter. Again, it tests for equality using equals. |
insertElementAt(Object, int) |
Inserts an object at the specified index. |
isEmpty() |
Tests if this vector has no elements. |
lastElement() |
Returns the last element of the vector. |
lastIndexOf(Object) |
Returns the index of the last occurrence of the specified object in this vector. This is useful for repeated elements. |
lastIndexOf(Object, int) |
Searches backwards for the specified object, starting from the specified index, and returns an index to it. |
removeAllElements() |
Removes all elements from this vector, and sets its size to zero. |
removeElement(Object) |
Removes the first occurrence of the argument from this vector. |
removeElementAt(int) |
Deletes the element at the specified index. |
setElementAt(Object, int) |
Sets the element at the specified index to the specified object. |
setSize(int) |
Sets the size of this vector. |
size() |
Returns the number of elements in this vector. |
toString( |
Returns a string representation of this vector. |
trimToSize() |
Trims the capacity of this vector to be the vector's current size. |
You have seen a straightforward and simple example of a vector. Let's look at a slightly more complex example that uses a few Vector methods. Suppose you are writing the software to register attendees at an event of some kind, maybe a sporting event. You want to track everyone who enters and leaves, and because you can't predict the exact number of attendees, you decide to use a vector to hold the list of names. The program will accept commands from the console and will act appropriately on the commands entered. So, your first requirement is for a static method that will write a prompt string to the console and return what the user typed in. Name this method readFromConsole and put it in a class by itself named Console, so that you can easily reuse it whenever you need to do this common requirement, as shown in Listing 6.17.
Listing 6.17: The Console Class with a Reusable ReadFromConsole Method
import java.io.*; public class Console { /** * Write out a prompt and then read a line from console * @param the string to write out first for the prompt * @return the string read in from the console */ public static String readFromConsole(String prompt) { System.out.println(prompt); // write prompt String userInput = ""; BufferedReader inFileStream = new BufferedReader( new InputStreamReader( new DataInputStream(System.in)) ); try { userInput = inFileStream.readLine(); } catch (IOException exc) { System.out.println("Error: " + exc.getMessage()); } return userInput; } // end readFromConsole method } // end of class Console
You have seen System.out many times for writing to the console; this method shows how to use its opposite, System.in, for reading from the console. Using it involves nesting it inside a number of "stream reading" classes from java.io, and calling the readLine method of one of those classes (BufferedReader). Because this call can throw an exception, you have to monitor for it by placing the call inside a try block and putting the exception-case code inside a catch block. Exceptions aren't discussed in detail until Chapter 10 and the stream-reading classes until Chapter 14, so for now, we simply ask you to accept that this method will work to prompt the user and return what he or she types. Tuck this method away; it is a keeper.
Now, let's return to the task at hand. You want to prompt the user for a single-letter command to tell the application what to do: R to register an attendee, U to "unregister" an attendee, D to display registered attendees, and E to exit the program. The code loops until the user types an E. If the command is to register or unregister, you will need to subsequently prompt the user to type the attendee's name. You will need a class named AttendeeList to contain the list of attendees, including methods to register a new one given a name, unregister an existing one given a name, and display the current list of registered attendees. You will also need a class named Registration that does the looping and prompting, using an instance of AttendeeList.
Let's discuss the Registration class first, in Listing 6.18. This is pretty straightforward, especially given the readFromConsole method created earlier, so let's move on to the AttendeeList class, which encapsulates our list of attendees. Internally, it will use a Vector for the list, but you can nicely hide that detail from users of the class so you are free to change it later.
Listing 6.18: A Registration Class that Prompts the User for Commands
public class Registration { private AttendeeList attendeeList = new AttendeeList(); public void run() { String command = "Not set"; while (!command.equalsIgnoreCase("E")) { command = Console.readFromConsole( "Enter command (R=Register, U=Unregister, D=Display, E=Exit)"); if (command.equalsIgnoreCase("R")) { String name = Console.readFromConsole("Enter name:"); attendeeList.register(name); } else if (command.equalsIgnoreCase("U")) { String name = Console.readFromConsole("Enter name:"); attendeeList.deRegister(name); } else if (command.equalsIgnoreCase("D")) attendeeList.display(); } // end while loop } // end run public static void main(String args[]) { System.out.println("Welcome to class Registration"); Registration register = new Registration(); register.run(); } // end main } // end of class Registration
Before we show you the class, let's assume that you have decided not to simply store String objects in the vector, one per attendee name. Rather, you encapsulate the notion of an Attendee into its own class, which takes the attendee name in the constructor and stores it away in an instance variable that is retrievable with a getName method. The reason for this new class is that, some time in the future, you might want to capture more information about the attendee than just their names. By using a class to represent each attendee, this becomes a simple matter of adding more instance variables to the class.
We will show this Attendee class shortly, but first, Listing 6.19 is the AttendeeList class that will instantiate an instance of Attendee for each attendee who is registered, and store that instance in a private Vector object.
Listing 6.19: The AttendeeList Class that Tracks Attendees at an Event
import java.util.*; public class AttendeeList { private Vector attendees = new Vector(); public void register(String name) { Attendee newAttendee = new Attendee(name); if (!checkForAttendee(newAttendee)) attendees.addElement(newAttendee); else System.out.println("An attendee with this name is already registered"); } public void deRegister(String name) { Attendee theAttendee = new Attendee(name); if (!checkForAttendee(theAttendee)) System.out.println("Sorry, that name is not registered"); else attendees.removeElement(theAttendee); } public boolean checkForAttendee(Attendee attendee) { return attendees.contains(attendee); } public void display() { Attendee currAttendee = null; for (int idx = 0; idx < attendees.size(); idx++) { currAttendee = (Attendee)attendees.elementAt(idx); currAttendee.display(); } } } // end class AttendeeList
This is also pretty straightforward, except for the error-checking. To register an attendee, it first creates an Attendee object, passing the name into the constructor. Then, it checks to see if there is already an attendee with that name registered, in which case it issues an error message. If not, it uses addElement of the Vector class to append the Attendee object to the list. To unregister an attendee, it also first creates an Attendee object using the name as input, and again checks to see if that attendee is already registered. If he or she is, it uses removeElement of the Vector class to remove that element; otherwise, it is an error. To display all registered attendees, the class walks the vector, extracting each element (Attendee object) and calling its display method.
Note that the Vector class only thinks it is storing instances of the generic class Object, so you have to cast the result of an element extraction to the actual class type that element is of, using the casting syntax: (Attendee). Yes, you can cast objects too (discussed in more detail in Chapter 9). For now, take our word this step is required. Otherwise, the call to method display would fail because the Object class does not have a method named display in it.
As you can see, both registration and unregistration require a check for the existence of a given Attendee object in the list, so this code is abstracted out into its own method, checkForAttendee, which the other two methods call. This check is done using the contains method of the Vector class. To determine if a given object is in a vector this way, the Vector code will call the method equals on each element of the list, passing the given object as a parameter. If any one of these calls returns true, then contains returns true. Thus, the classes must code this equals method for this to work, and you will see that the Attendee class does do this. This is also the technique used by the removeElement method call in Vector, to determine which element to remove.
Finally, it is time to see the Attendee class, in Listing 6.20. This is a reasonably straightforward class. The only interesting method in it is the equals method. You must supply this method in your class if you want to store objects of your class in a vector. You must define the method to take one parameter of type Object, but you can always assume that object will actually be the same type as your class, and so do whatever you want to decide if the current object and the passed-in object are "equal." Note that all Java-supplied classes already come with an equals method.
Listing 6.20: The Attendee Class that Represents an Attendee at an Event
public class Attendee { private String name; public Attendee(String name) { this.name = name; } public String getName() { return name; } public boolean equals(Object other) { Attendee otherAttendee = (Attendee)other; return name.equals(otherAttendee.getName()); } public void display() { System.out.println("—————————————"); System.out.println("Name........: " + name); System.out.println(); } }
So what happens when you run Registration and register a few attendees, display the list, and unregister an attendee? Let's have a look at a partial run:
Welcome to class Registration Enter command (R=Register, U=Unregister, D=Display, E=Exit) R Enter name: Phil Coulthard Enter command (R=Register, U=Unregister, D=Display, E=Exit) R Enter name: George Farr Enter command (R=Register, U=Unregister, D=Display, E=Exit) D —————————————————————————— Name........: Phil Coulthard —————————————————————————— Name........: George Farr Enter command (R=Register, U=Unregister, D=Display, E=Exit) u Enter name: George Farr
This little application can be modified to add more information for each attendee, such as phone number, to avoid name collisions.
So far, we have been talking only about Java. What about RPG? Does RPG have anything similar to what Java offers in terms of dynamically allocating and de-allocating storage? The answer, perhaps to your surprise, is yes. In RPG IV, you can perform the same functions as in the event-attendee example. To do this, you would need to first create a function to emulate Java's Vector class. That is, you would have to have the ability to maintain a list of items that can grow on demand. To do this, you would have to use the memory management operation codes ALLOC and DEALLOC. In V5R1 of RPG, these are also available as built-in functions. In either case, they allow you to allocate a specific amount of memory, store the result in a pointer data type (*), free up that memory, and resize that memory.
To emulate the Vector class, you would create a service program with procedures to:
Let's have a look at what the copy member containing the prototypes to implement all of those functions might look like, in Listing 6.21.
Listing 6.21: The Copy Member VECTORPRS with Prototypes for the RPG IV VECTOR Service Program
/* Returns a pointer data type that needs to be passed /* to all subsequent Vector procedures for this vector DVectorAllocate PR * /* Allocates memory and adds a pointer to it to vector. /* Also returns this pointer so caller can populate. DVectorAddElem PR * D vectorHeader@ * VALUE D dataSize 5U 0 VALUE /* Returns pointer to user data at given position nbr DVectorElemAt PR * D vectorHeader@ * VALUE D position 5U 0 VALUE /* Returns position of given user data in vector DVectorIndexOf PR 5U 0 D vectorHeader@ * VALUE D userData@ * VALUE /* Returns current number of elements in vector DVectorGetSize PR 5U 0 D vectorHeader@ * VALUE /* Removes user data pointer from vector and frees mem DVectorRmvElem PR D vectorHeader@ * VALUE D userData@ * VALUE
Really, writing the code to implement this service program amounts to writing a linked-list program in RPG. The twist, though, is that the memory for the list and all the user data pointed to by the elements in the list is dynamically created with the ALLOC operation. While this is a bit of work, it not only allows you to have a dynamically growable list, it also allows you to have more than one of them in a single program. That is because each list gets its own unique memory, just like Java with its objects. So now, you can code an RPG program that has a vector of employees and a vector of customers, if you want. This is good.
We don't show you all the ugly code for a vector here, just a couple of the procedures and all the global fields, in Listing 6.22. You can find the complete code on the included disk in the VECTOR.IRP source file in the LISTING6-22 directory. Put this file and the VECTORPRS.IRP copy member in a QRPGLESRC source file on the AS/400, compile the VECTOR module with CRTRPGMOD, and create from that a service program with the CRTSRVPGM command, specifying *ALL for the EXPORT parameter.
Listing 6.22: Part of the RPG IV VECTOR Module for Emulating Java Vectors
D* Declare the structure for the Vector header information D g_vectorHdr@ S * INZ(*NULL) D g_vectorHdr DS BASED(g_vectorHdr@) D g_vhElemCount 5U 0 D g_vhFirstElem@... D * D g_vhLastElem@ * D g_HdrSize S 10U 0 INZ(%SIZE( D g_vectorHdr)) D* Declare the structure for each vector element D g_vectorElem@ S * INZ(*NULL) D g_vectorElemDS DS BASED(g_vectorElem@) D g_elemData@ * D g_elemDataSize... D 10U 0 D g_elemNext@ * D g_elemHeader@ * D g_elemSize S 10U 0 INZ(%SIZE( D g_vectorElemDS)) PVectorAllocate B EXPORT DVectorAllocate PI * C ALLOC g_HdrSize g_vectorHdr@ C EVAL g_vhElemCount=0 C EVAL g_vhFirstElem@ = *NULL C EVAL g_vhLastElem@ = *NULL C RETURN g_vectorHdr@ PVectorAllocate E PVectorAddElem B EXPORT DVectorAddElem PI * D vectorHeader@ * VALUE D dataSize 5U 0 VALUE C* Local fields D newUserData@ S * D newElement@ S * C* Local logic C EVAL g_vectorHdr@ = vectorHeader@ C ALLOC dataSize newUserData@ C ALLOC g_elemSize g_vectorElem@ C EVAL newElement@ = g_vectorElem@ C EVAL g_elemHeader@ = vectorHeader@ C EVAL g_elemData@ = newUserData@ C EVAL g_elemDataSize = dataSize C EVAL g_elemNext@ = *NULL C EVAL g_vhElemCount = C g_vhElemCount + 1 C IF g_vhLastElem@ *NULL C EVAL g_vectorElem@ = g_vhLastElem@ C EVAL g_elemNext@ = newElement@ C ENDIF C EVAL g_vhLastElem@ = newElement@ C IF g_vhFirstElem@ = *NULL C EVAL g_vhFirstElem@ = newElement@ C ENDIF C return newUserData@ PVectorAddElem E
With these procedures in a service program, you could then code a registration program similar to the one in Java. Listing 6.23 shows you the mainline code for this program.
Listing 6.23: The Mainline RPG IV Program REGISTER that Uses the VECTOR Service Program
D attendees S * INZ(*NULL) D name@ S * INZ(*NULL) D attendeeName S 30A BASED(name@) D command S 30A INZ(*BLANKS) D prompt S 22A INZ( D 'Command: R,U,D,E') D name S 30A INZ(*BLANKS) C EVAL Attendees = VectorAllocate C DOW (command 'E') AND C (command 'e') C EVAL command=ReadFromConsole(prompt) C SELECT C WHEN (command = 'R') OR C (command = 'r') C EVAL name = C ReadFromConsole('Enter name:') C CALLP RegisterAttendee(name) C WHEN (command = 'U') OR C (command = 'u') C EVAL name = C ReadFromConsole('Enter name:') C CALLP DeRegisterAttendee(name) C WHEN (command = 'D') OR (command = 'd') C CALLP DisplayAttendees C ENDSL C ENDDO C EVAL *INLR = *ON
This uses procedures that mimic the Java version. Listing 6.24 shows those procedures. You allocate a vector and store its pointer in the attendees field. You then pass it as the first parameter to each call to the vector procedures. To register an attendee, you call the VectorAddElem procedure and give the size of the memory to allocate for this entry, which is the length of the simple field to store: name. To unregister an attendee, you have to loop through the elements of the vector using the VectorElemAt procedure until an element is found whose content (a simple name) matches the name given. If found, you use that element's address as the input to the VectorRmvElem procedure. To display attendees, you simply walk through all elements of the vector and display the contents of each element.
Listing 6.24: The Rest of the RPG IV Program REGISTER that Uses the VECTOR Service Program
PReadFromConsole B DReadFromConsole PI 30A D prompt 22A VALUE D retField S 30A C prompt DSPLY retField C RETURN retField P E P RegisterAttendee... P B D RegisterAttendee... D PI D name 30A VALUE C EVAL name@ = VectorAddElem(attendees: C %len(name)) C EVAL attendeeName = Name C RETURN P E P DeRegisterAttendee... P B D DeRegisterAttendee... D PI D name 30A VALUE D currPos S 5U 0 INZ(1) C EVAL name@ = C vectorElemAt(attendees:currPos) C DOW (name@ *NULL) AND C (attendeeName name) C EVAL currPos = currPos + 1 C EVAL name@ = C vectorElemAt(attendees:currPos) C ENDDO C IF name@ *NULL C CALLP vectorRmvElem(attendees:name@) C ENDIF C RETURN P E P DisplayAttendees... P B D DisplayAttendees... D PI D currPos S 5U 0 INZ(1) D dummy S 9 0 C EVAL name@ = C vectorElemAt(attendees:currPos) C DOW name@ *NULL C attendeeName DSPLY dummy C EVAL currPos = currPos + 1 C EVAL name@ = C vectorElemAt(attendees:currPos) C ENDDO C RETURN P E
The full source for this example is in Register.irp, which you can upload to a QRPGLESRC file on the AS/400. Compile it into a module first, and then into a program object using CRTPGM and specifying VECTOR in the BNDSRVPGM parameter. To run it, just issue CALL REGISTER from the command line and follow the prompts, as in the Java version. You'll see it behaves identically to its Java equivalent.
This is one use, but indeed, you could use this vector service program in all kinds applications. To write your own programs that use VECTOR, just include the VECTORPRS copy member and link to the VECTOR service program as you have seen with REGISTER. Notice we are only storing a simple character field in each element. More interesting would be to store a data structure. Just remember that whatever you store has to be defined with the BASED keyword so you can set its memory address via a pointer field.
You have seen how to create single- and multi-dimensional arrays and even arrays of objects. You also saw how another class, Vector, is better suited than arrays when you want to dynamically grow and shrink the list on demand. It turns out that there are other times in a programmer's life when perhaps an array is not the best choice to store a list of items. For example, what about "lookup tables" like you have in RPG in the form of tables and the LOOKUP operation? Java has a special class for situations when data needs to be efficiently found using a key value. The class Hashtable in the java.util package is for this.
Hashtables are used to map keys to values. Any object can be used as a key or as a value. Like the Vector class, the Hashtable class does not permit primitive types, so to store integers, for example, you must use the Integer wrapper class, as in new Integer(5) to store the number five, and then integerObject.intValue() to return the primitive value out of the object later. The reason to use a hashtable over an array or a vector is strictly performance. Hashtables can offer good performance benefits when searching for a key in a large number of values.
With a Hashtable, you add elements using the put method and pass in two parameters, the key and the value for that key, like this:
Hashtable attendees = new Hashtable(); attendees.put("5551112222","Phil Coulthard"); attendees.put("5551113333","George Farr");
This examples puts two entries in the hashtable; one has the key "5551112222" and the value "Phil Coulthard," and the second has the key "5551113333" and the value "George Farr." Although this example uses String objects for both the key and values, any object is valid. Further, you do not have to use the same object type for all keys or all values.
Internally, the key value is "hashed" into a number within a particular range, which is then used as the index into an internal table. The given value is then stored at that table position. To subsequently retrieve a value, use the get method and supply the value's key, like this:
String name = (String)attendees.get("5551112222"); if (name != null) System.out.println(name);
Notice the output of the get method has been cast to the actual class type of the value stored for that key, which is String in this case. Again, casting of objects is covered in Chapter 9, but it has to be used here because Hashtable only knows generically about Object objects, and so to use retrieved values, you have to tell Java exactly what the type of that value is. If no value is found with that key, get will return null, which you should test for.
To retrieve a list of all the values in a Hashtable, use the elements method. This returns an object of class type Enumeration (found in java.util as well). Enumeration objects are lists with two simple methods for traversing the list: hasMoreElements returns true while there is more elements in the list, and next returns the next element in the list. Here is an example:
Enumeration names = attendees.elements(); while (names.hasMoreElements()) System.out.println((String)names.next());
You have to cast the result of next like get, since Enumeration is also a generic helper class that works generically on objects of any type. There is also a keys method in Hashtable for similarly returning an Enumeration of the keys. You will find that many Java-supplied classes that contain lists have an elements method that returns an Enumer ation object, including the Vector class. You'll see more of Enumeration in Chapter 9.
There are lots of methods in the Hashtable class, beyond the put, get, elements, and keys methods shown here. For example, there is clear to clear the table, isEmpty to test if it is empty, remove to remove an entry given its key, size to return the size of the table, and containsValue and containsKey to simply test if a given value or given key exists in the table.
In our registration example, we may choose a hashtable if we decide to ask not only for the attendee names, which are not unique, but also their phone numbers which are unique. We basically want to store key-value pairs, where the phone number is the key and the attendee name is the value. Given any key, we want to efficiently find its entry in the list and extract the name. This is exactly what the Hashtable is for, so let's evolve the example to use it.
The first thing we need is a new class to represent the key objects (phone numbers) that will be stored in the hashtable. Once again, we could just use a raw String or Integer object, but we instead prefer to wrap that value inside our own class so that we have more flexibility in the future to change what we define as the key. Listing 6.25 shows the AttendeeKey class.
Listing 6.25: The AttendeeKey Class that Represents a Key Value for an Attendee at an Event
public class AttendeeKey { private String phoneNbr; public AttendeeKey(String phoneNbr) { this.phoneNbr = phoneNbr; } public String getNbr() { return phoneNbr; } public boolean equals(Object other) { AttendeeKey otherAttendee = (AttendeeKey)other; return phoneNbr.equals(otherAttendee.getNbr()); } public int hashCode() { return phoneNbr.hashCode(); } }
This is a simple class that stores the given phone number String object as an instance variable and offers a getNbr method to return it. Again, an equals method must be supplied for the Vector and Hashtable search code to work correctly. In addition to equals, though, a hashCode method must also be supplied to store objects of this class in a Hashtable. This returns an integer value that is unique for each unique object. Typically, we code this method to simply return the hashCode of the key instance field. This works because all Java-supplied classes, like String, already code this method.
Now that we have the key field class, let's update the Attendee class. We want to store a reference to the key field object (the phone number) for this attendee and print it in the display method. The updates are shown in Listing 6.26.
Listing 6.26: The Updated Attendee Class that Stores and Uses a Reference to AttendeeKey
public class Attendee { private String name; private AttendeeKey key; public Attendee(String name, AttendeeKey key) { this.name = name; this.key = key; } public String getName() { return name; } public AttendeeKey getKey() { return key; } public boolean equals(Object other) { Attendee otherAttendee = (Attendee)other; return name.equals(otherAttendee.getName()); } public void display() { System.out.println("—————————————"); System.out.println("Name........: " + name); System.out.println("Number......: " + key.getNbr()); System.out.println(); } }
This simply adds a new parameter to the constructor to take in a key object and subsequently remember it in an instance variable. We want this for our updated display method so that we can display the phone number from the key object. A getKey method is also supplied as a convenience, although it isn't needed here. Notice that the equals method is unchanged. We still consider two Attendee objects to be equal if their name matches, which is important for the containsValue method in the Hashtable class: it searches for a match on a given value object, versus a key object.
Finally, it is time to update the AttendeeList class to use a Hashtable versus a Vector and to accept the phone number in its methods. The updated class is shown in Listing 6.27.
Listing 6.27: The Updated Attendee Class that Stores and Uses a Reference to AttendeeKey
import java.util.*; public class AttendeeList { private Hashtable attendees = new Hashtable(); public void register(String number, String name) { AttendeeKey newKey = new AttendeeKey(number); if (!checkForAttendee(newKey)) attendees.put(newKey, new Attendee(name,newKey)); else System.out.println("An attendee with this number is already registered"); } public void deRegister(String number) { AttendeeKey theKey = new AttendeeKey(number); if (!checkForAttendee(theKey)) System.out.println("Sorry, that number is not registered"); else attendees.remove(theKey); } public boolean checkForAttendee(AttendeeKey key) { return attendees.containsKey(key); } public void display() { Enumeration elements = attendees.elements(); Attendee currAttendee = null; while (elements.hasMoreElements()) { currAttendee = (Attendee)elements.nextElement(); currAttendee.display(); } } }
The changes versus the Vector version are shown in bold. Notice the register method now requires both a number and a name. The number is used to create an AttendeeKey object, and that object is used to check for existence in the list. If that key is not found, put places the key object and an instance of the Attendee class into the hashtable. The deRegister method now takes a phone number as the key to search for and, if found, to remove from the list. The display method now uses the elements method described earlier to iterate the list of value objects, which in this case are instances of Attendee.
Finally, it is time to show the updated Registration class in Listing 6.28, which is simply updated to prompt for a phone number and name for registration, and just a phone number for unregistering.
Listing 6.28: The Updated Registration Class that Prompts for a Phone Number
public class Registration { private AttendeeList attendeeList = new AttendeeList(); public void run() { String command = "Not set"; while (!command.equalsIgnoreCase("E")) { command = Console.readFromConsole( "Enter command (R=Register, U=Unregister, D=Display, E=Exit)"); if (command.equalsIgnoreCase("R")) { String number = Console.readFromConsole( "Enter phone number:"); String name = Console.readFromConsole( "Enter name:"); attendeeList.register(number,name); } else if (command.equalsIgnoreCase("U")) { String number = Console.readFromConsole( "Enter phone number:"); attendeeList.deRegister(number); } else if (command.equalsIgnoreCase("D")) attendeeList.display(); } // end while loop } // end run } // end of class Registration
The main method isn't shown because it is unchanged. Running this a few times gives this result:
Welcome to class Registration Enter command (R=Register, U=Unregister, D=Display, E=Exit) R Enter phone number: 5551112222 Enter name: Phil Coulthard Enter command (R=Register, U=Unregister, D=Display, E=Exit) R Enter phone number: 5552221111 Enter name: George Farr Enter command (R=Register, U=Unregister, D=Display, E=Exit) D ————————————————————————— Name........: George Farr Number......: 5552221111 ————————————————————————— Name........: Phil Coulthard Number......: 5551112222 Enter command (R=Register, U=Unregister, D=Display, E=Exit) U Enter phone number: 1112223333 Sorry, that number is not registered Enter command (R=Register, U=Unregister, D=Display, E=Exit) E
What you should take out of this example is not just how to use hashtables, but also a common design pattern you will see often, where separate classes are used to represent the key values versus the non-key values. The latter class often holds a reference to the former, for convenience. Further, a third separate class is used to represent a list of objects. If the list needs to be searched by key, you use a hashtable of key and value pairs. If it does not need to be searched, you use a simple list such as a vector, containing just the value objects. Basically, separating the key into its own class gives more flexibility.
For fun, we also updated our RPG IV equivalent program for registering attendees to record not just the name of each attendee, but also his or her phone number, and to use that phone number as the key for finding a particular attendee. You can see this on the included disk in the file REGPHONE.IRP, in the LISTING6-28 subdirectory. You will see we also beefed it up a bit to add some error-checking to ensure the phone number entered is syntactically correct, and to check for attempts to register already-registered numbers and unregister never-registered numbers.
How do you pass arrays as a parameter? Or can you even do that in RPG and Java? The answer is yes you can, for both languages. The only difference between the two languages is whether they allow passing arrays by value, reference, or both. Pass-by-value means that the calling program passes the array and its values to a called procedure. If the called procedure alters the values of the array, the original values in the calling program will not be affected. This is not the case for pass-by-reference, where the address of the array is passed to the called procedure, and therefore any alteration to the array values will be reflected in the original program.
Let's take a look at RPG IV, where both calls by value and reference are allowed. Listing 6.29 illustrates pass-by-value.
Listing 6.29: Pass-by-Value in RPG
D Week S 9A DIM(7) CTDATA PERRCD(1) D PASSBYV PR D 9A DIM(7) VALUE * D i S 1P 0 INZ(1) * C DOW i<=7 C Week(i) DSPLY C ADD 1 i C ENDDO C CALLP PASSBYV(Week) C MOVE 1 i C DOW i<=7 C Week(i) DSPLY C ADD 1 i C ENDDO C ADD 1 i C ENDDO C MOVE *ON *INLR P PASSBYV B D PI D weekp 9A DIM(7) VALUE C DOW i<=7 C Move *Blanks weekp(i) C ENDDO P E ** CTDATA week Monday Tuesday Wednesday Thursday Friday Saturday Sunday
This example declares an array, Week, to be a compile-time array. As you can see, in the prototype definition, the keyword VALUE is specified to indicate passing by value. This example initializes the array to the days of week and starts by displaying the values in the array before calling the procedure to indicate the initialization value. Once that is done, it calls the procedure PASSBYV, which is defined in the same example. In the procedure, it tries to clear out the array values by moving blanks to it. Once the call is returned to the caller, it does the same loop to display the content on the array.
Notice that the original values of the array are unchanged, as expected, since the array was passed by VALUE. This would not be the case if you simply removed the keyword VALUE from the example. This one slight change would suddenly make the clearing of the array in the procedure affect the array in the mainline code, such that the second displaying of the elements would result in all blanks. This decision to use VALUE or not in RPG affects all parameter types the same way, whether they are arrays or not.
In Java, this is not the case. As mentioned in Chapter 2, Java does not support pointers. Therefore, Java methods normally cannot change a parameter's value. In other words, Java normally passes parameters to methods using pass-by-value, similar to indicating the VALUE keyword for RPG parameter passing. As it turns out, Java does pass non-primitive types such as objects, arrays, and vectors by reference. This lets the method change the content of the parameter.
If you like, you can even think of the object reference itself as being passed by value. That is, object reference variables are, after all, memory addresses. Therefore, when you pass an object—including an array, which, after all, is an object—you are passing a memory address. That address itself is passed by value, and so if you reset the object to which the variable points, perhaps by equating it to another object or a new object created with the new operator, that address change will not be reflected in the calling code. However, any changes made to the object the variable points to, by invoking methods on the object or changing elements in an array, are reflected to the caller.
Listing 6.30 is an example to illustrate all of this. It has a main method that first declares an array named arrayBefore and initializes it to a set of five numbers. Next, it prints the values as with the RPG example to show that initialization works as expected, but it does this by calling a little helper method named printElements, which is shown at the bottom of the class. Next, it calls another supplied method named changeArrayElements that, as you can see, changes each element by multiplying it by 10. To see if that affected the mainline array, printElements is called again. Next, another supplied method named changeArrayReference is called. This method changes not the elements this time, but the actual array variable itself, pointing it to an entirely new array. If this affects the calling code, you would expect the last call to printElements to show all zeros, since it is a new array.
Listing 6.30: Passing Arrays as Parameters in Java
public class TestArrayParameters { public static void main(String args[]) { int arrayBefore[] = { 1,2,3,4,5 }; printElements("before", arrayBefore); changeArrayElements(arrayBefore); printElements("after element changes ", arrayBefore); changeArrayReference(arrayBefore); printElements("after reference change", arrayBefore); } public static void changeArrayElements(int givenArray[]) { for (int idx = 0; idx < givenArray.length; idx++) givenArray[idx] *= 10; return; } public static void changeArrayReference(int givenArray[]) { givenArray = new int[5]; return; } public static void printElements(String prompt, int array[]) { System.out.print(prompt + ": "); for (int idx = 0; idx < array.length; idx++) System.out.print(array[idx] + " "); System.out.println(); } } // end TestArrayParameters class
Here is the result of running this test case:
C:JAVA>javac TestArrayParameters.java C:JAVA>java TestArrayParameters before: 1 2 3 4 5 after element changes : 10 20 30 40 50 after reference change: 10 20 30 40 50
You can see that the call to changeArrayElements did, in fact, change the contents of the array in the mainline code. However, the call to changeArrayReference had no affect on the mainline array variable arrayBefore—it still points to the same array in memory as it did before the call. Thus, you see that array contents can be changed, but array addresses cannot. Again, the address of the array itself is passed by value.
Remember that primitive values, like integers, are always passed by value in Java. That means you cannot write a method to swap the values of two integer variables, for example. The swap will have no affect on the caller's values of those variables. Pass-by-value effectively means a copy of the parameter values is passed to the method. However, there is one possible way to circumvent this: instead of passing primitive values, you could pass array objects—arrays with only one element, since you have just seen that array elements can be effectively changed by methods. Listing 6.31, then, is a swap method that works in Java.
Listing 6.31: Simulating Pass-by-Reference in Java
public class TestSwap { public static void main(String args[]) { int value1 = 10; int value2 = 20; int value1Array[] = new int[1]; int value2Array[] = new int[1]; value1Array[0] = value1; value2Array[0] = value2; System.out.println("value1 = " + value1 + ", value2 = " + value2); swap(value1Array, value2Array); value1 = value1Array[0]; value2 = value2Array[0]; System.out.println("value1 = " + value1 + ", value2 = " + value2); } public static void swap(int value1[], int value2[]) { int temp = value1[0]; value1[0] = value2[0]; value2[0] = temp; return; } } // end TestSwap class
The important method here is swap, which takes two single-element arrays and swaps the first element of each. The trick to using this method with primitive values is to first move those values into the first element of a couple of arrays created for this purpose, and then, after calling swap with those arrays, move the values back into the primitive variables from the arrays. This is shown in the main method of the example. While this is not a pretty thing to have to do, it does work, as you can see in the following output:
C:JAVA>java TestSwap value1 = 10, value2 = 20 value1 = 20, value2 = 10
This chapter introduced you to the following concepts:
Foreword