The historical collection classes -- Arrays

Excerpt from Java Collections

The book Java Collections (Apress) by John Zukowski presents details on the Collection Library provided with the Java 2 platform. It includes sections on the historical collection classes, the Collections Framework, and alternative collection libraries. This excerpt (Chapter 1 from the "Historical Collection Classes" section) covers details about arrays -- what you can do with them and how to avoid pitfalls when using arrays.

Share:

John Zukowski (jaz@zukowski.net), Java consultant, JZ Ventures, Inc.

John Zukowski does strategic Java consulting with JZ Ventures to provide direction, architectural advice, design, debugging, and code reviews, plus custom solution development. He received a B.S. in computer science and mathematics from Northeastern University and an M.S. in computer science from Johns Hopkins University. Contact John at jaz@zukowski.net.



01 May 2001

Also available in Japanese

Arrays are the only collection support defined within the Java programming language. They are objects that store a set of elements in an order accessible by index, or position. They are a subclass of Object and implement both the Serializable and Cloneable interfaces. However, there is no .java source file for you to see how the internals work. Basically, you create an array with a specific size and type of element, then fill it up.

Note: Because arrays subclass Object, you can synchronize on an array variable and call its wait() and notify() methods.

Let's take a look at what we can do with array objects -- beginning with basic usage and declaration and moving through to copying and cloning. We'll also look at array assignment, equality checking, and reflection.

Array basics

Before going into the details of declaring, creating, initializing, and copying arrays, let's go over a simple array example. When creating a Java application, the main() method has a single argument that is a String array: public static void main(String args []). The compiler doesn't care what argument name you use, only that it is an array of String objects.

Given that we now have the command-line arguments to our application as an array of String objects, we can look at each element and print it. Within Java, arrays know their size, and they are always indexed from position zero. Therefore, we can ask the array how big it is by looking at the sole instance variable for an array: length. The following code shows how to do this:

public class ArrayArgs {
  public static void main (String args[]) {
    for (int i=0, n=args.length; i<n; i++) {
      System.out.println("Arg " + i +":" + args[i]);
    }
  }
}

Note: Array indices cannot be of type long. Because only non-negative integers can be used as indices, this effectively limits the number of elements in an array to 2,147,483,648, or 2 31 , with a range of indices from 0 to 2 31 -1.

Because an array's size doesn't change as we walk through the loop, there is no need to look up the length for each test case, as in: for (int i=0; i<args.length; i++). In fact, to go through the loop counting down instead of up as a check for zero test case is nominally faster in most instances: for (int i=args.length-1; i>=0; i -). While the JDK 1.1 and 1.2 releases have relatively minor performance differences when counting down versus counting up, these timing differences are more significant with the 1.3 release. To demonstrate the speed difference on your platform, try out the program in Listing 2-1 to time how long it takes to loop "max int" times:

Listing 2-1.Timing loop performance
public class TimeArray {
  public static void main (String args[]) {
    int something = 2;
    long startTime = System.currentTimeMillis();
    for (int i=0, n=Integer.MAX_VALUE; i<n; i++) {
      something =- something;
    }
    long midTime = System.currentTimeMillis();
    for (int i=Integer.MAX_VALUE-1; i>=0; i-) {
      something = -something;
    }
    long endTime = System.currentTimeMillis();
    System.out.println("Increasing Delta: " + (midTime - startTime));
    System.out.println("Decreasing Delta: " + (endTime - midTime));
  }
}

This test program is really timing the for-loop and not the array access because there is no array access.

Note: In most cases, the numbers calculated on my 400 MHz Windows NT system were in the low 11,000s for JDK 1.1 and 1.2. However, under JDK 1.3 with the -classic option (no JIT), the timing numbers increased to around 250,000. Even using the HotSpot VM with 1.3, the numbers were between 19,000 and 30,000.

If you ever try to access before the beginning or after the end of an array, an ArrayIndexOutOfBoundsException will be thrown. As a subclass of IndexOutOfBoundsException, the ArrayIndexOutOfBoundsException is a run-time exception, as shown in Figure 2-1. Thankfully, this means that you do not have to place array accesses within try-catch blocks. In addition, because looking beyond the bounds of an array is a run-time exception, your program will compile just fine. The program will only throw the exception when the access is attempted.

