Chapter 4

Using Java Arrays

The array is a universal data structure among programming languages. With an array, you can collect many instances of one type of data in a single data structure. Once you learn the concept of an array in any language, you will know the basics for applying it in other languages.

Arrays are great for learning and have many practical uses. In Java, however, the implementation is bit funny. Arrays don’t take full advantage of the object model that Java is known for. I hear some people say they are “part object, part primitive,” but that’s not quite right. To understand them very well, you’ll want to pay close attention to how arrays are made and used.

In this chapter, we’ll cover the following topics:

  • Understanding Java arrays
  • Using a one-dimensional array
  • Using a multi-dimensional array
  • Using an ArrayList object

Understanding Java Arrays

We have so far distinguished between primitive and object types in order to understand why and how we should treat them differently. Now as we turn our attention to arrays, we’re going to get a little of both. Arrays, in some senses, blur the line between object and primitive.

An array is unquestionably an object type. It inherits from the Object class and, like any other subclass, supports all the methods it receives from its parent class. But an array by itself isn’t an actual type. You can’t declare an array like this:

[] bugs;

That is, an array has an Object class interface but no implementation of its own. It seems, in a way, closer to the kinds of classes we’ve been writing as test code: arrays have subclasses, yes, but nothing that modifies or extends the parent class as a fully formed subclasses would. So an array has what type? It depends, actually, on what you intend to put in it. An array by itself is a single structure that will hold some declared number of elements. Once you declare the array’s type, you also declare the type of element it will store.

certobjective.eps

To declare an array properly, you can use either a primitive or a Java class for the type:

String [] names;
double [] measurements;

Brackets, as shown in Chapter 3, “Using Java Operators and Conditional Logic,” signify the array itself. In a declaration, the brackets establish an aspect of the type. For the two lines above, you might hear someone say “a String array of names and a double array of measurements,” or you might hear them say “a names array of type String and a measurements array of type double.” Either form gets the point across.

In an expression, the brackets behave as an access operator that contains the operand. The operand may be any expression that resolves to an integral type, except long, whose value is nonnegative and less than the declared size of the array.

The type you declare for an array defines the elements it may store. You must be careful, however, to understand how you refer to the array itself, which is a subclass of Object, and how you refer to an array component, which is a value of the declared component type.

public final class ArrayType {
public static void main(String args[]) {
      String [] bugs = { "cricket", "beetle", "katydid" };
      String [] alias = bugs;
      System.out.println(bugs.equals(alias));
      System.out.println(bugs.toString());
      System.out.println(bugs[0].toString());
   }
}

