Array initialization
Initializing arrays in C is error-prone and tedious. C++ uses aggregate initialization to make it much safer.[25] Java has no aggregates like C++ does, 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 that are 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:
int[] a1;
You can also put the square brackets after the identifier to produce exactly the same meaning:
int a1[];
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 doesnt allow you to tell it how big the array is. This brings us back to that issue of references. All that you have at this point is a reference to an array, and theres 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 reference without an array?
int[] a2;
Well, its possible to assign one array to another in Java, so you can say:
a2 = a1;
What youre really doing is copying a reference, as demonstrated here:
//: c04:Arrays.java
// Arrays of primitives.
import com.bruceeckel.simpletest.*;
public class Arrays {
static Test monitor = new Test();
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++)
System.out.println(
"a1[" + i + "] = " + a1[i]);
monitor.expect(new String[] {
"a1[0] = 2",
"a1[1] = 3",
"a1[2] = 4",
"a1[3] = 5",
"a1[4] = 6"
});
}
} ///:~
You can see that a1 is given an initialization value but a2 is not; a2 is assigned laterin this case, to another array.
Theres something new here: All arrays have an intrinsic member (whether theyre arrays of objects or arrays of primitives) that you can querybut not changeto 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 theres 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 trade-off.
What if you dont know how many elements youre going to need in your array while youre writing the program? You simply use new to create the elements in the array. Here, new works even though its creating an array of primitives (new wont create a nonarray primitive):
//: c04:ArrayNew.java
// Creating arrays with new.
import com.bruceeckel.simpletest.*;
import java.util.*;
public class ArrayNew {
static Test monitor = new Test();
static Random rand = new Random();
public static void main(String[] args) {
int[] a;
a = new int[rand.nextInt(20)];
System.out.println("length of a = " + a.length);
for(int i = 0; i < a.length; i++)
System.out.println("a[" + i + "] = " + a[i]);
monitor.expect(new Object[] {
"%% length of a = \\d+",
new TestExpression("%% a\\[\\d+\\] = 0", a.length)
});
}
} ///:~
The expect( ) statement contains something new in this example: the TestExpression class. A TestExpression object takes an expression, either an ordinary string or a regular expression as shown here, and a second integer argument that indicates that the preceding expression will be repeated that many times. TestExpression not only prevents needless duplication in the code, but in this case, it allows the number of repetitions to be determined at run time.
The size of the array is chosen at random by using the Random.nextInt( ) method, which produces a value from zero to that of its argument. Because of the randomness, its clear that array creation is actually happening at run time. In addition, the output of this program shows that array elements of primitive types are automatically initialized to empty values. (For numerics and char, this is zero, and for boolean, its false.)
Of course, the array could also have been defined and initialized in the same statement:
int[] a = new int[rand.nextInt(20)];
This is the preferred way to do it, if you can.
If youre dealing with an array of nonprimitive objects, you must always use new. Here, the reference issue comes up again, because what you create is an array of references. Consider the wrapper type Integer, which is a class and not a primitive:
//: c04:ArrayClassObj.java
// Creating an array of nonprimitive objects.
import com.bruceeckel.simpletest.*;
import java.util.*;
public class ArrayClassObj {
static Test monitor = new Test();
static Random rand = new Random();
public static void main(String[] args) {
Integer[] a = new Integer[rand.nextInt(20)];
System.out.println("length of a = " + a.length);
for(int i = 0; i < a.length; i++) {
a[i] = new Integer(rand.nextInt(500));
System.out.println("a[" + i + "] = " + a[i]);
}
monitor.expect(new Object[] {
"%% length of a = \\d+",
new TestExpression("%% a\\[\\d+\\] = \\d+", a.length)
});
}
} ///:~
Here, even after new is called to create the array:
Integer[] a = new Integer[rand.nextInt(20)];
its only an array of references, and not until the reference itself is initialized by creating a new Integer object is the initialization complete:
a[i] = new Integer(rand.nextInt(500));
If you forget to create the object, however, youll get an exception at run time when you try to use the empty array location.
Take a look at the formation of the String object inside the print statements. You can see that the reference to the Integer object is automatically converted to produce a String representing the value inside the object.
Its also possible to initialize arrays of objects by using the curly-brace-enclosed list. There are two forms:
//: c04:ArrayInit.java
// Array initialization.
public class ArrayInit {
public static void main(String[] args) {
Integer[] a = {
new Integer(1),
new Integer(2),
new Integer(3),
};
Integer[] b = new Integer[] {
new Integer(1),
new Integer(2),
new Integer(3),
};
}
} ///:~
The first form is useful at times, but its 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 provides a convenient syntax to create and call methods that can produce the same effect as Cs variable argument lists (known as varargs in C). These can include unknown quantities of arguments as well as unknown types. Since all classes are ultimately inherited from the common root class Object (a subject you will learn more about as this book progresses), you can create a method that takes an array of Object and call it like this:
//: c04:VarArgs.java
// Using array syntax to create variable argument lists.
import com.bruceeckel.simpletest.*;
class A { int i; }
public class VarArgs {
static Test monitor = new Test();
static void print(Object[] x) {
for(int i = 0; i < x.length; i++)
System.out.println(x[i]);
}
public static void main(String[] args) {
print(new Object[] {
new Integer(47), new VarArgs(),
new Float(3.14), new Double(11.11)
});
print(new Object[] {"one", "two", "three" });
print(new Object[] {new A(), new A(), new A()});
monitor.expect(new Object[] {
"47",
"%% VarArgs@\\p{XDigit}+",
"3.14",
"11.11",
"one",
"two",
"three",
new TestExpression("%% A@\\p{XDigit}+", 3)
});
}
} ///:~
You can see that print( ) takes an array of Object, then steps through the array and prints each one. The standard Java library classes produce sensible output, but the objects of the classes created hereA and VarArgsprint the class name, followed by an @ sign, and yet another regular expression construct, \p{XDigit}, which indicates a hexadecimal digit. The trailing + means there will be one or more hexadecimal digits. Thus, the default behavior (if you dont define a toString( ) method for your class, which will be described later in the book) is to print the class name and the address of the object.