Figure 2-1. The class hierarchy of ArrayIndexOutOfBoundsException
The class hierarchy of ArrayIndexOutOfBoundsException

Note: You cannot turn off array bounds checking. It is part of the security architecture of the Java run time to ensure that invalid memory space is never accessed.

The following code demonstrates an improper way to read through the command-line array elements:

public class ArrayArgs2 {
  public static void main (String args[]) {
    try {
      int i=0;
      do {
        System.out.println("Arg " + i + ": " + args[i++]);
      } while (true);
    } catch (ArrayIndexOutOfBoundsException ignored) {
    }
  }
}

While functionally equivalent to the earlier ArrayArgs example, it is bad programming practice to use exception handling for control flow. Exception handling should be reserved for exceptional conditions.


Declaring and creating arrays

Remember that arrays are objects that store a set of elements in an index-accessible order. Those elements can either be a primitive datatype, such as an int or float, or any type of Object. To declare an array of a particular type, just add brackets ([ ]) to the declaration:

int[] variable;

For array declaration, the brackets can be in one of three places: int[] variable , int []variable , and int variable[]. The first says that the variable is of type int[]. The latter two say that the variable is an array and that the array is of type int.

Once you've declared the array, you can create the array and save a reference to it. The new operator is used to create arrays. When you create an array, you must specify its length. Once this length is set, you cannot change it. As the following code demonstrates, the length can be specified as either a constant or an expression:

int variable[] = new int [10];

or

int[] createArray(int size) {
  return new int[size];
}

Note: If you try to create an array where the length is negative, the run-time exception NegativeArraySizeException will be thrown. Zero-length arrays, however, are valid.

You can combine array declaration and creation into one step:

int variable[] = new int[10];

Warning: In the event that the creation of an array results in an OutOfMemoryError being thrown, all dimension expressions will already have been evaluated. This is important if an assignment is performed where the dimension is specified. For example, if the expression int variable[] = new int[var1 = var2*var2] were to cause an OutOfMemoryError to be thrown, the variable var1 will be set prior to the error being thrown.

Once an array has been created, you can fill it up. This is normally done with a for-loop or with separate assignment statements. For instance, the following code will create and fill a three-element array of names:

String names = new String[3];
names [0] = "Leonardo";
names [1] = "da";
names [2] = "Vinci";

Arrays of primitives

When you create an array of primitive elements, the array holds the actual values for those elements. For instance, Figure 2-2 shows what an array of six integers (1452, 1472, 1483, 1495, 1503, 1519) referred to from the variable life would look like with regards to stack and heap memory.

Figure 2-2. The stack and heap memory for an array of primitives
The stack and heap memory for an array of primitives

Arrays of objects

Unlike an array of primitives, when you create an array of objects, they are not stored in the actual array. The array only stores references to the actual objects, and initially each reference is null unless explicitly initialized. (More on initialization shortly.) Figure 2-3 shows what an array of Object elements would look like where the elements are as follows:

  • Leonardo da Vinci's country of birth, Italy
  • An image of his painting The Baptism of Christ
  • His theories (drawings) about helicopters and parachutes
  • An image of the Mona Lisa
  • His country of death, France

The key thing to notice in Figure 2-3 is that the objects are not in the array; only references to the objects are in the array.

Figure 2-3. The stack and heap memory for an array of objects
The stack and heap memory for an array of objects

Multidimensional arrays
Because arrays are handled through references, there is nothing that stops you from having an array element refer to another array. When one array refers to another, you get a multidimensional array. This requires an extra set of square brackets for each added dimension on the declaration line. For instance, if you wish to define a rectangular, two-dimensional array, you might use the following line:

int coordinates[][];

As with one-dimensional arrays, if an array is one of primitives, you can immediately store values in it once you create the array. Just declaring it isn't sufficient. For instance, the following two lines will result in a compilation-time error because the array variable is never initialized:

int coordinates[][];
coordinates[0][0] = 2;