$ javac ArrayType
$ java ArrayType
true
[Ljava.lang.String;@437dcb
cricket

I will cover all the ways you create arrays in the next section; ignore that aspect of our example for now. Do notice that the variable bugs can access the equals() method. Calling bugs.toString() returns, as all Object instances will, a report of the referent’s type and memory location. The [L; notation that wraps the package-qualified String class shows it’s an array.

Arrays also have a built-in property called length, which you can access just like a field. It’s just there; the Java Language Specification (JLS) defines it, so any compiler that complies with the JLS must support it somehow. The length property stores the size of the array, or the number of elements it has room for. Arrays are fixed in size upon creation. The length property is therefore always nonnegative, immutable, and read-only.

Every array has length. The elements are indexed by number. You can access any element by its numeric position, commonly called the index: The first element occupies index 0; the last one occupies the index value length-1. Figure 4-1 shows the bugs array elements by their position.

Figure 4-1: Elements of the bugs[] array

c04f001.eps

You can infer from Figure 4-1 that array elements do not store referents, but rather locations to referents, just like any other object reference. Thus the array itself is a referent that contains references to other referents! An array full of null values is the same size in memory as an array full of references. It is an object that is separate from the objects it collects.

The Java array is one example of a data structure that behaves like a list. All lists order their elements; it is the index value that fulfills this role in a Java array. You can think of this number as a unique identifier within the array.

Conceptually, a list identifies each element by its position, not by its value or its memory location. Since nothing about the element itself influences how the array identifies and orders it, you can have as many duplicate values in an array as you’d like. From the array’s perspective, the index itself establishes each element’s meaning.

Let’s say you wanted to track four old cars that have fallen out of fashion but you like them anyway. You want to keep them in a private underground parking garage you’re building. All you need to track them is a simple description of each car, including year, make and model, and the parking spot number. Even if two cars are more or less the same, they’ll have different-numbered spots and that’s how you’ll tell them apart. This spot number could double as an array index, as shown in Figure 4-2.

Figure 4-2: A usedCar array with four String elements

c04f002.eps

The cars at array positions 2 and 3 are identical, at least so far as our description for them goes. The element value for each is also the same. They remain distinct solely by virtue of their index, or parking spot number.

One last concept, and then we’ll dive into the nuts and bolts of making arrays. Since one array is just a list of references to other objects, an array can store other arrays as its elements. We refer to this kind of array as having two dimensions. I will discuss declaring, populating, and accessing multi-dimensional arrays after I have covered the simpler case of one dimension.

Using a One-Dimensional Array

We’ll need a few examples to employ all the useful aspects an array has to offer, so the following sections cover four topics: declaring arrays, populating them, instantiating them, and using them. With one-dimensional arrays, these operations are straightforward to express and apply. I’ll also note some rather subtle effects that are made possible by the array syntax defined by Java’s designers.

certobjective.eps

Declaring an Array

You can declare an array of any type by adding brackets to an existing declaration. You can declare the separators after the type or after the variable name. Either of the following forms is legal:

int [] numbersGuessed;
int numbersGuessed [];

Omitting white space seems like a small detail, but style guidelines are a big part of defining communication among Java programmers.

Because the square brackets are separators that can’t be used in a type or variable name, you can also omit the white space; the compiler won’t complain:

int[] numbersGuessed;
int numbersGuessed[];

All these forms have the same effect. One (arguable) benefit of allowing this variety is that it’s easier to declare multiple array variables at once, or declare variables together that may or may not be arrays, as follows:

String [] bands, players, songs;
String bands[], players[], song;

In the first example, I declare all three variables as String arrays. In the second example, I declare bands and players as String arrays, but song is a String only. Saving a line or two this way at the expense of more but simple declarations, I would argue, seems like a negligible benefit. Whether you like to associate brackets with the element type or the variable name is a matter of preference. It’s best to choose one approach and use it consistently. Or, if you’re working with code that’s given to you, adopt the conventions taken in that code.

Inside a method body, you can assign an array to null if you’re not going to initialize it right away:

void someMethod() {
double [] plotPoints = null;
// more code
}

When declaring parameters and return types for a method, it’s as simple as adding brackets where you’d expect. In the return type, the separators follow the type declaration. For a method parameter, the separators may follow either type or the parameter name:

public float[] smoothPlotPoints(float rawData[]) { . . . };

Instantiating an Array

An array is a combination of types, as you’ve learned. There’s the array itself, which is a direct subclass of Object, and the type that defines its elements. Arrays also have a size (length) that is fixed at creation time. When we construct an array, therefore, we instantiate both the element type and array’s size, as in this example:

double [] plotPoints = new double[10];

This statement will create an array with a length of 10 and an index that goes from zero to nine. The elements themselves are initialized to the type-appropriate default value; in the case of a double it’s 0.0. If you declared the array to contain object references, the default values would be null.

You can, by the way, construct an array with zero elements. In fact, we’ve done so several times already. Each time we run a Java program, we invoke a main() method. If we don’t pass any arguments into the program, the JVM must construct an args array with nothing in it. You can test this yourself:

public final class NoArgs {
public static void main(String args[]) {
if (args.length == 0)
         System.out.println("No arguments were passed in.");
   }
}

$ javac NoArgs.java
$ java NoArgs
No arguments were passed in.

You can create a zero-length array yourself by using zero in the instantiation expression, but it’s not useful for much.

The Java Virtual Machine creates an args array of zero size if no arguments are passed into a program from the command line. Many programmers prefer this to receiving a null object reference.

The wisdom of this approach is subtle. To fully test an object that is designed to contain multiple components, we normally have to consider three cases, not two:

  • The array has no referent (it is null).
  • The array has no elements.
  • The array has one or more elements.

By eliminating the first case whenever possible, we reduce the problem to an either-or case, making our programming life just a bit less tedious.

Populating an Array with a Loop

All other things being equal, populating a 10-component array takes just as much work as initializing 10 individual variables would. Writing the code is less work, however, if we can use loops to repeat the code statements. We don’t discuss loops until Chapter 5, “Using Loops in Java Code.” However, it’s about time we wrote something that includes everything we’ve discussed so far. And why not throw in a sneak preview of the next chapter, too?

public final class LoopArray {
public static void main(String args[]) {
int [] numbers = new int[10];
int counter = 0;
while (counter < numbers.length) {
numbers[counter] = counter;
         System.out.println("number[" + counter + "]: " + counter);
counter++;
      }
   }
}

Use the + concatenation operator to combine literal text with program values. Remember to allow for spaces in the output.

In this example, we declare and instantiate an array of 10 elements and declare a counter variable. The while statement tests whether counter is less than 10. If it is, we assign the value of counter to the corresponding array position, print it, and increment counter by one. At the end of the code block, control returns silently to the test expression that immediately follows while.

Eventually, counter will equal numbers.length. Once it does, the expression will evaluate to false and control will silently transfer to the end of the block. Here’s the output for this program:

number[0]: 0
number[1]: 1
number[2]: 2
number[3]: 3
number[4]: 4
number[5]: 5
number[6]: 6
number[7]: 7
number[8]: 8
number[9]: 9

Populating an Array without a Loop

If we have values of some nonincrementing variety to put in an array such that using a loop isn’t practical, we can take a different approach. We can instead populate the array at the same time we declare it. It won’t matter if the arrays are class members or method variables. The way it works for each is a little different, so we’ll cover them one at a time, class members first.

Recall that class members we declare but do not assign values will be initialized to a type-appropriate default by the compiler. This behavior seems like a programmer convenience, but really it complements the compiler’s type-checking job. For types other than an array, you can assign your own value. How you do the same for an array relies on a use for code body separators you haven’t covered yet:

public final class FishTank {
    String[] creatures = { "goldfish", "oscar", "guppy", "minnow" };

}

You can add your own main() method to this class to test whether the text description of it is accurate.

This code declares and populates the creatures array with the four elements shown, implicitly setting its length. The braces, which we normally think of as code separators, contain the element list. It’s a little bit of magic that was included in the earliest versions of the Java Language Specification so that arrays had the same options for initialization as other types do.

Why is it magic? Code block separators aren’t operators; they don’t accept operands. Even though the FishTank class compiles, the rules you learned in Chapter 3 tell you there is no expression here to evaluate. No evaluation, no resulting value. Therefore we get nothing we could assign to the creatures array. There is in fact no legal statement in this class! The syntax must work by exception, not adherence, to the rules you’ve learned.

You can do the same thing inside a method body. Compile the following code; you’ll get no errors:

public final class ArrayInitMethod {
public static void main(String args[]) {
int[] numbs = { 1, 2, 3,4, 5, 6 };
    }
}

But this magic ends with initialization. Let’s say you wanted to create an array in an expression such that it evaluates to an array on the fly, like this:

parseArray({1, 2, 3, 4, 5, 6});

Assume for the sake of illustration that the parseArray() method accepts an integer array as a parameter. You can’t do this in Java.

To make it work, you need a legal expression. To that end, Java added support for what it calls anonymous arrays. An anonymous array retains the convenience of a list but uses the new operator to complete a proper Java expression. The result isn’t as clean as simply declaring a list of elements, but it’s still better than having to write the array initialization and population longhand:

parseArray(new int[] {1, 2, 3, 4, 5, 6});

Writing the statement in this form preserves the rules Java programmers depend on to know that their programs are syntactically correct. The anonymous array approach also works for fields, so you can replace any occurrences of the exception syntax you may find in code with this minor edit if you wish.

Using an Array

Now let’s use the arrays you’ve learned to declare and create. First rule: Know when the [] brackets are required and when they aren’t. You already know an array reference is an Object subclass but its components belong to the type specified at declaration.

You need the separators to declare the array, either as a variable or as a method parameter. When you construct an array, you must size it, explicitly or implicitly, with a nonnegative integral value. Accessing an array component requires an integer value ranging from zero to one less than the array’s length.


The full discussion on exceptions takes place in Chapter 11, “Throwing and Catching Exceptions in Java.”

If you do use a negative value, or one that exceeds the array’s length, the compiler won’t actually notice. The JVM will, however, and will tell you about it by throwing a NegativeArraySizeException or an ArrayIndexOutOfBoundsException, respectively, and stopping the program at the scene of the crime.

public final class NegArray {
public static void main(String args[]) {
int neg[] = new int[-1];
   }
}
$ javac NegArray.java
$ java NegArray
Exception in thread "main" java.lang.
NegativeArraySizeException
        at NegArray.main(NegArray.java:3)

Notice that the code compiles but the program bails when run. The exception report returns the program line on which the code failed. The most common out-of-bounds errors occur when a program tries to use length as an index:

public final class OutOfBounds {
public static void main(String args[]) {
int arr[] = new int[11];
      System.out.println(arr[arr.length]);
   }
}
$ javac OutOfBounds.java
$ java OutOfBounds
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 11
        at OutOfBounds.main(OutOfBounds.java:4)

Remember that arrays start at zero. The value of length will always exceed the last legal index number by one.

When passing whole arrays as method parameters, we do not use the [] separators:

public final class PassingArgs {
public void sysPrint(String[] parms) {
      System.out.println("First argument: " + parms[0]);
   }
public static void main(String args[]) {
      PassingArgs pa = new PassingArgs();
if (args.length > 0)
pa.sysPrint(args);
   }
}

I discuss all the details of passing parameters in Chapter 7, “Using Java Methods to Communicate.”

In this example we pass the args array from the main() method directly to the sysPrint() method. We know we aren’t passing a null value. The JVM makes sure args always has a length, even if it is zero. The sysPrint() method must signify that it accepts a String array, which is the type received by main(). It is sufficient to pass the array to it by name alone.

Using a String Object as a Character Array

I waited until Chapter 4 to discuss arrays, but as you just saw, you’ve been using two of them since the start. The args array in the main method is one. The other, believe it or not, is the String class. Deep down, it turns out to be nothing more than an array of characters.

Recall from Chapter 2, “Applying Data Types in Java Programming,” that a String instance is an immutable object. Once the value has been set, you can’t change a String referent in any way. You can only use the reference to refer to another String. An array, as you have learned, is immutable in size. That’s how the String type manages to acquire this property.

A String really is just a char[] underneath the covers. You can in fact derive this representation from a String by asking for it, using its toCharArray() method:

public final class CharName {
public static void main(String args[]) {
      String name = "Michael";
char[] charName = name.toCharArray();
      System.out.println(charName);
      System.out.println(charName.toString());
   }
}

$ javac CharName
$ java CharName
Michael
[C@1469a69

The output shows that the charName elements match what was assigned to name. The open square bracket and the capital C in the last line signify that the printed referent is an array of characters.

The String class also supplies other methods to perform the nitty-gritty work of manipulating its content. As users of the String interface, you get the convenience of thinking in textual terms rather than in terms of the list of characters that make it up. Don’t underestimate this convenience! But also remember for every method that returns an altered String to you, you’re paying a hidden cost of making a new object. In a text-intensive program, that cost can become a significant part of your program’s overall resource demand.

Using System.arraycopy()

When you use String methods such as concat()—the method equivalent of the + operator—or substring(), you’re not really changing string content at the char level. Nonetheless, you still need an array of a different size to hold the results. For that work, and for arrays of any type, the System.arraycopy() method does the low-level transfer work.

As a consumer of Java classes, you’d like the class author to hide this detail for you. You’ll be a provider of Java classes someday, and your users would like you to do the same for them. The arraycopy() method is the first one I’ve looked at that takes several parameters: five altogether. The method signature is as follows:

static void arraycopy(Object src, int srcPos, Object dest,
int destPos, int length)

To use it, we need two arrays (the src and dest parameters): the copy-from and copy-to starting position for each array (srcPos and destPos); and the length, or total number of components, we want to transfer. Using arraycopy(), we can write a substring test of our own:

public final class MySubstring {
public static void main(String args[]) {
char[] html = new char[] {'M','i','c','h','a','e','l'};
char[] lastFour = new char[4];
      System.arraycopy(html, 3, lastFour, 0, lastFour.length);
      System.out.println(lastFour);
   }
}
$ javac MySubstring.java
$ java MySubstring
hael

Changing a method so you can use it a variety of ways is called generalizing or abstracting its code.

There are two things to note here. First, our test is quite specific. It would take a bit more technical skill and know-how to generalize this code into a reusable substringing operation. When another programmer saves us that trouble, it’s a big deal.

Second, to be generally useful itself, the arraycopy() method declares Object parameters when what it really needs are two arrays of the same declared type. There’s nothing in the method signature for arraycopy() to tell you that. Either that requirement strikes you as intuitive—you’ve worked with Java long enough to think of such things as “common sense”—or you read the documentation. Or you write test programs that fail until you figure it out.

This schism between an open-ended method signature and more precise requirements reveals a compromise that generalized code often has to make. You need the method to allow arrays of any type as input. If the programmer doesn’t make sure src and dest share the same type, however, the method will fail at runtime. It will not fail at compile time. The compiler, as usual, can only verify that the parameters are Object instances and therefore meet the requirements for arraycopy().

Using Arrays of Other Types

Examining the arraycopy() method closely raises another issue regarding arrays and type. From time to time, it’s tempting to put objects of different types into a single array because, from the programmer’s perspective, their contents are logically related. Say you have a person’s name, age, employee number, and date of hire. For a small program, it seems reasonable to store them as one Object array with Integer, String, Integer, and Date elements in it:

Object[] person = new Object[]
      { "Michael", new Integer(94),
      new Integer(1), new Date() };

An Object array will hold references of any type; all references have Object in their ancestry. The notion of implicit casting we discussed in Chapter 3 applies here for objects as well. If one type is a parent class to another type, a referent of the second type can always have the parent type for a reference.

Object string = new String("Some text");

By the same token, if we assign a String referent to an Object array index, the compiler applies implicit casting to accept the assignment.

You can even have an array of array components, which is the subject of the next section, “Understanding the Arrays Class.” A problem arises only when you want to retrieve the component. Since components are stored in an array of Object references, accessing a component will evaluate to an Object reference. To get back to the referent’s original type, you have to cast it explicitly, as follows:

String name = (String)person[0];  //ok
Integer age = (Integer)person[1]; //ok
Date start = (Date)person[2];     //oops!

If you cast to the wrong type, as this code suggests, the program will bail out. The compiler isn’t responsible for tracking the original reference type of each array component; only the programmer is. The problem with storing objects of different types together in one array is the same as the bitset concept described in Chapter 3; in return for a certain convenience in your storage arrangement, you need code to maintain knowledge of that arrangement.

Understanding the Arrays Class

Java arrays are a useful but bare-bones construct. Using the String class as an example, you can see how that class’s author uses character arrays to save the rest of us a lot of routine code work. The System.arraycopy() method moves content from one immutable structure to another, saving even the String class’s author more than a little hassle.

For just about any other operation an experienced array user could want, there’s the java.util.Arrays class. It is one example of what’s called a utility class: a set of methods that support working with another kind of object. The Arrays class includes methods such as binarySearch(), copyOf(), fill(), and sort(). Different versions of each method support each of the primitive types, as well as object references, as parameters.

The Arrays class provides many useful utilities. I make note of it here so you’ll think of it when taking inventory of methods that support array operations in the standard APIs.

Using a Multi-Dimensional Array

Arrays are objects, and of course array components can be objects. It doesn’t take much time, rubbing those two facts together, to wonder if arrays can hold other arrays, and of course they can. It’s a bit quirky in Java, though.

For some experienced programmers, the concept of a multi-dimensional array brings to mind a space in which each dimension has a fixed length, like a grid. Java allows you to create such a thing, but doesn’t require it, and so building an x × y array isn’t as straightforward as you might expect.

certobjective.eps

Declaring a Multi-Dimensional Array

Multiple array separators are all it takes to declare arrays with multiple dimensions. You can locate them with the type or variable name in the declaration, just as before:

int[][] vars;
int vars [][];
int[] vars [], space [][];     // also acceptable

This third syntax, which declares a two-dimensional array called vars and a three-dimensional array called space, is meant to be a succinct way to declare several array variables of one type. It’s not fun to maintain code of this sort. When you see code of this sort posted on numerous help sites asking for an interpretation, it’s a good sign that the syntax, however legal, creates more confusion than it is worth.

Even the JLS comments that this syntax is a poor choice for practice. That said, I might as well tell you now: You will come across programmers who prefer the letter of the law to its spirit. At the very least, you will certainly take exams or face technical interviews that test your command of such legalities. Be prepared.

Instantiating and Using a Multi-Dimensional Array

To create a uniform multi-dimensional array, or matrix, you can set the dimensions you want at instantiation time:

int [][] args = new int[4][5];

The result of this statement is an array args with four components, each of which refers to an array of five components. You can think of the addressable range as [0][0] through [3][4], but don’t think of it as a structure of addresses like [0,0] or [3,4].

Using the notion of rows and columns to visualize multi-dimensional arrays in Java has a lot of appeal. It makes sense to initialize both dimensions at once to create a table of data. Just remember there are no facilities in Java that support this view of a two-dimensional array. If you want a true matrix, supply the logic in a class to support that interface, similar to the way the String class does for a char array. Or find an existing class that does it for you.

You can also initialize just an array’s first dimension, and define the size of each array component in a separate statement:

int [][] args = new int[4][];
args[0] = new int[5];
args[1] = new int[3];

This technique reveals what you really get with Java: arrays of arrays that, properly managed, offer a multi-dimensional effect.

You can also use the trick that lets you assign a bracketed list of components to an array variable. Using outer braces to identify a list of arrays, and nested braces to hold the components of each component array, you can produce a makeshift table like the following:

public final class MDArray {
public String[][] bits =
    { { "Michael", "Ernest", "MFE"},
    { "Ernest", "Friedman-Hill", "EFH"},
    { "Kathi", "Duggan", "KD"},
    { "Jeff", "Kellum",  "JK"} };
public static void main(String args[]) {
      MDArray mda = new MDArray();
      System.out.println("There are " + mda.bits.length + " names:");
      System.out.println(mda.bits[0][2]);
      System.out.println(mda.bits[1][2]);
      System.out.println(mda.bits[2][2]);
      System.out.println(mda.bits[3][2]);
   }
}

You can use anonymous arrays as before, either with class members or for on-the-fly array creation. This approach is especially helpful if you want to fill component arrays one at a time:

bits[0] = new String[]
       {"Rudy", "Polanski", "RP"};
bits[1] = new String[]
       {"Rudy", "Washington", "RW"};
bits[2] = new String[]
       {"Rudy", "O'Reilly", "RO"};

Finding a graceful way to populate the next dimension—an array of arrays of arrays—is a challenge. Fortunately, there aren’t many uses for multi-dimensional arrays that can’t be managed by a simpler data structure. It’s instructive to work through the challenge of using lightweight but bare-bones facilities at least once. Doing so helps you realize the other services you could use to make the work easier and less repetitive.

Doing that kind of work often enough will lead you to another benefit of object-oriented development: writing extensible code. If you don’t have the method you need to make some routine task less tedious, you can write it. Methods like System.arraycopy() and classes like Arrays were developed with that motivation in mind. In a lot of cases, you can find a method like arraycopy(), which already does a lot of heavy lifting, and build on it.

Or, in our ongoing mission to become wide-ranging API consumers, we can browse packages and see if someone hasn’t already thought of, designed, and written code that makes working with arrays even easier.

Using an ArrayList Object

The ArrayList class is a direct outcome of realizing useful, general-purpose services that can spare the everyday programmer time and effort from routine, low-level work with arrays. Like the Arrays class, it resides in the java.util package, where all classes that are known as collections are kept.


Subclasses also add to and extend their parent classes. The key difference is that a subclass specializes, or narrows, the class it extends.

The ArrayList is a wrapper class, in one sense. A wrapper class is one that contains a simpler type and extends it in some fundamental way with named properties as fields and new services as methods.

Well-known wrapper classes in the java.lang package cover all the primitive types—Byte, Character, Short, Integer, Long, Float, Double, and Boolean. These classes let us handle their corresponding primitives as object references when that’s convenient. These classes also provide methods to support the type’s existing properties and add a few helpful capabilities to them.

You can also think of the relationship of ArrayList to an array as similar to the way String is related to the char array.

Understanding ArrayList Support for Array Features

The methods in the ArrayList class interface support many operations you already know with respect to an array:

  • isEmpty(): Same as testing an array’s length for zero. Returns true if the object has no elements.
  • clear(): Removes all elements. In an array you have to clear the elements one at a time.
  • contains(Object o): Returns true if any element matches the parameter. In any array we’d have to test the elements one at a time for effective equality.
  • remove(Object o): Removes the first occurrence of the parameter. With an array you’d provide your own search-and-delete logic.
  • size(): The method equivalent of an array’s length property.
  • toArray(): Returns an Object array of the elements. Similar in concept to String.toCharArray().
  • addAll(...): A functional equivalent to System.arraycopy(). Its usage is beyond our scope, but I mention it here to illustrate a service the class programmer thinks is important.

Capacity refers to the number of components a structure can hold. Size is the number of components it currently has.

Two aspects of ArrayList in particular make it a big deal. One, it has a mutable capacity. That is, through a method call you can change how many components it will hold. If you add a component to a full ArrayList object, it will resize itself. You never have to check first for an empty slot. You can also reduce an ArrayList object so its capacity and size are equal. Two, the ArrayList class implements an interface called List. The List interface formalizes the implicit properties of an array—the components have an order, duplicates are allowed—and gives them a type identity. You’ll have to wait until Chapter 10, “Understanding Java Interfaces and Abstract Classes,” to see why that’s so great, but it will be worth it.

For now, let’s review some methods in ArrayList made possible by these features:

  • add (int index, E element): Inserts the type element at the position index
  • ensureCapacity(int minCapacity): Resizes the ArrayList object to hold minCapacity elements
  • removeRange(intfromIndex, inttoIndex): Removes elements from the given range; shifts trailing elements down to close the gap
  • trimToSize(): Reduces the current capacity to equal current size

An alert reader may wonder if ArrayList methods just use System.arraycopy() to expand or trim capacity. They sure do. An array’s size, after all, is still an immutable property. The ArrayList class just lets you work around it.

Instantiating an ArrayList Class

certobjective.eps

Now the downside of the ArrayList class for a beginner: It’s not as intuitive to use as you might hope. The class supports three constructors:

ArrayList()
ArrayList(Collection<? extends E> c)
ArrayList(int initialCapacity)

The default constructor will create space for 10 components. The third version lets you set the initial capacity. The second one is hard to interpret without a lot more information. If you just toss an array in it, you’ll see it not only doesn’t work, but it also spews plenty of complaints:

import java.util.ArrayList;
public final class ArrListCap {
public static void main(String args[]) {
      String name = "Michael";
      ArrayList al = new ArrayList(name.toCharArray());
   }
}

$ javac ArrListCap.java
ArrListCap.java:6: error: no suitable constructor
found for ArrayList(char[])
          ArrayList al = new ArrayList(name.toCharArray());
                         ^
    constructor ArrayList.ArrayList(Collection) is not applicable
      (actual argument char[] cannot be converted to Collection
by method invocation conversion)
    constructor ArrayList.ArrayList() is not applicable
      (actual and formal argument lists differ in length)
    constructor ArrayList.ArrayList(int) is not applicable
      (actual argument char[] cannot be converted to int
by method invocation conversion)
1 error

Collection, like List, is a Java interface. It defines the methods a class must contain for its instances to be treated like a Collection type. We know arrays are Object subclasses only, so this attempt was doomed from the start.

We can instead create an ArrayList class, using one of the other constructors, and add components as you would with an array:

import java.util.ArrayList;
public final class ArrListCap {
public static void main(String args[]) {
      ArrayList al = new ArrayList(25);
al.add('M'),
al.add('i'),
al.add('c'),
al.add('h'),
al.add('a'),
al.add('e'),
al.add('l'),
   }
}

Now we have something we can experiment with. Take this snippet as starter code and try out the other methods in the ArrayList class on your own.

Consuming the ArrayList API

Perhaps you did not expect it to be easy to put an array into an ArrayList object. I did, at first, because I assumed an ArrayList would want to do all sorts of things to wean me off of arrays. And in a way it does, but not without imposing some requirements of its own. It doesn’t simply add every method a consumer could think to ask for.

The ArrayList, like many classes in the java.util package, is part of a larger type system called the Collections API. By enforcing certain rules and conditions through Java interfaces (and documentation), this type system promotes interoperability among many classes that, other than being collection oriented, don’t have much to do with each other.

When you’re ready to learn whole packages at a time, one of the first extended visits you should plan is with the java.util package. Learning the Collections API in particular will pay off handsomely time and again. It’s not a short and shallow learning curve, but it’s very well worth it. By the time you finish using this book, you’ll have reviewed one or two simpler type systems that will prepare you for this journey. Until then, keep learning.


The Essentials and Beyond
In this chapter, I defined the aspects of an array, the first type you’ve seen that lets you collect values of one type and manage them with a single object reference. We covered several ways to declare and use arrays and explored Java’s support for multi-dimensional arrays. We reviewed two classes in the java.util package, Arrays and ArrayList, which support arrays by providing utility methods or expanding their native capabilities, respectively.
Additional Exercises
1. Write a program to store your first name in a char array. (Just use the first eight characters if you wish). Reverse the array’s order and print out the result.
2. Using the code snippet in the section “Using Arrays of Other Types,” create an Object array. Retrieve each component from the array. Cast one to the wrong type. Compile and run the code and see what happens.
3. Write a program that requires two arguments from the command line. Transfer the two arguments to a char array big enough to hold both, including a space to separate them.
4. Using the code from Exercise 1, use the Arrays.sort() method on the resulting array and print it out.
5. Modify the code from the section “Instantiating an ArrayList Class” to your liking. Use two or three methods from the ArrayList class and test or print their results.
Review Questions
1. True or false: You can use the clone() method on an array with components of type float.
2. Which array declaration is not legal?
A. int[][] scores = new int[5][];
B. Object [][][] cubbies = new Object[3][0][5];
C. String beans[] = new beans[6];
D. java.util.Date[] dates[] = new java.util.Date[2][];
3. True or false: Given a char array containing j, u, d, o and a String object containing judo, the String object’s length()) method will return the same value that the char array’s length property contains.
4. What is an anonymous array?
A. An array that does not name its object reference
B. An array that does not name its type
C. A way to declare and initialize an array at the same time
D. A way to initialize arrays without a loop
5. True or false: The System.arraycopy() method lets you transfer the contents of one array to any other as long as the number of components transferred doesn’t exceed the target array’s length.
6. Which method reports the current capacity of an ArrayList object?
A. size()
B. capacity()
C. length()
D. None of the above
7. What two properties describe a list?
A. A list is sized and allows additional capacity.
B. A list is ordered and allows duplicate components.
C. A list is ordered and allows multiple dimensions.
D. A list is sized and can hold multiple arrays.
8. True or false: Two arrays with the same content are not equal.
9. True or false: Two ArrayList objects with the same content are equal.
10. Select the true statement.
A. You cannot create an ArrayList instance with zero capacity.
B. You cannot trim an ArrayList to zero capacity.
C. If you call remove(0) using an empty ArrayList object, it will compile successfully.
D. If you call remove(0) on an empty ArrayList, it will run successfully.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.133.49