Wednesday, February 2, 2011

Chapter 18: Array Declaration, Construction, and Initialization

Arrays are objects in Java that store multiple variables of the same type. Arrays can hold either primitives or object references, but the array itself will always be an object on the heap, even if the array is declared to hold primitive elements. In other words, there is no such thing as a primitive array, but you can make an array of primitives. We are going to learn the following three things in this Chapter:

• How to make an array reference variable (declare)
• How to make an array object (construct)
• How to populate the array with elements (initialize)

There are several different ways to do each of those, and you need to know about all of them for the exam.

Tip:
Arrays are efficient, but most of the time you’ll want to use one of the Collection types from java.util (including HashMap, ArrayList, TreeSet). Collection classes offer more flexible ways to access an object (for insertion, deletion, and so on) and unlike arrays, can expand or contract dynamically as you add or remove elements (they’re really managed arrays, since they use arrays behind the scenes). There’s a Collection type for a wide range of needs. We will be covering Collections in greater detail in one of the forthcoming chapters.

Declaring an Array

Arrays are declared by stating the type of element the array will hold, which can be an object or a primitive, followed by square brackets to the left or right of the identifier.
Declaring an array of primitives:

int[] Values; // brackets before name (recommended)
int Values []; // brackets after name (legal but less readable)
// spaces between the name and [] legal, but bad

Declaring an array of object references:
Ferrari[] Ferraris; // Recommended
Ferrari Ferraris[]; // Legal but less readable

When declaring an array reference, you should always put the array brackets immediately after the declared type, rather than after the identifier (variable name). That way, anyone reading the code can easily tell that, for example, Values is a reference to an int array object, and not an int primitive.

We can also declare multidimensional arrays, which are in fact arrays of arrays. This can be done in the following manner:
String[][][] occupantName; // recommended
String[] ManagerName []; // Real bad coding practice, but legal

The first example is a three-dimensional array (an array of arrays of arrays) and the second is a two-dimensional array. Notice in the second example we have one square bracket before the variable name and one after. This is perfectly legal to the compiler, but if you write code like this, anyone who is going to manage your code in future is sure going to curse you badly.
It is never legal to include the size of the array in your declaration. Yes, before you ask me, let me admit that you can do that in some other languages, which is exactly why you might see a question or two that include code similar to the following:

int[5] races;

The preceding code won’t make it past the Java compiler. Remember, the JVM doesn’t allocate space until you actually instantiate the array object. That’s when size matters and not when you declare the array variable.

Constructing an Array

Constructing an array means creating the array object on the heap i.e., doing a new on the array type. To create an array object, Java must know how much space to allocate on the heap, so you must specify the size of the array at creation time. The size of the array is the number of elements the array will hold.

Constructing One-Dimensional Arrays

The most straightforward way to construct an array is to use the Keyword new followed by the array type, with a bracket specifying how many elements of that type the array will hold. The following is an example of constructing an array of type int:
int[] myArray; // Declares the array of ints
myArray = new int[4]; // constructs an array and assigns it
// to the myArray variable

The preceding code puts one new object on the heap—an array object holding four elements—with each element containing an int with a default value of 0. Think of this code as saying to the compiler, “Create an array object that will hold four int values, and assign it to the reference variable named myArray. Also, go ahead and set each int element to zero. Thanks.”
You can also declare and construct an array in one statement as follows:

int[] myArray = new int[4];

This single statement produces the same result as the two previous statements. Arrays of object types can be constructed in the same way:

Ferrari[] Ferraris = new Ferrari[5];

Remember that—despite how the code appears—the Ferrari constructor is not being invoked. We’re not creating a Ferrari instance, but rather a single Ferrari array object. After the preceding statement, there are still no actual Ferrari objects!