If, however, you created the array between these two source lines (with something like coordinates = new int [3][4];), the last line would become valid.

In the case of an array of objects, creating the multidimensional array produces an array full of null object references. You still need to create the objects to store in the arrays, too.

Because each element in the outermost array of a multidimensional array is an object reference, there is nothing that requires your arrays to be rectangular (or cubic for three-dimensional arrays). Each inner array can have its own size. For instance, the following demonstrates how to create a two-dimensional array of floats, where the inner arrays are sized like a set of bowling pins -- the first row has one element, the second has two, the third has three, and the fourth has four:

float bowling[][] = new float[4][];
for (int i=0; i<4; i++) {
  bowling[i] = new float[i+1];
}

To help visualize the final array, see Figure 2-4.

Figure 2-4. A triangular, bowling-pin-like array
A triangular, bowling-pin-like array

When accessing an array with multiple dimensions, each dimension expression is fully evaluated before the next dimension expression to the right is ever examined. This is important to know if an exception happens during an array access.

Note: While you can syntactically place square brackets before or after an array variable when it is for a single dimension ([index]name or name[index]), you must place the square brackets after the array variable for multiple dimensions, as in name[index1][index2]. Syntactically, [index1][index2]name and [index1]name[index2] are illegal and will result in a compile-time error if found in your code. For declarations, it is perfectly legal to place the brackets before (type [][]name), after (type name[][]), or around (type []name[]) the variable name.

Keep in mind that computer memory is linear -- when you access a multidimensional array you are really accessing a one-dimensional array in memory. If you can access the memory in the order it is stored, the access will be most efficient. Normally, this wouldn't matter if everything fit in memory, as computer memory is quick to hop around. However, when using large data structures, linear access performs best and avoids unnecessary swapping. In addition, you can simulate multidimensional arrays by packing them into a one-dimensional array. This is done frequently with images. The two manners of packing the one-dimensional array are row-major order, where the array is filled one row at a time; and column-major order, where columns are placed into the array. Figure 2-5 shows the difference between the two.

Figure 2-5. Row-major versus column-major order
Row-major versus column-major order

Note: In many of the image processing routines, such as setPixels() of the ImageFilter class, you'll find two-dimensional image arrays flattened into row-major order, where Pixel(m, n) translates into a one-dimensional position, n * scansize + m. This reads top-down, left-to-right through the image data.

Initializing arrays

When an array is first created, the run-time environment will make sure that the array contents are automatically initialized to some known (as opposed to undefined) value. As with uninitialized instance and class variables, array contents are initialized to either the numerical equivalent of zero, the character equivalent of \u0000, the boolean false, or null for object arrays, as shown in Table 2-1.

Table 2-1. Array initial values
Default valueArray
0byte
short
int
long
0.0float
double
\u0000char
falseboolean
null Object

When you declare an array you can specify the initial values of the elements. This is done by providing a comma-delimited list between braces {} after an equal sign at the declaration point.

For instance, the following will create a three-element array of names:

String names[] = {"Leonardo", "da", "Vinci"};

Notice that when you provide an array initializer, you do not have to specify the length. The array length is set automatically based upon the number of elements in the comma-delimited list.

Note: The Java language syntax permits a trailing comma after the last element in an array initializer block, as in {"Leonardo", "da", "Vinci",}. This does not change the length of the array to four, but keeps it at three. This flexibility is primarily for the benefit of code generators.

For multidimensional arrays, you would just use an extra set of parentheses for each added dimension. For instance, the following creates a 6 X 2 array of years and events. Because the array is declared as an array of Object elements, it is necessary to use the Integer wrapper class to store each int primitive value inside. All elements within an array must be of the array's declared type, or a subclass of that type, in this case, Object, even though all of the elements are subclasses.

Object events [][] = {
  {new Integer(1452), new Birth("Italy")},
  {new Integer(1472), new Painting("baptismOfChrist.jpg")},
  {new Integer(1483), new Theory("Helicopter")},
  {new Integer(1495), new Theory("Parachute")},
  {new Integer(1503), new Painting("monaLisa.jpg")},
  {new Integer(1519), new Death("France")}
};

