Thursday, September 4, 2014

Java Arrays

I recently took (and passed) Oracle's Java SE Programmer I (1ZO-803) exam.  Although I had waited until I had gotten at least a 90% on the practice exam, I unfortunately lost a few points on the questions concerning arrays.  It's particularly troubling for me as I had thought about doing a blog post on arrays but didn't think I had enough stuff to talk about.
Declaring an Array
There are two things to know about when declaring an array:
  • You don't specify a size
  • The square brackets may be placed before or after the identifier
int[] array; // preferred way
int array[];
Initializing an Array
Since arrays are a reference type, you can use the new keyword to initialize an array.
array = new int[3];
In this case, all the elements in the array are set to their default value:
for(int i = 0; i < array.length; i++) {
   System.out.println("array[" + i "] = " + array[i]);
}
Outputs
array[0] = 0
array[1] = 0
array[2] = 0
Note: This goes against what I had said earlier in that only class fields are assigned default values.

Alternatively you can use what's known as an anonymous array to both initialize and set the contents of the array:
array = new int[] {1, 2, 3};
There's an even shorter way to do this, but it only works when declaring the array.
int[] first = {1, 2, 3};
int[] second;

// second = {1, 2, 3}; // compiler error
// Array initializer is not allowed here

second = new int[]{1, 2, 3}; // okay
This last part is what tripped me up. For some reason I thought it was a compiler error to do:
int[] array = {1, 2, 3};
Multi-Dimensional Arrays
Multi-dimensional arrays are just arrays of arrays. When initializing a multi-dimensional array, you must specify the size of the first array:
int[][] array = new int[3][];
Since each element of a multi-dimensional array is a reference to another array, you'll need to initialize each of them like so:
array[0] = new int[3];
Otherwise, the element that the first array references is null:
System.out.println(array[1]); // null
And if you try to access the contents of a null array you'll get a NullPointerException:
System.out.println(array[2][2]); // NullPointerException
When initializing a multi-dimensional array, you can also specify the size of the other arrays like so:
int[][] array = new int[3][3];
Here's how to declare and initialize a multi-dimensional array:
int[][] array = {
  {1, 2, 3},
  {10, 20, 30}
};
Reference Type With No Class
Earlier I had mentioned that arrays are a reference type. What this means is that you can use all the methods that are in the Object class on an array.
int[] array = new int[3];

array.hashCode( );
There is a slight problem here in that although the Object class has a getClass( ) method there is no class that actually gets instantiated. Instead you'll get this somewhat weird looking syntax that begins with a '[' and is followed by a capital letter which represents the type of each element in the array.
System.out.println(array.getClass( )); // class [I
The getName( ) method of the Class class details what these letters represent:

Letter Element
Z boolean
B byte
C char
Lclassname class or interface
D double
F float
I int
J long
S short

You'll see one '[' for each "dimension" of the array:
int[][] array = new int[3][3];

System.out.println(array.getClass( )); // class [[I
length field
If you want to know the size of an array - that is, the number of elements it can contain - there is a read-only data field called length that you can use.
int[] array = {0, 1, 2, 3};

System.out.println("array.length = " + array.length); // array.length = 4
Note: For strings, length is a method.
"Hello World".length( );
Copying Arrays
There's several ways to copy an array. There's the clone( ) method from the Object class:
int[] first = {1, 2, 3};
int[] second = first.clone( );
This copies the entire array.  If you only want to copy a specific range of elements you can use the arraycopy method from the System class:
int[] src = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int[] dest = new int[3];

int srcPos = 2, destPos = 0, length = 3;
System.arraycopy(src, srcPos, dest, destPos, length);
Both of these techniques only make a shallow copy of the array. That is, for primitive types it just copies the value of the primitive and for reference types it copies the value of the reference, not the actual contents of the reference. So, for example, suppose we had the following class:
public class Klass {
   public String str;
}
We then do the following:
Klass[] src = new Klass[1];

src[0] = new Klass( );
src[0].str = "Hello World";

Klass[] dest = new Klass[src.length];

int srcPos = 0, destPos = 0, length = src.length;
System.arraycopy(src, srcPos, dest, destPos, length);
Now here's the part where shallow copying is concerned. If we change the str field for one of the dest array elements we'll also change the str field for the corresponding src element.
dest[0].str = "Goodbye World";

System.out.println(src[0].str); // Goodbye World
System.out.println(dest[0].str); // Goodbye World
If you want to copy the actual contents of each element in an array one way to do that is with a simple for loop:
for(int i = 0; i < src.length; i++) {
   dest[i] = new Klass();
   dest[i].str = new String(src[i].str);
}

dest[0].str = "Goodbye World";

System.out.println(src[0].str); // Hello World
System.out.println(dest[0].str); // Goodbye World
Overriding the clone method, however, does not work:
public class Klass implements Cloneable {

   public String str;

   @Override
   protected Object clone() throws CloneNotSupportedException {
      Klass clone = new Klass();
      clone.str = new String(this.str);
      return clone;
   }

   public static void main(String[] args) throws Exception {
      Klass[] src = new Klass[1];

      src[0] = new Klass();
      src[0].str = "Hello World";

      Klass[] dest = src.clone();

      dest[0].str = "Goodbye World";

      System.out.println(src[0].str); // Goodbye World
      System.out.println(dest[0].str); // Goodbye World
   }
}
Array Return Type
Here's an example of a method that returns an array:
int[] fooBar( ) {
   return new int[]{1, 2, 3};
}
Notice the size of the returned array isn't specified.
Array Parameters and Varags
You can specify that a method takes an array argument like so:
void fooBar(int[] array) {
}
A new feature that was introduced in Java 5 is the varargs, which stands for variable number of arguments. Varargs are represented by three dots and behave pretty much the same way that arrays do. That is, they have a length field and you use the square brackets syntax to access individual elements:
public static void fooBar(int... array) {
   for(int i = 0; i < array.length; i++) {
      System.out.println("array[" + i + "] = " + array[i]);
   }
}
There are a few differences between varargs and array parameters, though. For starters, when array parameters are used, you must pass an array to the method.

The following example passes an anonymous array to the method fooBar.
fooBar(new int[]{1, 2, 3});
With varargs, however, you just pass the arguments. The array is created for you.
fooBar(1, 2, 3);
You don't even have to pass any arguments to the method. This would create an empty array.
public static void fooBar(int... array) {
   System.out.println(array.length);
}

public static void main(String... args) {
   fooBar();
}
Outputs
0
There is a downside to this feature, though. Suppose you have two methods with the same name that both take a varargs.
public static void fooBar(int... array) {
}

public static void fooBar(String... array) {
}
Since both methods can be called without passing in any arguments...
public static void main(String... args) {
   fooBar();
}
Java will throw an Error message similar to the following at runtime.
Error: reference to fooBar is ambiguous,
  both method fooBar(int ...) and method fooBar(String ...) match
Another downside of using varargs is that they must come last in the list of parameters. That means you can only have one vararg per method:
void fooBar(String str, boolean flag, int... array) {
}
With arrays, on the other hand, you can have several of them in the list of parameters for a method:
void fooBar(int[] first, int[] second, int[] third) {
}
References
"Section 7.1 : Array Details" from David J. Eck's Introduction to Programming Using Java (7th Edition)

Java 1.5's varargs Guide

emory's reponse to a StackOverflow question titled "getClass method Java with array types" posted July 29, 2011

Java, A Beginner's Guide by Herbert Schildt (6th Edition), pages 221 & 222.

No comments:

Post a Comment