Exam Tip: Think carefully about how many objects are on the heap after a code statement or block executes. The exam will expect you to know, for example, that the preceding code produces just one object (the array assigned to the reference variable named Ferraris). The single object referenced by Ferraris holds five Ferrari reference variables, but no Ferrari objects have been created or assigned to those references.

Remember, arrays must always be given a size at the time they are constructed. The JVM needs the size to allocate the appropriate space on the heap for the new array object. It is never legal, for example, to do the following:

int[] carList = new int[]; // Will not compile; needs a size

Exam Tip: You may see the words “construct”, “create”, and “instantiate” used interchangeably. They all mean, “An object is built on the heap.” This also implies that the object’s constructor runs, as a result of the construct/create/instantiate code. You can say with certainty, for example, that any code that uses the Keyword new, will (if it runs successfully) cause the class constructor and all superclass constructors to run.

Constructing Multidimensional Arrays

Multidimensional arrays, remember, are simply arrays of arrays. So a two-dimensional array of type int is really an object of type int array (int []), with each element in that array holding a reference to another int array. The second dimension holds the actual int primitives. The following code declares and constructs a two-dimensional array of type int:

int[][] myArray = new int[3][];

Notice that only the first brackets are given a size. That’s acceptable in Java, since the JVM needs to know only the size of the object assigned to the variable myArray.

Initializing an Array

Initializing an array means putting things into it. The “things” in the array are the array’s elements, and they’re either primitive values, or objects referred to by the reference variables in the array. If you have an array of objects (as opposed to primitives), the array doesn’t actually hold the objects, just as any other nonprimitive variable never actually holds the object, but instead holds a reference to the object. But we talk about arrays as, for example, “an array of five strings,” even though what we really mean is, “an array of five references to String objects.” Then the big question becomes whether or not those references are actually referring to real String objects, or are simply null. Remember, a reference that has not had an object assigned to it is a null reference. And if you try to actually use that null reference by, say, applying the dot operator to invoke a method on it, you’ll get the most famous NullPointerException.

The individual elements in the array can be accessed with an index number. The index number always begins with zero, so for an array of ten objects the index numbers will run from 0 through 9. Suppose we create an array of three Ferraris as follows:
Ferrari [] raceCars = new Ferrari[3];

We have one array object on the heap, with three null references of type Ferrari, but we don’t have any Ferrari objects. The next step is to create some Ferrari objects and assign them to index positions in the array referenced by raceCars:
raceCars[0] = new Ferrari();
raceCars[1] = new Ferrari();
raceCars[2] = new Ferrari();

This code puts three new Ferrari objects on the heap and assigns them to the three index positions (elements) in the raceCars array.

Exam Tip: Look for code that tries to access an out-of-range array index. For example, if an array has three elements, trying to access the [3] element will raise an ArrayIndexOutOfBoundsException, because in an array of three elements, the legal index values are 0, 1, and 2. You also might see an attempt to use a negative number as an array index. The following are examples of legal and illegal array access attempts. Be sure to recognize that these cause runtime exceptions and not compiler errors!
Nearly all of the exam questions list both runtime exception and compiler error as possible answers.
int[] x = new int[5];
x[4] = 2; // OK, the last element is at index 4
x[5] = 3; // Runtime exception. There is no element at index 5!

int[] z = new int[2];
int y = -3;
z[y] = 4; // Runtime exception. y is a negative number

These can be hard to spot in a complex loop, but that’s where you’re most likely to see array index problems in exam questions.

A two-dimensional array (an array of arrays) can be initialized as follows:

int[][] races = new int[3][];
// Declare and create an array holding three references
// to int arrays

races[0] = new int[4];
// the first element in the races array is an int array
// of four int elements

races[1] = new int[6];
// the second element in the races array is an int array
// of six int elements

races[2] = new int[1];
// the third element in the races array is an int array
// of one int element

Initializing Elements in a Loop

Array objects have a single public variable, length that gives you the number of elements in the array. The last index value, then, is always one less than the length. For example, if the length of an array is 4, the index values are from 0 through 3. Often, you’ll see array elements initialized in a loop as follows:

Ferrari[] myFerraris = new Ferrari[6];
for(int x = 0; x < myFerraris.length; x++) {

myFerraris[x] = new Ferrari();

}


The above code creates a Ferrari array reference and then assigns a new Ferrari object into each position in the array using the length property of the array.


The length variable tells us how many elements the array holds, but it does not tell us whether those elements have been initialized.


Declaring, Constructing, and Initializing on One Line

You can use two different array-specific syntax shortcuts to both initialize and instantiate an array in a single statement. The first is used to declare, create, and initialize in one statement as follows:
int[] dots = {6,7,8};

The preceding code does four things:
• Declares an int array reference variable named dots.
• Creates an int array with a length of three (three elements).
• Populates the array’s elements with the values 6, 7, and 8.
• Assigns the new array object to the reference variable dots.

The size (length of the array) is determined by the number of comma-separated items between the curly braces. The code is functionally equivalent to the following longer code:
int[] dots;
dots = new int[3];
dots[0] = 6;
dots[1] = 7;
dots[2] = 8;

You may ask me now, “why use the longer way then?” The answer is simple my friend. Not always do we know how many elements we want to put in an array. For an example perspective I know how many int values I want in my array but during coding for an application, the array size might vary so, using the longer way is much more beneficial.

With object references rather than primitives, it works exactly the same way:

Ferrari[] myFerraris = {new Ferrari(“One”), new Ferrari("Two"), new Ferrari("Three")};

The preceding code creates one Ferrari array, referenced by the variable myFerraris, with a length of three elements. It also creates three new Ferrari objects (One, Two and Three), and adds them to the Ferrari reference variable elements in the myFerraris array.
You can also use the shortcut syntax with multidimensional arrays, as follows:
int[][] races = {{5,2,4,7}, {9,2}, {3,4}};

The preceding code creates a total of four objects on the heap. First, an array of int arrays is constructed (the object that will be assigned to the races reference variable). The races array has a length of three, derived from the number of items (comma-separated) between the outer curly braces. Each of the three elements in the races array is a reference variable to an int array, so the three int arrays are constructed and assigned to the three elements in the races array.

The size of each of the three int arrays is derived from the number of items within the corresponding inner curly braces. For example, the first array has a length of four, the second array has a length of two, and the third array has a length of two. So far, we have four objects: one array of int arrays (each element is a reference to an int array), and three int arrays (each element in the three int arrays is an int value). Finally, the three int arrays are initialized with the actual int values within the inner curly braces. Thus, the first int array contains the values 5, 2, 4, and 7. The following code shows the values of some of the elements in this two-dimensional array:

races[0] // an array of four ints
races[1] // an array of 2 ints
races[2] // an array of 2 ints
races[0][1] // the int value 2
races[2][1] // the int value 4

Constructing and Initializing an Anonymous Array

The second shortcut is called “anonymous array creation” and can be used to construct and initialize an array, and then assign the array to a previously declared array reference variable:
int[] myArray;
myArray = new int[] {4,7,2};

The preceding code creates a new int array with three elements, initializes the three elements with the values 4, 7, and 2, and then assigns the new array to the previously declared int array reference variable myArray. We call this anonymous array creation because with this syntax you don’t even need to assign the new array to anything.

Exam Tip: Remember that you do not specify a size when using anonymous array creation syntax. The size is derived from the number of items (comma-separated) between the curly braces. Pay very close attention to the array syntax used in exam questions (and there will be a lot of them). You might see syntax such as
new Object[3] {null, new Object(), new Object()};
// not legal;size must not be specified


Legal Array Element Assignments

