The Essence of OOP using Java, Array Objects, Part 2

Baldwin explains the programming details involved in the use of array objects.  He shows you three ways to emulate traditional two-dimensional rectangular arrays, and also shows you how to create and use ragged arrays.

Published:  May 22, 2002
By Richard G. Baldwin

Java Programming Notes # 1624


Preface

This series of lessons is designed to teach you about the essence of Object-Oriented Programming (OOP) using Java.

The first lesson in the series was entitled The Essence of OOP Using Java, Objects, and Encapsulation.  The previous lesson was entitled The Essence of OOP using Java, Array Objects, Part 1.

You may find it useful to open another copy of this lesson in a separate browser window.  That will make it easier for you to scroll back and forth among the different listings while you are reading about them.

For further reading, see my extensive collection of online Java tutorials at Gamelan.com. A consolidated index is available at Baldwin's Java Programming Tutorials.

Preview

This lesson explains various details regarding the use of array objects in Java, and illustrates many of those details through the use of sample code.

A sample program shows you three ways to emulate traditional two-dimensional rectangular arrays, and also shows you how to create and use ragged arrays.

Discussion and Sample Code

Array objects

A different syntax is required to create array objects than the syntax normally used to create ordinary objects.  Array objects are accessed via references.  Any of the methods of the Object class can be invoked on a reference to an array object.

The indices of a Java array object

Array objects encapsulate a group of variables, which don't have individual names. They are accessed using positive integer index values.  The integer indices of a Java array object always extend from 0 to (n-1) where n is the length of the array encapsulated in the object.

Multidimensional arrays

All array objects in Java encapsulate one-dimensional arrays.  However, the component type of an array may itself be an array type.  This makes it possible to create array objects whose individual components refer to other array objects. This is the mechanism for creating multi-dimensional or ragged arrays in Java.

Such a structure of array objects can be thought of as a tree of array objects, with the data being stored in the array objects that make up the leaves of the tree.

Array types

When declaring a reference variable capable of referring to an array object, the array type is declared by writing the name of an element type followed by some number of empty pairs of square brackets [].  This is illustrated in Listing 1, which declares a reference variable named v1, capable of storing a reference to a two-dimensional array of type int.
 
  int[][] v1;

Listing 1