Note: In the event the type of the array is an interface, all elements in the array must implement the interface.

Starting with the second dot-point Java release (Java 1.1), the concept of anonymous arrays was introduced. While it was easy to initialize an array when it was declared, you couldn't reinitialize the array later with a comma-delimited list unless you declared another variable to store the new array in. This is where anonymous arrays step in. With an anonymous array, you can reinitialize an array to a new set of values, or pass unnamed arrays into methods when you don't want to define a local variable to store said array.

Anonymous arrays are declared similarly to regular arrays. However, instead of specifying a length within the square brackets, you place a comma-delimited list of values within braces after the brackets, as shown here:

new type[] {comma-delimited-list}

To demonstrate, the following line shows how to call a method and pass to it an anonymous array of String objects:

method(new String[] {"Leonardo", "da", "Vinci"});

You'll find anonymous arrays used frequently by code generators.

Passing array arguments and return values

When an array is passed as an argument to a method, a reference to the array is passed. This permits you to modify the contents of the array and have the calling routine see the changes to the array when the method returns. In addition, because a reference is passed around, you can also return arrays created within methods and not worry about the garbage collector releasing the array's memory when the method is done.


Copying and cloning arrays

You can do many things when working with arrays. If you've outgrown the initial size of the array, you need to create a new larger one and copy the original elements into the same location of the larger array. If, however, you don't need to make the array larger, but instead you want to modify the array's elements while keeping the original array intact, you must create a copy or clone of the array.

The arraycopy() method of the System class allows you to copy elements from one array to another. When making this copy, the destination array can be larger; but if the destination is smaller, an ArrayIndexOutOfBoundsException will be thrown at run time. The arraycopy() method takes five arguments (two for each array and starting position, and one for the number of elements to copy): public static void arraycopy (Object sourceArray,int sourceOffset,Object destinationArray,int destinationOffset,int numberOfElementsToCopy). Besides type compatibility, the only requirement here is that the destination array's memory is already allocated.

Warning: When copying elements between different arrays, if the source or destination arguments are not arrays or their types are not compatible, an ArrayStoreException will be thrown. Incompatible arrays would be where one is an array of primitives and the other is an array of objects; or the primitive types are different; or the object types are not assignable.

To demonstrate, Listing 2-2 takes an integer array and creates a new array that is twice as large. The doubleArray() method in the following example does this for us:

public class DoubleArray {
  public static void main (String args ]) {
    int array1[] = {1, 2, 3, 4, 5};
    int array2[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    System.out.println("Original size: " + array1.length);
    System.out.println("New size: " + doubleArray(array1).length);
    System.out.println("Original size: " + array2.length);
    System.out.println("New size: " + doubleArray(array2).length);
  }
  static int[] doubleArray(int original[]) {
    int length = original.length;
    int newArray[] = new int[length*2];
    System.arraycopy(original, 0, newArray, 0, length);
    return newArray;
  }
}

After getting the length of the original array, a new array of the right size is created before the old elements are copied into their original positions in the new array. After you learn about array reflection, you can generalize the method to double the size of an array of any type.

When executed, the program generates the following output:

Original size: 5
New size: 10
Original size: 9
New size: 18

Note: When copying arrays with arraycopy(), the source and destination arrays can be the same if you want to copy a subset of the array to another area within that array. This works even if there is some overlap.

Because arrays implement the Cloneable interface, besides copying regions of arrays, you can also clone them. Cloning involves creating a new array of the same size and type and copying all the old elements into the new array. This is unlike copying, which requires you to create and size the destination array yourself. In the case of primitive elements, the new array has copies of the old elements, so changes to the elements of one are not reflected in the copy. However, in the case of object references, only the reference is copied. Thus, both copies of the array would point to the same object. Changes to that object would be reflected in both arrays. This is called a shallow copy or shallow clone.

To demonstrate, the following method takes one integer array and returns a copy of said array.

static int[] cloneArray(int original[]) {
  return (int[])original.clone();
}

Array cloning overrides the protected Object method that would normally throw a CloneNotSupportedException with a public one that actually works.


Array immutability

It is useful to return an array clone from a method if you don't want the caller of your method to modify the underlying array structure. You can declare arrays to be final, as in the following example:

final static int array[] = {1, 2, 3, 4, 5};

But declaring an object reference final (specifically, an array reference here) does not restrict you from modifying the object. It only limits you from changing what the final variable refers to. The following line results in a compilation error:

array = new int[] {6, 7, 8, 9};

But changing an individual element is perfectly legal:

array[3] = 6;

Tip: Another way to "return" an immutable array from a method is to return an Enumeration or Iterator into the array, rather than returning the actual array. Either interface provides access to the individual elements without exposing the whole array to changes or requiring you to make a copy of the entire array. You'll learn more about these interfaces in later chapters of Java Collections.


Array assignments

Array assignments work like variable assignments. If variable x is a reference to an array of y, then x can be a reference to z if a variable of type z can be assigned to y. For instance, imagine that y is the AWT Component class and z is the AWT Button class. Because a Button variable can be assigned to a Component variable, a Button array can be assigned to a Component array:

Button buttons[] = {
  new Button("One"),
  new Button("Two"),
  new Button("Three")};
Component components[] = buttons;

When an assignment like this is made, both variables' buttons and components refer to the same heap space in memory, as shown by Figure 2-6. Changing an array element for one array changes the element for both.

Figure 2-6. Shared memory after an array assignment
Shared memory after an array assignment

If, after assigning an array variable to a superclass array variable (as in the prior example of assigning the button array to a component array variable) you then try to place a different subclass instance into the array, an ArrayStoreException is thrown. To continue the prior example, an ArrayStoreException would be thrown if you tried to place a Canvas object into the components array. Even though the components array is declared as an array of Component objects, because the components array specifically refers to an array of Button objects, Canvas objects cannot be stored in the array. This is a run-time exception as the actual assignment is legal from the perspective of a type-safe compiler.


Checking for array equality

Checking for equality between two arrays can be done in one of two manners depending upon the type of equality you are looking for. Are the array variables pointing to the same place in memory and thus pointing to the same array? Or are the elements of two arrays comparatively equivalent?

Checking for two references to the same memory space is done with the double equal sign operator (==). For example, the prior components and buttons variables would be equal in this case because one is a reference to the other:

components == buttons  //true

However, if you compare an array to a cloned version of that array then these would not be equal as far as == goes. Because these arrays have the same elements but exist in different memory space, they are different. In order to have a clone of an array be _equal_ to the original, you must use the equals() method of the java.util.Arrays class.

String[] clone = (String[]) strarray.clone();
boolean b1 = Arrays.equals(strarray, clone);   //Yes,they're equal

This will check for equality with each element. In the case where the arguments are arrays of objects, the equals() method of each object will be used to check for equality. Arrays.equals() works for arrays that are not clones, too.


Array reflection

If for some reason you are ever unsure whether an argument or object is an array, you can retrieve the object's Class object and ask it. The isArray()method of the Class class will tell you. Once you know you have an array, you can ask the getComponentType()method of Class what type of array you actually have. The getComponentType() method returns null if the isArray() method returns false. Otherwise, the Class type of the element is returned. You can recursively call isArray()if the array is multidimensional. It will still have only one component type. In addition, you can use the getLength() method of the Array class found in the java.lang.reflect package to discover the length of the array.

To demonstrate, Listing 2-3 shows that the argument to the main() method is an array of java.lang.String objects where the length is the number of command-line arguments specified:

public class ArrayReflection {
  public static void main (String args[]) {
    printType(args);
  }
  private static void printType (Object object) {
    Class type = object.getClass();
    if (type.isArray()) {
      Class elementType = type.getComponentType();
      System.out.println("Array of: " + elementType);
      System.out.println(" Length: " + Array.getLength(object));
    }
  }
}

Note: If printType()was to be called with the previously defined buttons and components variables, each would state that the array is of the java.awt.Button type.