What can you put in a particular array? For the exam, you need to know that arrays can have only one declared type (int [], Ferrari[], String [], and so on), but that doesn’t necessarily mean that only objects or primitives of the declared type can be assigned to the array elements. And what about the array reference itself? What kind of array object can be assigned to a particular array reference? For the exam, you’ll need to know the answers to all of these questions. And, that is exactly what we are going to see in the next few paragraphs.

Arrays of Primitives

Primitive arrays can accept any value that can be promoted implicitly to the declared type of the array. For example, an int array can hold any value that can fit into a 32-bit int variable. Thus, the following code is legal:

int[] list = new int[5];
byte b = 4;
char c = 'c';
short s = 7;
list[0] = b;
list[1] = c;
list[2] = s;

Arrays of Object References

If the declared array type is a class, you can put objects of any subclass of the declared type into the array. For example, if Subaru is a subclass of Car, you can put both Subaru objects and Car objects into an array of type Car as follows:

class Car {}
class Subaru extends Car {}
class Ferrari extends Car {}
...
Car [] myCars = {new Subaru(), new Car(), new Ferrari()};

It helps to remember that the elements in a Car array are nothing more than Car reference variables. So anything that can be assigned to a Car reference variable can be legally assigned to a Car array element.

If the array is declared as an interface type, the array elements can refer to any instance of any class that implements the declared interface. The following code demonstrates the use of an interface as an array type:
interface Retractable {
void retractRoof();
}

class Ferrari extends Car implements Retractable {
public void retractRoof() {
// implement cool Retractable method in a Ferrari-specific way
}
}

class Subaru extends StationWagon implements Retractable {
public void retractRoof() {
// implement cool Retractable method in a RacingShoe-specific way
}
}
class Volkswagen { }
class TestRetractableCars {
public static void main (String [] args) {
Retractable[] RetractableThings = new Retractable [3];
RetractableThings[0] = new Ferrari();
// OK because, Ferrari implements Retractable
RetractableThings[1] = new Subaru();
// OK because, Subaru implements Retractable
RetractableThings[2] = new Volkswagen();
// Not OK because, Volkswagen does not implement Retractable
}
}

The bottom line is this: any object that passes the “IS-A” test for the declared array type can be assigned to an element of that array.

Array Reference Assignments for One-Dimensional Arrays

For the exam, you need to recognize legal and illegal assignments for array reference variables. We’re not talking about references in the array (in other words, array elements), but rather references to the array object. For example, if you declare an int array, the reference variable you declared can be reassigned to any int array (of any size), but cannot be reassigned to anything that is not an int array, including an int value. Remember, all arrays are objects, so an int array reference cannot refer to an int primitive. The following code demonstrates legal and illegal assignments for primitive arrays:

int[] cats;
int[] dogs = new int[4];
char[] rats = new char[5];
cats = dogs; // OK, dogs refers to an int array
cats = rats; // NOT OK, rats refers to a char array

It’s tempting to assume that because a variable of type byte, short, or char can be explicitly promoted and assigned to an int, an array of any of those types could be assigned to an int array. You cannot do that in Java.

Arrays that hold object references, as opposed to primitives, aren’t as restrictive. Just as you can put a Ferrari object in a Car array (because Ferrari extends Car), you can assign an array of type Ferrari to a Car array reference variable as follows:

Car[] cars;

Ferrari[] myFerraris = new Ferrari[5];
cars = myFerraris; // OK because Ferrari is a type of Car
Truck[] trucks = new Truck [99];
cars = trucks; // NOT OK, Truck is not a type of Car

Apply the IS-A test to help sort the legal from the illegal. Ferrari IS-A Car, so a Ferrari array can be assigned to a Car array. Truck IS-A Car is not true; Truck does not extend Car.
Exam Tip: You cannot reverse the legal assignments. A Car array cannot be assigned to a Ferrari array. A Car is not necessarily a Ferrari, so if you’ve declared a Ferrari array, it might blow up if you assigned a Car array to the Ferrari reference variable. Think about it: a Car array could hold a reference to a Ferrari, so someone who thinks they have an array of Ferraris could suddenly find themselves with a Ferrari. Remember that the IS-A test can be checked in code using the instanceof operator.

