Array
initialization
Initializing
arrays in C is error-prone and tedious. C++ uses
aggregate
initialization
to make it much safer.
[22]
Java has no “aggregates” like C++, since everything is an object in
Java. It does have arrays, and these are supported with array
initialization.
An
array is simply a sequence of either objects or primitives, all the same type
and packaged together under one identifier name. Arrays are defined and used
with the square-brackets indexing
operator
[ ].
To define an array you simply follow your type name with empty square brackets:
You
can also put the square brackets after the identifier to produce exactly the
same meaning:
This
conforms to expectations from C and C++ programmers. The former style, however,
is probably a more sensible syntax, since it says that the type is “an
int
array.” That style will be used in this book.
The
compiler doesn’t allow you to tell it how big the array is. This brings
us back to that issue of “handles.” All that you have at this point
is a handle to an array, and there’s been no space allocated for the
array. To create storage for the array you must write an initialization
expression. For arrays, initialization can appear anywhere in your code, but
you can also use a special kind of initialization expression that must occur at
the point where the array is created. This special initialization is a set of
values surrounded by curly braces. The storage allocation (the equivalent of
using
new)
is taken care of by the compiler in this case. For example:
int[]
a1 = { 1, 2, 3, 4, 5 };
So
why would you ever define an array handle without an array?
Well,
it’s possible to assign one array to another in Java, so you can say:
What
you’re really doing is copying a handle, as demonstrated here:
//: Arrays.java
// Arrays of primitives.
public class Arrays {
public static void main(String[] args) {
int[] a1 = { 1, 2, 3, 4, 5 };
int[] a2;
a2 = a1;
for(int i = 0; i < a2.length; i++)
a2[i]++;
for(int i = 0; i < a1.length; i++)
prt("a1[" + i + "] = " + a1[i]);
}
static void prt(String s) {
System.out.println(s);
}
} ///:~
You
can see that
a1
is given an initialization value while
a2
is not;
a2
is assigned later – in this case, to another array.
There’s
something new here: all arrays have an intrinsic member (whether they’re
arrays of objects or arrays of primitives) that you can query – but not
change – to tell you how many elements there are in the array. This
member is
length.
Since arrays in Java, like C and C++, start counting from element zero, the
largest element you can index is
length
- 1
.
If you go out of bounds,
C and C++ quietly accept this and allow you to stomp all over your memory,
which is the source of many infamous bugs. However, Java protects you against
such problems by causing a run-time error (an
exception,
the subject of Chapter 9) if you step out of bounds. Of course, checking every
array access costs time and code and there’s no way to turn it off, which
means that array accesses might be a source of inefficiency in your program if
they occur at a critical juncture. For Internet security and programmer
productivity, the Java designers thought that this was a worthwhile tradeoff.
What
if you don’t know how many elements you’re going to need in your
array while you’re writing the program? You simply use
new
to create the elements in the array. Here, new
works even though it’s creating an array of primitives (
new
won’t create a non-array primitive):
//: ArrayNew.java
// Creating arrays with new.
import java.util.*;
public class ArrayNew {
static Random rand = new Random();
static int pRand(int mod) {
return Math.abs(rand.nextInt()) % mod;
}
public static void main(String[] args) {
int[] a;
a = new int[pRand(20)];
prt("length of a = " + a.length);
for(int i = 0; i < a.length; i++)
prt("a[" + i + "] = " + a[i]);
}
static void prt(String s) {
System.out.println(s);
}
} ///:~
Since
the size of the array is chosen at random (using the
pRand( )
method defined earlier), it’s clear that array creation is actually
happening at run-time. In addition, you’ll see from the output of this
program that array elements of primitive types are automatically initialized to
”empty” values. (For numerics, this is zero, for
char,
it’s
null,
and for
boolean,
it's
false.) Of
course, the array could also have been defined and initialized in the same
statement:
int[]
a = new int[pRand(20)];
If
you’re dealing with an array of non-primitive objects, you must always use
new.
Here, the handle issue comes up again because what you create is an array of
handles. Consider the wrapper type
Integer,
which is a class and not a primitive:
//: ArrayClassObj.java
// Creating an array of non-primitive objects.
import java.util.*;
public class ArrayClassObj {
static Random rand = new Random();
static int pRand(int mod) {
return Math.abs(rand.nextInt()) % mod;
}
public static void main(String[] args) {
Integer[] a = new Integer[pRand(20)];
prt("length of a = " + a.length);
for(int i = 0; i < a.length; i++) {
a[i] = new Integer(pRand(500));
prt("a[" + i + "] = " + a[i]);
}
}
static void prt(String s) {
System.out.println(s);
}
} ///:~
Here,
even after
new
is called to create the array:
Integer[]
a = new Integer[pRand(20)];
it’s
only an array of handles, and not until the handle itself is initialized by
creating a new
Integer
object is the initialization complete:
a[i]
= new Integer(pRand(500));
If
you forget to create the object, however, you’ll get an exception at
run-time when you try to read the empty array location.
Take
a look at the formation of the
String
object
inside the print statements. You can see that the handle to the
Integer
object is automatically converted to produce a
String
representing
the value inside the object.
It’s
also possible to initialize arrays of objects using the curly-brace-enclosed
list. There are two forms, the first of which is the only one allowed in Java 1.0.
The second (equivalent) form is allowed starting with Java 1.1:
//: ArrayInit.java
// Array initialization
public class ArrayInit {
public static void main(String[] args) {
Integer[] a = {
new Integer(1),
new Integer(2),
new Integer(3),
};
// Java 1.1 only:
Integer[] b = new Integer[] {
new Integer(1),
new Integer(2),
new Integer(3),
};
}
} ///:~
This
is useful at times, but it’s more limited since the size of the array is
determined at compile time. The final comma in the list of initializers is
optional. (This feature makes for easier maintenance of long lists.)
The
second form of array initialization, added in Java 1.1,
provides a convenient syntax to create and call methods that can produce the
same effect as C’s variable
argument lists
(known as “varargs” in C). These included, if you choose, unknown
quantity of arguments as well as unknown type. Since all classes are ultimately
inherited from the common root class
Object,
you can create a method that takes an array of
Object
and call it like this:
//: VarArgs.java
// Using the Java 1.1 array syntax to create
// variable argument lists
class A { int i; }
public class VarArgs {
static void f(Object[] x) {
for(int i = 0; i < x.length; i++)
System.out.println(x[i]);
}
public static void main(String[] args) {
f(new Object[] {
new Integer(47), new VarArgs(),
new Float(3.14), new Double(11.11) });
f(new Object[] {"one", "two", "three" });
f(new Object[] {new A(), new A(), new A()});
}
} ///:~
At
this point, there’s not much you can do with these unknown objects, and
this program uses the automatic
String
conversion to do something useful with each
Object.
In Chapter 11 (run-time type identification or RTTI) you’ll learn how to
discover the exact type of such objects so that you can do something more
interesting with them.
Multidimensional
arrays
Java
allows you to easily create multidimensional
arrays:
//: MultiDimArray.java
// Creating multidimensional arrays.
import java.util.*;
public class MultiDimArray {
static Random rand = new Random();
static int pRand(int mod) {
return Math.abs(rand.nextInt()) % mod;
}
public static void main(String[] args) {
int[][] a1 = {
{ 1, 2, 3, },
{ 4, 5, 6, },
};
for(int i = 0; i < a1.length; i++)
for(int j = 0; j < a1[i].length; j++)
prt("a1[" + i + "][" + j +
"] = " + a1[i][j]);
// 3-D array with fixed length:
int[][][] a2 = new int[2][2][4];
for(int i = 0; i < a2.length; i++)
for(int j = 0; j < a2[i].length; j++)
for(int k = 0; k < a2[i][j].length;
k++)
prt("a2[" + i + "][" +
j + "][" + k +
"] = " + a2[i][j][k]);
// 3-D array with varied-length vectors:
int[][][] a3 = new int[pRand(7)][][];
for(int i = 0; i < a3.length; i++) {
a3[i] = new int[pRand(5)][];
for(int j = 0; j < a3[i].length; j++)
a3[i][j] = new int[pRand(5)];
}
for(int i = 0; i < a3.length; i++)
for(int j = 0; j < a3[i].length; j++)
for(int k = 0; k < a3[i][j].length;
k++)
prt("a3[" + i + "][" +
j + "][" + k +
"] = " + a3[i][j][k]);
// Array of non-primitive objects:
Integer[][] a4 = {
{ new Integer(1), new Integer(2)},
{ new Integer(3), new Integer(4)},
{ new Integer(5), new Integer(6)},
};
for(int i = 0; i < a4.length; i++)
for(int j = 0; j < a4[i].length; j++)
prt("a4[" + i + "][" + j +
"] = " + a4[i][j]);
Integer[][] a5;
a5 = new Integer[3][];
for(int i = 0; i < a5.length; i++) {
a5[i] = new Integer[3];
for(int j = 0; j < a5[i].length; j++)
a5[i][j] = new Integer(i*j);
}
for(int i = 0; i < a5.length; i++)
for(int j = 0; j < a5[i].length; j++)
prt("a5[" + i + "][" + j +
"] = " + a5[i][j]);
}
static void prt(String s) {
System.out.println(s);
}
} ///:~
The
code used for printing uses
length
so that it doesn’t depend on fixed array sizes.
The
first example shows a multidimensional array of primitives. You delimit each
vector in the array with curly braces:
int[][] a1 = {
{ 1, 2, 3, },
{ 4, 5, 6, },
};
Each
set of square brackets moves you into the next level of the array.
The
second example shows a three-dimensional array allocated with
new.
Here, the whole array is allocated at once:
int[][][]
a2 = new int[2][2][4];
But
the third example shows that each vector in the arrays that make up the matrix
can be of any length:
int[][][] a3 = new int[pRand(7)][][];
for(int i = 0; i < a3.length; i++) {
a3[i] = new int[pRand(5)][];
for(int j = 0; j < a3[i].length; j++)
a3[i][j] = new int[pRand(5)];
}
The
first
new
creates an array with a random-length first element and the rest undetermined.
The second
new
inside the for loop fills out the elements but leaves the third index
undetermined until you hit the third
new. You
will see from the output that array values are automatically initialized to
zero if you don’t give them an explicit initialization value.
You
can deal with arrays of non-primitive objects in a similar fashion, which is
shown in the fourth example, demonstrating the ability to collect many
new
expressions with curly braces:
Integer[][] a4 = {
{ new Integer(1), new Integer(2)},
{ new Integer(3), new Integer(4)},
{ new Integer(5), new Integer(6)},
};
The
fifth example shows how an array of non-primitive objects can be built up piece
by piece:
Integer[][] a5;
a5 = new Integer[3][];
for(int i = 0; i < a5.length; i++) {
a5[i] = new Integer[3];
for(int j = 0; j < a5[i].length; j++)
a5[i][j] = new Integer(i*j);
}
The
i*j
is just to put an interesting value into the
Integer.
[22]
See
Thinking
in C++
for a complete description of aggregate initialization.