If you don't use the isArray() and getComponentType() methods and you try to print the Class type for an array, you'll get a string that includes a [ followed by a letter and the class name (or no class name if a primitive). For instance, if you tried to print out the type variable in the printType() method above, you would get class [Ljava.lang.String; as the output.

In addition to asking an object if it is an array and what type of array it is, you can also create arrays at run time with the java.lang.reflect.Array class. This might be helpful to create generic utility routines that perform array tasks such as size doubling. (We'll return to that shortly.)

To create a new array, use the newInstance() method of Array, which comes in two varieties. For single dimension arrays you would normally use the simpler version, which acts like the statement new type [length] and returns the array as an object: public static Object newInstance(Class type, int length). For instance, the following creates an array with room for five integers:

int array[] = (int[])Array.newInstance(int.class, 5);

Note: To specify the Class object for a primitive, just add .class to the end of the primitive type name.You can also use the TYPE variable of the wrapper classes, like Integer.TYPE.

The second variety of the newInstance() method requires the dimensions to be specified as an array of integers: public static Object newInstance(Class type,int dimensions []). In the simplest case of creating a single dimension array, you would create an array with only one element. In other words, if you were to create the same array of five integers, instead of passing the integer value of 5, you would need to create an array of the single element 5 to pass along to the newInstance() method:

int dimensions[] = {5};
int array[] = (int[])Array.newInstance(int.class, dimensions);

As long as you only need to create rectangular arrays, you can fill up the dimensions array with each array length. For example, the following is the equivalent of creating a 3 X 4 array of integers:

int dimensions[] = {3, 4};
int array[][] = (int[][])Array.newInstance(int.class, dimensions);

If, however, you need to create a non-rectangular array, you would need to call the newInstance() method several times. The first call would define the length of the outer array and would have what looks like a funny-looking class argument ([].class for an array of floats). Each subsequent call would define the length of each inner array. For instance, the following demonstrates how to create an array of floats where the inner arrays are sized like a set of bowling pins: the first row with one element, the second with two, the third with three, and the fourth with four. To help you visualize this, recall the triangular array shown earlier in Figure 2-4.

float bowling[][] = (float[][])Array.newInstance(float[].class, 4);
for (int i=0; i<4; i++) {
  bowling[i] = (float[])Array.newInstance(float.class, i+1);
}

Once you've created your arrays at run time, you can also get and set the elements of the array. This isn't normally done unless the square bracket keys on your keyboard aren't working or you're working in a dynamic programming environment where the array names were unknown when the program was created. As shown in Table 2-2, the Array class has a series of getter and setter methods for getting and setting the array elements. Which method you use depends upon the type of array you're working with.

Table 2-2. Array Getter and Setter Methods
Getter methodsSetter methods
get(Object array, int index)set(Object array, int index, Object value)
getBoolean(Object array, int index)setBoolean(Object array, int index, boolean value)
getByte(Object array, int index)setByte(Object array, int index, byte value)
getChar(Object array, int index)setChar(Object array, int index, char value)
getDouble(Object array, int index)setDouble(Object array, int index, double value)
getFloat(Object array, int index)setFloat(Object array, int index, float value)
getInt(Object array, int index)setInt(Object array, int index, int value)
getLong(Object array, int index)setLong(Object array, int index, long value)
getShort(Object array, int index)setShort(Object array, int index, short value)

Note: You can always use the get() and set() methods. If the array is one of primitives, the return value of the get() method or the value argument to the set() method would be wrapped into the wrapper class for the primitive type, as in an Integer with an int array.

Listing 2-4 provides a complete example of how to create, fill up, and display information about an array. Square brackets are used only in the main() method declaration.

Listing 2-4. Using reflection to create, fill, and display an array
import java.lang.reflect.Array;
import java.util.Random;
public class ArrayCreate {
  public static void main (String args[]) {
    Object array = Array.newInstance(int.class, 3);
    printType(array);
    fillArray(array);
    displayArray(array);
  }
  private static void printType (Object object) {
    Class type = object.getClass();
    if (type.isArray()) {
      Class elementType = type.getComponentType();
      System.out.println("Array of: " + elementType);
      System.out.println("Array size: " + Array.getLength(object));
    }
  }
  private static void fillArray(Object array) {
    int length = Array.getLength(array);
    Random generator = new Random(System.currentTimeMillis());
    for (int i=0; i<length; i++) {
      int random = generator.nextInt();
      Array.setInt(array, i, random);
    }
  }
  private static void displayArray(Object array) {
    int length = Array.getLength(array);
    for (int i=0; i<length; i++) {
      int value = Array.getInt(array, i);
      System.out.println("Position: " + i +", value: " + value);
    }
  }
}

When run, the output will look like the following (although the random numbers will differ):

Array of: int
Array size: 3
Position: 0, value: -54541791
Position: 1, value: -972349058
Position: 2, value: 1224789416

Let's return to our earlier example of creating a method that doubles the size of an array. Now that you know how to get an array's type, you can create a method that will double the size of any type of array. This method ensures that we have an array before getting its length and type. It then doubles the size of a new instance before copying over the original set of elements.

static Object doubleArray(Object original) {
  Object returnValue = null;
  Class type = original.getClass();
  if (type.isArray()) {
    int length = Array.getLength(original);
    Class elementType = type.getComponentType();
    returnValue = Array.newInstance(elementType, length*2);
    System.arraycopy(original, 0, returnValue, 0, length);
  }
  return returnValue;
}

Character arrays

One last thing to mention before we wrap up our look at Java arrays: unlike C and C++, character arrays in Java are not strings. While you can easily go back and forth between a String and a char[] with the String constructor (which takes an array of char objects) and the toCharArray() method of String, they are definitely different.

Byte arrays are another case though. While they too are not strings, trying to go back and forth between a byte[] and a String involves a bit of work, because strings in Java are Unicode-based and 16 bits wide. You need to tell the String constructor what the encoding scheme is. Table 2-3 shows the primary available encoding schemes with the 1.3 platform. For a list of the extended set, see the online list at http://java.sun.com/j2se/1.3/docs/guide/intl/encoding.doc.html. These vary by JDK version.

Table 2-3. Primary byte-to-character encoding schemes
NameDescription
ASCIIAmerican Standard Code for Information Interchange
Cp1252Windows Latin-1
ISO8859_1ISO 8859-1, Latin alphabet No. 1
UnicodeBigSixteen-bit Unicode Transformation Format, big-endian byte order, with byte-order mark
UnicodeBigUnmarkedSixteen-bit Unicode Transformation Format, big-endian byte order
UnicodeLittle Sixteen-bit Unicode Transformation Format, little-endian byte order, with byte-order mark
UnicodeLittleUnmarkedSixteen-bit Unicode Transformation Format, little-endian byte order
UTF16Sixteen-bit Unicode Transformation Format, byte order specified by a mandatory initial byte-order mark
UTF8Eight-bit Unicode Transformation Format

If you do specify an encoding, you must place the call to the String constructor within a try-catch block because an UnsupportedEncodingException can be thrown if the specified encoding scheme is invalid.

If you are working only with ASCII characters, you really don't have to worry much here. Passing a byte [] to the String constructor without any encoding scheme argument uses the platform's default encoding, which is sufficient. Of course, to be safe, you can always just pass ASCII as the scheme.

Note: To check the default on your platform, look at the _file.encoding_ system property.


Summary

Arrays in Java seem easy to work with, but there are many things to be aware of to fully utilize their capabilities. While basic array declaration and usage can be considered simple, there are different things to be concerned about when working with arrays of primitives and objects, as well as multidimensional arrays. Array initialization doesn't have to be complicated, but throw in anonymous arrays and things get more involved.

Once you have an array, it takes a little thought to figure out how best to work with it. You need to take special care when passing arrays to methods because they are passed by reference. If you outgrow the original array size, you'll need to make a copy with additional space. Array cloning lets you pass around a copy without worrying about the idiosyncrasies of the final keyword. When assigning arrays to other variables, be careful not to run across an ArrayStoreException as it can be ugly to deal with at run time. Equality checking of arrays can involve either checking for the same memory area or checking for equivalently valued elements. Through the magic of Java reflection, you can manipulate objects that happen to be arrays. The last thing you learned in this chapter was how to convert between byte arrays and strings, and how byte arrays are not strings by default as they are in other languages.

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=10541
ArticleTitle=The historical collection classes -- Arrays
publish-date=05012001