The rules for array assignment apply to interfaces as well as classes. An array declared as an interface type can reference an array of any type that implements the interface. Remember, any object from a class implementing a particular interface will pass the IS-A (instanceof) test for that interface. For example, if Car implements Drivable, the following is legal:

Drivable[] drivableThings;
Car[] CarThings = new Car[3];
drivableThings = CarThings;
// OK, Car implements Drivable, so Car IS-A Drivable

Array Reference Assignments for Multidimensional Arrays

When you assign an array to a previously declared array reference, the array you’re assigning must be the same dimension as the reference you’re assigning it to. For example, a two-dimensional array of int arrays cannot be assigned to a regular int array reference, as follows:
int[] cats;
int[][] dogs = new int[3][];
cats = dogs; // NOT OK, dogs is a
// two-d array of int arrays
int[] rats = new int[6];
cats = rats; // OK, rats is an int array

Pay particular attention to array assignments using different dimensions. You might, for example, be asked if it’s legal to assign an int array to the first element in an array of int arrays, as follows:

int[][] books = new int[3][];
int[] numbers = new int[6];
int aNumber = 7;
books[0] = aNumber; // NO, expecting an int array not an int
books[0] = numbers; // OK, numbers is an int array


Initialization Blocks

We’ve talked about two places in a class where you can put code that performs operations: methods and constructors. Initialization blocks are the third place in a Java program where operations can be performed. Initialization blocks run when the class is first loaded (a static initialization block) or when an instance is created (an instance initialization block). Let’s look at an example:

class TestInitBlocks {
static int x;
int y;

static { x = 7 ; } // static init block
{ y = 8; } // instance init block
}

As you can see, the syntax for Initialization blocks is pretty straight-forward. They don’t have names, they can’t take arguments, and they don’t return anything. A static initialization block runs once, when the class is first loaded. An instance initialization block runs once every time a new instance is created. Remember when we talked about the order in which constructor code executed? Instance init block code runs right after the call to super() in a constructor, in other words, after all super-constructors have run.

You can have many Initialization blocks in a class. It is important to note that unlike methods or constructors, the order in which Initialization blocks appear in a class matters. When it’s time for Initialization blocks to run, if a class has more than one, they will run in the order in which they appear in the class file.

Finally, if you make a mistake in your static init block, the JVM can throw an ExceptionInInitializationError. Let’s look at an example,
class InitErrorExample {
static int [] x = new int[4];
static { x[4] = 5; } // bad array index!
public static void main(String [] args) { }
}

which produces something like:

Exception in class "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.ArrayIndexOutOfBoundsException: 4
at InitErrorExample.(InitErrorExample.java:3)

Exam Tip: By convention, init blocks usually appear near the top of the class file, somewhere around the constructors. However don’t be surprised if you find an init block tucked in between a couple of methods. Though it is perfectly legal to code your init blocks anywhere in the class you want, you may end up with compilation errors if you happen to take that risk

Previous Chapter: Chapter 17 - Passing Variables to Methods

Next Chapter: Chapter 19 - Wrapper Classes and Boxing

1 comment:

  1. Hi,
    Thanks for this Nice article just to add while discussing about HashMap its worth mentioning following questions which frequently asked in Java interviews now days like How HashMap works in Java or How get() method of HashMap works in JAVA very often. on concept point of view these questions are great and expose the candidate if doesn't know deep details.

    Javin
    FIX Protocol tutorial

    ReplyDelete

© 2013 by www.inheritingjava.blogspot.com. All rights reserved. No part of this blog or its contents may be reproduced or transmitted in any form or by any means, electronic, mechanical, photocopying, recording, or otherwise, without prior written permission of the Author.

ShareThis

Google+ Followers

Followers