(Note that Listing 1 doesn't really declare a two-dimensional array in the traditional sense of other programming languages.  Rather, it declares a reference variable capable of storing a reference to a one-dimensional array object, which in turn is capable of storing references to one-dimensional array objects of type int.)
Multiple pairs of square brackets are allowed

The components in an array object may refer to other array objects.  The number of bracket pairs used in the declaration of the reference variable indicates the depth of array nesting (in the sense that array elements can refer to other array objects).  This is one of the ways that Java implements the concept of traditional multi-dimensional arrays (I will show you some other ways later in this lesson).

The code in Listing 1 shows two levels of nesting for the reference variable of type int[][].

Length not part of variable declaration
(Note that an array's length is not part of its type or reference variable declaration.)

Ragged arrays
(Note also that multi-dimensional arrays, when implemented in this fashion, are not required to represent rectangles, cubes, etc.  For example, the number of elements in each row of a Java two-dimensional array can be different.  Some authors refer to this as a ragged array.)

Allowable types

The specified element type of an array may be any primitive or reference type. Note, however, that all elements of the array must be of the same type (consistent with the type-conversion rules discussed below).

Listing 2 shows the declaration of a reference variable capable of referring to a three-dimensional array object of element type Button (Button is one of the classes in the standard class library).
 
  Button[][][] v2;

Listing 2

Rules of type conversion and assignment compatibility apply

The normal rules of type conversion and assignment compatibility apply when creating and populating array objects.  For example, if the specified type is the name of a non-abstract class, a null reference or a reference to any object instantiated from that class or any subclass of that class may be stored in the array element.

The generic class Object

For example, Listing 3 shows the declaration of a reference variable capable of referring to a one-dimensional array object of element type Object.

Since Object is the superclass of all other classes, this array object is capable of storing references to objects instantiated from any other class.  (As we saw in the previous lesson, it is also capable of storing a reference to any other array object as well.)
 
  Object[] v3;

Listing 3

Primitive type conversions

Similarly, if the declared element type for the array object is one of the primitive types, the elements of the array can be used to store values of any primitive type that is assignment compatible with the declared type (without the requirement for a cast).

For example, the code in Listing 4 shows the creation of a one-dimensional array object capable of storing values of type int.  The array object has a length of 3 elements, and the object's reference is stored in a reference variable named v1.
 
    int[] v1;
    v1 = new int[3];
    byte x1 = 127;
    short x2 = 16384;
    int x3 = 32000;
    v1[0] = x1;
    v1[1] = x2;
    v1[2] = x3;

Listing 4

Assignment-compatible assignments

Values of the types byte, short, and int, are stored in the elements of the array object.

Actual type is lost in the process
(It should be noted that the byte and short values are converted to type int as they are stored.  When retrieved later, they will be retrieved as type int.  Any indication that these values were ever of any type other than int is lost in the process of storing and retrieving the values.)
What about class types?

If the declared element type is the name of a class, (which may or may not be abstract), a null reference or a reference to any object instantiated from the class or any subclass of the class may be stored in the array element.

(Obviously you can't store a reference to an object instantiated from an abstract class, because you can't instantiate an abstract class.)
What about an interface type?

If the declared element type is an interface type, a null reference or a reference to any object instantiated from any class that implements the interface can be stored in the array element.

(This is an extremely powerful concept, allowing references to objects instantiated from many different classes to be collected into an array as the interface type.)
Array reference variables

All array objects are accessed via references.  A reference variable whose declared type is an array type does not contain an array.  Rather, it contains either null, or a reference to an array object.

Allocation of memory

Declaring the reference variable does not create an array, nor does it allocate any space for the array components.  It simply causes memory to be allocated for the reference variable itself, which may later contain a reference to an array object.

Initialization

In the same sense that it is possible to declare a reference variable for an ordinary object, and initialize it with a reference to an object when it is declared, it is also possible to declare a reference to an array object and initialize it with a reference to an array object when it is declared.  This is illustrated in Listing 5, which shows the following operations combined into a single statement:

    int[] v1 = new int[3];

Listing 5

Can refer to different array objects

The length of an array is not established when the reference variable is declared.  As with references to ordinary objects, a reference to an array object can refer to different array objects at different points in the execution of a program.

For example, a reference variable that is capable of referring to an array of type int[] can refer to an array object of a given size at one point in the program and can refer to a different array object of the same type but a different size later in the program.

Placement of square brackets

When declaring an array reference variable, the square brackets [] may appear as part of the type, or following the variable name, or both.  This is illustrated in Listing 6.
 
    int[][] v1;
    int[] v2[];
    int v3[][];


Listing 6

Type and length

Once an array object is created, its type and length never changes.  A reference to a different array object must be assigned to the reference variable to cause the reference variable to refer to an array of different length.

Creating the actual array object

An array object is created by an array creation expression or an array initializer.

An array creation expression (or an array initializer) specifies:

Two valid array creation expressions are illustrated by the boldface statements in Listing 7.
 
    int[][] v1;
    int[] v2[];

    v1 = new int[2][3];
    v2 = new int[10][];

Listing 7

A two-dimensional rectangular array

The first boldface statement creates an array object of element type int with two levels of nesting.  This array object can be thought of as a traditional two-dimensional rectangular array having two rows and three columns.

A ragged array

The second boldface statement also creates an array object of element type int with two levels of nesting.  However, the number of elements in each row is not specified at this point, and it is not appropriate to think of this as a two-dimensional rectangular array.  In fact, once the number of elements in each row has been specified, it may not describe a rectangle at all.  Some authors refer to an array of this type as a ragged array.

The length of the array

The length of the array is always available as a final instance variable named length.  I will show you how to use the value of length in a sample program later in this lesson.

Accessing array elements

An array element is accessed by an array access expression.  The access expression consists of an expression whose value is an array reference followed by an indexing expression enclosed by matching square brackets.

The boldface expression in Listing 8 illustrates an array access expression (or perhaps two concatenated array access expressions).
 
    int[][] v1 = new int[2][3];
    System.out.println(v1[0][1]);

Listing 8

First-level access

This array access expression first accesses the contents of the element at index 0 in the array object referred to by the reference variable named v1.  This is a reference to a second array object (note the double matching square brackets, [][]).

Second-level access

The array access expression in Listing 8 uses that reference to access the value stored in the element at index value 1 in the second array object.  That value is then passed to the println method for display on the standard output device.

(In this case, the value 0 is displayed, because array elements are automatically initialized to default values when the array object is created.  The default value for all primitive numeric values is zero.)
Zero-based indexing

All array indexes in Java begin with 0. An array with length n can be indexed by the integers 0 to (n-1).  Array accesses are checked at runtime.  If an attempt is made to access the array with any other index value, an ArrayIndexOutOfBoundsException will be thrown.

Index value types

Arrays must be indexed by integer values of the following types: int, short, byte, or char.  For any of these types other than int, the value will be promoted to an int and used as the index.

An array cannot be accessed using an index of type long.  Attempting to do so results in a compiler error.

Default initialization

If the elements in an array are not purposely initialized when the array is created, the array elements will be automatically initialized with default values.  The default values are:

Explicit initialization of array elements

The values in the array elements may be purposely initialized when the array object is created using a comma-separated list of expressions enclosed by matching curly braces.  This is illustrated in Listing 9.
 
    int[] v1 = {1,2,3,4,5};

Listing 9

No new operator
(Note that this format does not make use of the new operator.  Also note that the expressions in the list may be much more complex than the simple literal values shown in Listing 9.)
Length and order

The length of the constructed array will equal the number of expressions in the list.

The expressions in an array initializer are executed from left to right in the order that they occur in the source code.  The first expression specifies the value at index value zero, and the last expression specifies the value at index value n-1 (where n is the length of the array).

Each expression must be assignment-compatible with the array's component type, or a compiler error will occur.

A sample program

The previous paragraphs in this lesson have explained some of the rules and characteristics regarding array objects.  They have also illustrated some of the syntax involved in the use of array objects in Java.

More powerful and complex

Many aspiring Java programmers find the use of array objects to be something less than straightforward, and that is understandable.  In fact, Java array objects are somewhat more powerful than array structures in many other programming languages, and this power often manifests itself in additional complexity.

A traditional two-dimensional rectangular array

Some of that complexity is illustrated by the program named Array07, shown in Listing 26 near the end of this lesson.  This program illustrates three different ways to accomplish essentially the same task using array objects in Java.  That task is to emulate a traditional two-dimensional rectangular array as found in other programming languages.  Two of the ways that are illustrated are essentially ragged arrays with subarrays having equal length.

Ragged arrays

The program also illustrates two different ways to work with array objects and ragged arrays.

Will discuss in fragments

As is my practice, I will discuss and explain the program in fragments.

All of the interesting code in this program is contained in the main method, so I will begin my discussion with the first statement in the main method.

Create a two-dimensional rectangular array structure

Listing 10 creates an array structure that emulates a traditional rectangular array with two rows and three columns.
 
    Object[][] v1 = new Object[2][3];

Listing 10

Requires all rows to be same length
(Note that unlike the ragged array structures to be discussed later, this approach requires all rows to be the same length.)
Reference variable declaration

The code in Italics (to the left of the equal sign, =) in Listing 10 declares a reference variable named v1.  This reference variable is capable of holding a reference to an array object whose elements are of the type Object[].

In other words, this reference variable is capable of holding a reference to an array object, whose elements are capable of holding references to other array objects, whose elements are of type Object.

Two levels of nesting
(The existence of double matching square brackets in the variable declaration in Listing 10 indicates two levels of nesting.)
Not very general

This is not a very general approach.  In particular, the elements in the array object referred to by v1 can only hold references to other array objects whose element type is Object (or references to array objects whose element type is a subclass of Object).

The elements in the array object referred to by v1 cannot hold references to ordinary objects instantiated from classes, or array objects whose element type is a primitive type.

In other words, the elements in the array object referred to by v1 can only hold references to other array objects.  The element types of those array objects must be assignment compatible with the type Object (this includes interface types and class types but not primitive types).

A tree of empty array objects

The boldface code in Listing 10 (to the right of the equal sign, =) creates a tree structure of array objects.  The object at the root of the tree is an array object of type Object[], having two elements (a length of two).

The reference variable named v1 refers to the array object that forms the root of the tree.

Each of the two elements in the root array object is initialized with a reference to another array object.

(These two objects might be viewed as subarrays, or as child nodes in the tree structure).
Each of the child nodes is an array object of type Object, and has a length of three.

Each element in each of the two child node array objects is initialized to the value null (this is the default initialization for array elements of reference types that don't yet refer to an object).

Recap

To recap, the reference variable named v1 contains a reference to a two-element, one-dimensional array object.  Each element in that array object is capable of storing a reference of type Object[] (a reference to another one-dimensional array object of type Object).

Two subarray objects

Two such one-dimensional subarray (or child node) objects, of element type Object, are created.  References to the two subarray objects are stored in the elements of the two-element array object at the root of the tree.

Each of the subarray objects has three elements.  Each element is capable of storing a reference to an object as type Object.

The leaves of the tree
(These two subarray objects might be viewed as the leaves of the tree structure.)
Initialize elements to null

However, the objects of type Object don't exist yet.  Therefore, each element in each of the subarray objects is automatically initialized to null.

Arrays versus subarrays
(Note that there is no essential difference between an array object and a subarray object in the above discussion.  The use of the sub prefix is used to indicate that an ordinary array object belongs to another array object, because the reference to the subarray object is stored in an element of the owner object.)
Many dimensions are possible

Multi-dimensional arrays of any (reasonable) depth can be emulated in this manner.  An array object may contain references to other array objects, which may contain references to other array objects, and so on.

The leaves of the tree structure

Eventually, however, the elements of the leaves in the tree structure must be specified to contain either primitive values or references.  This is where the data is actually stored.

(Note however, that if the leaves are specified to contain references of type Object, they may contain references to other array objects of any type, and the actual data could be stored in those array objects.)
The length of an array

Every array object contains a public final instance variable named length, which contains an integer value specifying the number of elements in the array.

Once created, the length of the array encapsulated in an array object cannot change.  Therefore, the value of length specifies the length of the array throughout the lifetime of the array object.

Using length to populate the leaves of the tree structure

The value of length is very handy when processing array objects.  This is illustrated in Listing 11, which uses a nested for loop to populate the elements in the leaves of the tree structure referred to by v1(The elements in the leaf objects are populated with references to objects of type Integer.)
 
    for(int i=0;i<v1.length;i++){
      for(int j=0;j<v1[i].length;j++){
        v1[i][j] = 
              new Integer((i+1)*(j+1));
      }//end inner loop
    }//end outer loop

Listing 11

Using length in loop's conditional expressions

Hopefully by now you can read and understand this code without a lot of help from me.  I will point out, however, that the value returned by v1.length (in the conditional expression for the outer loop) is the number of leaves in the tree structure (this tree structure has two leaves).

I will also point out that the value returned by v1[i].length (in the conditional expression for the inner loop) is the number of elements in each leaf array object (each leaf object in this tree structure has three elements).

Finally, I will point out that the expression v1[i][j] accesses the jth element in the ith leaf, or subarray.  In the traditional sense of a rectangular array, this could be thought of as the jth column of the ith row.  This mechanism is used to store object references in each element of each of the leaf array objects.

Populate with references to Integer objects

Thus, each element in each leaf array object is populated with a reference to an object of the type Integer.  Each object of the type integer encapsulates an int value calculated from the current values of the two loop counters.

Display leaf object contents

In a similar manner, the code in Listing 12 uses the length values in the conditional expressions of nested for loops to access the references stored in the elements of the leaf array objects, and to use those references to access and display the values encapsulated in the Integer objects whose references are stored in those elements.
 
    for(int i=0;i<v1.length;i++){
      for(int j=0;j<v1[i].length;j++){
        System.out.print(
                       v1[i][j] + " ");
      }//end inner loop
      System.out.println();//new line
    }//end outer loop

Listing 12

The rectangular output

The code in Listing 12 produces the following output on the screen.

1 2 3
2 4 6

As you can see, this emulates a traditional two-dimensional array having two rows and three columns.

A ragged array with two rows and three columns

The second approach to emulating a traditional two-dimensional rectangular array will create a ragged array where each row is the same length.

(With a ragged array, the number of elements in each row can be different.)
The most significant thing about this approach is the manner in which the tree of array objects is created (see Listing 13).
 
    Object[][] v2 = new Object[2][];

Listing 13

Three statements required

With this approach, three statements are required to replace one statement from the previous approach.  (Two additional statements are shown in Listing 14.)

A single statement in the previous approach (Listing 10) created all three array objects required to construct the tree of array objects, and initialized the elements in the leaf array objects with null values.

Create only the root array object

However, the boldface code in Listing 13 creates only the array object at the root of the tree.  That array object is an array object having two elements capable of storing references of type Object[].

Empty square brackets

If you compare this statement with the statement in Listing 10, you will notice that the right-most pair of square brackets in Listing 13 is empty.  Thus, Listing 13 creates only the array object at the root of the tree, and initializes the elements in that array object with null values.

Leaf array objects don't exist yet

The leaf array objects don't exist at the completion of execution of the statement in Listing 13.

Create the leaf array objects

The boldface portions of the statements in Listing 14 create two array objects of element type Object.
 
    v2[0] = new Object[3];
    v2[1] = new Object[3];

Listing 14

Save the references to the leaves

References to these two leaf objects are stored in the elements of the array object at the root of the tree, (which was created in Listing 13).  Thus, these two array objects become the leaves of the tree structure of array objects.

This completes the construction of the tree structure.  Each element in each leaf object is initialized with null.

Why bother?

You might ask why I would bother to use this approach, which requires three statements in place of only one statement in the previous approach.

The answer is that I wouldn't normally use this approach if my objective were to emulate a traditional rectangular array.  However, this approach is somewhat more powerful than the previous approach.

The lengths of the leaf objects can differ

With this approach, the length of the two leaf array objects need not be the same.  Although I caused the length of the leaf objects to be the same in this case, I could just as easily have caused them to be different lengths (I will illustrate this capability later in the program).

Populate and display the data

If you examine the complete program in Listing 26 near the end of the lesson, you will see that nested for loops, along with the value of length was used to populate and display the contents of the leaf array objects.  Since that portion of the code is the same as with the previous approach, I won't show and discuss it here.

The rectangular output

This approach produced the following output on the screen, (which is the same as before):

1 2 3
2 4 6

Now for something really different

The next approach that I am going to show you for emulating a two-dimensional rectangular array is significantly different from either of the previous two approaches.

Not element type Object[]

In this approach, I will create a one-dimensional array object of element type Object (not element type Object[]).  I will populate the elements of that array object with references to other array objects of element type Object.  In doing so, I will create a tree structure similar to those discussed above.

The length of the leaf objects

As with the second approach above, the array objects that make up the leaves of the tree can be any length, but I will make them the same length in order to emulate a traditional rectangular two-dimensional array.

Create the array object

First consider the statement shown in Listing 15.  Compare this statement with the statements shown earlier in Listing 10 and Listing 13.
 
    Object[] v3 = new Object[2];
Listing 15

No double square brackets

Note in particular that the statement in Listing 15 does not make use of double square brackets, as was the case in Listing 10 and Listing 13.  Thus, the statement show in Listing 15 is entirely different from the statements shown in Listing 10 and Listing 13.

Declare a reference variable

That portion of the statement shown in Italics (to the left of the equal sign, =) declares a reference variable capable of storing a reference to an array object whose elements are capable of storing references of the type Object (not type Object[] as in the previous examples).

Refer to the root object

This reference variable will refer to an array object that forms the root of the tree structure.  However, the root object in this case will be considerably different from the root objects in the previous two cases.

In the previous two cases, the elements of the root object were required to store references of type Object[] (note the square brackets). In other words, an array object whose elements are of type Object[] can only store references to other array objects whose elements are of type Object.

A more general approach

However, an array object whose elements are of type Object (as is the case here), can store:

Therefore, this is a much more general, and much more powerful approach.

A price to pay

However, there is a price to pay for the increased generality and power.  In particular, the programmer who uses this approach must have a much better understanding of Java object-oriented programming concepts than the programmer who uses the two approaches discussed earlier in this lesson.

Particularly true relative to first approach

This is particularly true relative to the first approach discussed earlier.  That approach is sufficiently similar to the use of multi-dimensional arrays in other languages that a programmer with little understanding of Java object-oriented programming concepts can probably muddle through the syntax based on prior knowledge.  However, it is unlikely that a programmer could muddle through this approach without really understanding what he is doing.

Won't illustrate true power

Although this approach is very general and very powerful, this sample program won't attempt to illustrate that power and generality.  Rather, this sample program will use this approach to emulate a traditional two-dimensional rectangular array just like the first two approaches discussed earlier. (Later, I will also use this approach for a couple of ragged arrays.)

Populate the root object

The two statements in Listing 16 create two array objects, each having three elements.  Each element is capable of storing a reference to any object that is assignment compatible with the Object type.

(Assignment compatibility includes a reference to any object instantiated from any class, or a reference to any array object of any type (including primitive types), or a mixture of the two.)
    v3[0] = new Object[3];
    v3[1] = new Object[3];

Listing 16

References to the two new array objects are stored in the elements of the array object that forms the root of the tree structure.  The two new array objects form the leaves of the tree structure.

Populate the leaf array objects

As in the previous two cases, the code in Listing 17 uses nested for loops to populate the array elements in the leaf objects with references to new Integer objects.  (The Integer objects encapsulate int values based on the loop counter values for the outer and inner loops.)
 
    for(int i=0;i<v3.length;i++){
      for(int j=0;
            j<((Object[])v3[i]).length;
                                  j++){
        ((Object[])v3[i])[j] = 
              new Integer((i+1)*(j+1));
      }//end inner loop
    }//end outer loop

Listing 17

Added complexity

The added complexity of this approach manifests itself in

Inside and outside the parentheses

Note that within the inner loop, one of the square-bracket accessor expressions is inside the parentheses and the other is outside the parentheses.

Why are the casts necessary?

The casts are necessary to convert the references retrieved from the array elements from type Object to type Object[].  For example, the reference stored in v3[i] is stored as type Object.

Get length of leaf array object

The cast in the following expression converts that reference to type Object[] before attempting to get the value of length belonging to the array object whose reference is stored there.

((Object[])v3[i]).length

Assign a value to an element in the leaf array object

Similarly, the following expression converts the reference stored in v3[i] from type Object to type Object[].  Having made the conversion, it then accesses the jth element of the array object whose reference is stored there (in order to assign a value to that element).

((Object[])v3[i])[j]= ...

Display data in leaf array objects

Listing 18 uses similar casts to get and display the values encapsulated in the Integer objects whose references are stored in the elements of the leaf array objects.
 
    for(int i=0;i<v3.length;i++){
      for(int j=0;
            j<((Object[])v3[i]).length;
                                  j++){
        System.out.print(
           ((Object[])v3[i])[j] + " ");
      }//end inner loop
      System.out.println();//new line
    }//end outer loop

Listing 18

The rectangular output

This approach produced the following output on the screen, (which is the same as the previous two approaches):

1 2 3
2 4 6

Ragged arrays

All the code in the previous three cases has been used to emulate a traditional rectangular two-dimensional array.  In the first case, each row was required to have the same number of elements by the syntax used to create the tree of array objects.

In the second and third cases, each row was not required to have the same number of elements, but they were programmed to have the same number of elements in order to emulate a rectangular two-dimensional array.

A triangular array, sort of ...

Now I am going to show you some cases that take advantage of the ragged-array capability of Java array objects.  In the next case, (beginning with Listing 19), I will create a ragged array having two rows.  The first row will have two elements and the second row will have three elements.  (This array object might be thought of as being sort of triangular.)
 
    Object[][] v4 = new Object[2][];
    v4[0] = new Object[2];
    v4[1] = new Object[3];

Listing 19

You have seen this before

You saw code like this before in the second case discussed earlier.  However, in that case, the second and third statements created new array objects having the same length.  In this case, the second and third statements create array objects having different lengths.  This is one of the ways to create a ragged array in Java (you will see another way in the next case that I will discuss).

Populate the leaf array objects

Listing 20 populates the elements of the leaf array objects with references to objects of the class Integer.
 
    for(int i=0;i<v4.length;i++){
      for(int j=0;j<v4[i].length;j++){
        v4[i][j] = 
              new Integer((i+1)*(j+1));
      }//end inner loop
    }//end outer loop

Listing 20

You have seen this before also

You have also seen the code in Listing 20 before.  I repeated it here because this case clearly emphasizes the value of the length constant that is available in all Java array objects.  In the earlier case, the length of the two leaf array objects was the same, so it would have been feasible to simply hard-code that value into the conditional expression of the inner for loop.

The length is not the same now

However, in this case, the length of the two leaf array objects is not the same.  Therefore, it wouldn't work to hard-code a limit into the conditional expression of the inner for loop.  However, because the length of each leaf array object is available as a public member of the array object, that value can be used to control the number of iterations of the inner loop for each separate leaf array object.

The triangular output

The next section of code in the program shown in Listing 26 near the end of the lesson uses the same code as before to display the int values encapsulated in the Integer objects whose references are stored in the leaf array objects.  Since it is the same code as before, I won't repeat it here.

The output produced by this case is shown below:

1 2
2 4 6

Note that this is not the same as before, and this output does not describe a rectangular array. Rather, it describes a ragged array where the rows are of different lengths.

(As I indicated earlier, it is sort of triangular.  However, it could be any shape that you might want it to be.)
A more general approach

The next case, shown in Listing 21, is the same as the third case discussed earlier, except that the lengths of the leaf array objects are not the same.

As before, this case creates a one-dimensional array object of type Object (having two elements) that forms the root of a tree.  Each element in the root object contains a reference to another array object of type Object.

One of those leaf objects has two elements and the other has three elements, thus producing a ragged array (you could make the lengths of those objects anything that you want them to be).
 
    Object[] v5 = new Object[2];
    v5[0] = new Object[2];
    v5[1] = new Object[3];

Listing 21

Populate the leaf objects

As before, the elements in the leaf array objects are populated with references to objects of the class Integer, which encapsulate int values based on the current value of the loop counters.  This is shown in Listing 22.
 
    for(int i=0;i<v5.length;i++){
      for(int j=0;
            j<((Object[])v5[i]).length;
                                  j++){
        ((Object[])v5[i])[j] = 
              new Integer((i+1)*(j+1));
      }//end inner loop
    }//end outer loop

Listing 22

Same code as before

This is the same code that you saw in Listing 17.  I repeated it here to emphasize the requirement for casting (shown in boldface in Listing 22).

Display the data

This case uses the same code as Listing 18 to display the int values encapsulated by the Integer objects whose references are stored in the elements of the leaf array objects.  I won't repeat that code here.

The triangular output

The output produced by this case is shown below:

1 2
2 4 6

Note that this is the same as the case immediately prior to this one.  Again, it does not describe a rectangular array. Rather, it describes a ragged array where the rows are of different lengths.

A more general case

I'm going to show you one more general case for a ragged array.  This case illustrates a more general approach.  In this case, I will create a one-dimensional array object of element type Object.  I will populate the elements of that array object with references to other array objects.  These array objects will be the leaves of the tree.

Leaf array objects are type int

In this case, the leaves won't be of element type Object.  Rather, the elements in the leaf objects will be designed to store primitive int values.

(An even more general case would be to populate the elements of the root object with references to a mixture of objects of class types, interface types, and array objects where the elements of the array objects are designed to store primitives of different types, and references of different types.  Note, however, each leaf array object must be designed to store a single type, but will accept for storage any type that is assignment-compatible with the specified type for the array object.)
This case begins in Listing 23, which creates the root array object, and populates its elements with references to leaf array objects of type int.
 
    Object[] v6 = new Object[2];
    v6[0] = new int[7];
    v6[1] = new int[3];

Listing 23

Leaf objects are different lengths

One of the leaf array objects has a length of 7.  The other has a length of 3.

Populate the leaf array elements

Listing 24 populates the elements in the leaf array objects with values of type int.
 
    for(int i=0;i<v6.length;i++){
      for(int j=0;
            j<((int[])v6[i]).length;
                                  j++){
        ((int[])v6[i])[j] = (i+2)*(j+2);
      }//end inner loop
    }//end outer loop

Listing 24

Similar to previous code

The code in Listing 24 is similar to code that you saw earlier.  The differences are:

Display the output

Finally, Listing 25 displays the int values stored in the elements of the leaf array objects.
 
    for(int i=0;i<v6.length;i++){
      for(int j=0;
            j<((int[])v6[i]).length;
                                  j++){
        System.out.print(
           ((int[])v6[i])[j] + " ");
      }//end inner loop
      System.out.println();//new line
    }//end outer loop

Listing 25

The code in Listing 25 is very similar to what you have seen before, and there should be no requirement for an explanation of this code.

The code in Listing 25 produces the following output:

4 6 8 10 12 14 16
6 9 12

I will leave it as an exercise for the student to correlate the output with the code.
 

Summary

When declaring a reference variable capable of referring to an array object, the array type is declared by writing the name of an element type followed by some number of empty pairs of square brackets [].

The components in an array object may refer to other array objects.  The number of bracket pairs used in the declaration of the reference variable indicates the depth of array nesting (in the sense that array elements can refer to other array objects).

An array's length is not part of its type or reference variable declaration.

Multi-dimensional arrays are not required to represent rectangles, cubes, etc.  They can be ragged.

The normal rules of type conversion and assignment compatibility apply when creating and populating array objects.

Object is the superclass of all other classes.  Therefore, an array of element type Object is capable of storing references to objects instantiated from any other class.  The type declaration for such an array object would be Object[].

An array of element type Object is also capable of storing references to any other array object.

If the declared element type for the array object is one of the primitive types, the elements of the array can be used to store values of any primitive type that is assignment compatible with the declared type (without the requirement for a cast).

If the declared element type is the name of a class, (which may or may not be abstract), a null reference or a reference to any object instantiated from the class or any subclass of the class may be stored in the array element.

If the declared element type is an interface type, a null reference or a reference to any object instantiated from any class that implements the interface can be stored in the array element.

A reference variable whose declared type is an array type does not contain an array.  Rather, it contains either null, or a reference to an array object.  Declaring the reference variable does not create an array, nor does it allocate any space for the array components.

It is possible to declare a reference to an array object and initialize it with a reference to an array object when it is declared.

A reference to an array object can refer to different array objects (of the same element type and different lengths) at different points in the execution of a program.

When declaring an array reference variable, the square brackets [] may appear as part of the type, or following the variable name, or both.

Once an array object is created, its type and length never changes.

An array object is created by an array creation expression or an array initializer.

An array creation expression (or an array initializer) specifies:

The length of the array is always available as a final instance variable named length.

An array element is accessed by an expression whose value is an array reference followed by an indexing expression enclosed by matching square brackets.

If an attempt is made to access the array with an invalid index value, an ArrayIndexOutOfBoundsException will be thrown.

Arrays must be indexed by integer values of the types int, short, byte, or char.  An array cannot be accessed using an index of type long.

If the elements in an array are not purposely initialized when the array is created, the array elements will be automatically initialized with default values.

The values in the array elements may be purposely initialized when the array object is created using a comma-separated list of expressions enclosed by matching curly braces.

The program in this lesson illustrated three different ways to emulate traditional rectangular two-dimensional arrays.

The program also illustrated two different ways to create and work with ragged arrays.

What's Next?

In the next lesson, I will provide so additional information about array objects, and then illustrate the use of the classes named Array and Arrays for the creation and manipulation of array objects.

Complete Program Listing

A complete listing of the program is shown in Listing 26 below.
 
/*File Array07.java
Copyright 2002, R.G.Baldwin

This program illustrates three 
different ways to emulate a traditional
rectangular array in Java.  Two of 
those ways are essentially ragged
arrays with equal-length sub arrays.

The program also illustrates two ways
to create ragged arrays in Java.

Tested using JDK 1.3 under Win 2000.
**************************************/

public class Array07{
  public static void main(
                        String[] args){

    //Create an array structure that
    // emulates a traditional 
    // rectangular array with two rows
    // and three columns.  This
    // approach requires all rows to
    // be the same length.
    Object[][] v1 = new Object[2][3];
    //Populate the array elements with
    // references to objects of type 
    // Integer.
    for(int i=0;i<v1.length;i++){
      for(int j=0;j<v1[i].length;j++){
        v1[i][j] = 
              new Integer((i+1)*(j+1));
      }//end inner loop
    }//end outer loop
    //Display the array elements
    for(int i=0;i<v1.length;i++){
      for(int j=0;j<v1[i].length;j++){
        System.out.print(
                       v1[i][j] + " ");
      }//end inner loop
      System.out.println();//new line
    }//end outer loop
    System.out.println();//new line   

    //Create a ragged array with two
    // rows.  The first row has three
    // columns and the second row has
    // three columns.  The length of
    // each row could be anything, but
    // was set to three to match the
    // above array structure.
    Object[][] v2 = new Object[2][];
    v2[0] = new Object[3];
    v2[1] = new Object[3];
    //Populate the array elements with
    // references to objects of type 
    // Integer.
    for(int i=0;i<v2.length;i++){
      for(int j=0;j<v2[i].length;j++){
        v2[i][j] = 
              new Integer((i+1)*(j+1));
      }//end inner loop
    }//end outer loop
    //Display the array elements
    for(int i=0;i<v2.length;i++){
      for(int j=0;j<v2[i].length;j++){
        System.out.print(
                       v2[i][j] + " ");
      }//end inner loop
      System.out.println();//new line
    }//end outer loop
    System.out.println();//new line

    //Create a one-dimensional array
    // of type Object, which contains
    // references to array objects of 
    // type Object.  The secondary
    // array objects could be of any
    // length, but were set to three
    // to match the above array 
    // structure.
    Object[] v3 = new Object[2];
    v3[0] = new Object[3];
    v3[1] = new Object[3];
    //Populate the array elements with
    // references to objects of type 
    // Integer.
    for(int i=0;i<v3.length;i++){
      for(int j=0;
            j<((Object[])v3[i]).length;
                                  j++){
        ((Object[])v3[i])[j] = 
              new Integer((i+1)*(j+1));
      }//end inner loop
    }//end outer loop
    //Display the array elements
    for(int i=0;i<v3.length;i++){
      for(int j=0;
            j<((Object[])v3[i]).length;
                                  j++){
        System.out.print(
           ((Object[])v3[i])[j] + " ");
      }//end inner loop
      System.out.println();//new line
    }//end outer loop
    System.out.println();//new line

    //Create a ragged array with two
    // rows.  The first row has two
    // columns and the second row has
    // three columns.
    Object[][] v4 = new Object[2][];
    v4[0] = new Object[2];
    v4[1] = new Object[3];
    //Populate the array elements with
    // references to objects of type 
    // Integer.
    for(int i=0;i<v4.length;i++){
      for(int j=0;j<v4[i].length;j++){
        v4[i][j] = 
              new Integer((i+1)*(j+1));
      }//end inner loop
    }//end outer loop
    //Display the array elements
    for(int i=0;i<v4.length;i++){
      for(int j=0;j<v4[i].length;j++){
        System.out.print(
                       v4[i][j] + " ");
      }//end inner loop
      System.out.println();//new line
    }//end outer loop
    System.out.println();//new line

    //Create a one-dimensional array
    // of type Object, which contains
    // references to array objects of 
    // type Object.  The secondary
    // array objects could be of any
    // length, but were set to two and
    // three to match the ragged array 
    // above.
    Object[] v5 = new Object[2];
    v5[0] = new Object[2];
    v5[1] = new Object[3];
    //Populate the array elements with
    // references to objects of type 
    // Integer.
    for(int i=0;i<v5.length;i++){
      for(int j=0;
            j<((Object[])v5[i]).length;
                                  j++){
        ((Object[])v5[i])[j] = 
              new Integer((i+1)*(j+1));
      }//end inner loop
    }//end outer loop
    //Display the array elements
    for(int i=0;i<v5.length;i++){
      for(int j=0;
            j<((Object[])v5[i]).length;
                                  j++){
        System.out.print(
           ((Object[])v5[i])[j] + " ");
      }//end inner loop
      System.out.println();//new line
    }//end outer loop
    System.out.println();
   
    //Create a one-dimensional array
    // of type int, which contains
    // references to array objects of 
    // type Object.  The secondary
    // array objects could be of any
    // length.
    Object[] v6 = new Object[2];
    v6[0] = new int[7];
    v6[1] = new int[3];
    //Now illustrate that the elements
    // of the leaves of a ragged array
    // implemented in this manner can
    // contain primitive values.
    // Populate the array elements with
    // type int.
    for(int i=0;i<v6.length;i++){
      for(int j=0;
            j<((int[])v6[i]).length;
                                  j++){
        ((int[])v6[i])[j] = (i+2)*(j+2);
      }//end inner loop
    }//end outer loop
    //Display the array elements
    for(int i=0;i<v6.length;i++){
      for(int j=0;
            j<((int[])v6[i]).length;
                                  j++){
        System.out.print(
           ((int[])v6[i])[j] + " ");
      }//end inner loop
      System.out.println();//new line
    }//end outer loop
   
  }//end main
}//end class Array07
//===================================//

Listing 26


Copyright 2002, Richard G. Baldwin.  Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.

About the author

Richard Baldwin is a college professor (at Austin Community College in Austin, TX) and private consultant whose primary focus is a combination of Java and XML. In addition to the many platform-independent benefits of Java applications, he believes that a combination of Java and XML will become the primary driving force in the delivery of structured information on the Web.

Richard has participated in numerous consulting projects involving Java, XML, or a combination of the two.  He frequently provides onsite Java and/or XML training at the high-tech companies located in and around Austin, Texas.  He is the author of Baldwin's Java Programming Tutorials, which has gained a worldwide following among experienced and aspiring Java programmers. He has also published articles on Java Programming in Java Pro magazine.

Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.

baldwin.richard@iname.com

-end-