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

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

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:

  • Declaration of a variable to contain a reference to an array object
  • Creation of the array object
  • Storage of the array object’s reference in the reference variable
    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:

  • The element type
  • The number of levels of nested arrays
  • The length of the array for at least one of the levels of nesting

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:

  • All reference types:  null
  • Primitive numeric types:  0
  • Primitive boolean type:  false
  • Primitive char type:  the Unicode character with 16 zero-valued bits
    (‘u0000’
    (the NUL character)

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:

  • References to any object instantiated from any class
  • References to array objects whose elements are of any type (primitive
    or reference)
  • A mixture of the two kinds of references.

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

  • The cast operators shown in boldface Italics in Listing 17
  • The attendant required grouping of terms within parentheses

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:

  • Cast is to type int[] instead of object[] (see boldface
    code)
  • Values assigned are type int instead of references to Integer
    objects (see boldface Italic code)

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 element type
  • The number of levels of nested arrays
  • The length of the array for at least one of the levels of nesting

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

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.

# # #

Latest Posts

Related Stories