JavaEnterprise JavaObjects and Collections: ArrayLists

Objects and Collections: ArrayLists

In the previous columns, you explored the topic of Java Arrays and Vectors, from a technical approach as well as a somewhat historical perspective. In this column, you are going to take a look at one of the more common Java containers, the ArrayList.

Recall that Vectors had some potentially thorny issues relating to their use with threads. Whereas ArrayLists can be used in a way similar to Vectors, they are designed for use within a single thread. Vectors are able to be synchronized; however; there are certain side effects to deal with, including performance issues.

You will also touch upon the topic of <Generics>, which are a new addition to the Java platform. Although you will explore generics in much more detail in a later article, you will see why generics can be used to make your applications more type safe and thus much more robust.

Note: The Object-Oriented Thought Process series is intended for someone just learning an object-oriented language and who wants to understand the basic concepts before jumping into the code, or someone who wants to understand the infrastructure behind an object-oriented language he or she is already using. These concepts are part of the foundation that any programmer will need to make the paradigm shift from procedural programming to object-oriented programming. You should see the first article in this series for detailed descriptions for compiling and running all the code examples.

ArrayLists

Start directly by looking at some code. Listing 1 presents a very simple example of an ArrayList.

Listing 1: TestArrayList.java

import java.util.ArrayList;

class TestArrayList{
   public static void main(String[] args){
      ArrayList myList = new ArrayList();
      myList.add("One");
   }
}

The functionality is very similar to that of the Vector class that was covered in the last column. In fact, the Java documentation states the following.

Resizable-array implementation of the List interface. Implements all optional list operations, and permits all elements, including null. In addition to implementing the List interface, this class provides methods to manipulate the size of the array that is used internally to store the list. (This class is roughly equivalent to Vector, except that it is unsynchronized.)

Just as with the Vector, perhaps the biggest advantage of the ArrayList is that it is resizable, overcoming one of the limitations of an Array.

If you compile this code (which hopefully you will), the following message appears.


C:column26>"C:Program FilesJavajdk1.5.0_06binjavac"
   -classpath . TestArray
List.java
Note: TestArrayList.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
C:column26>

Although the code will compile and execute, as always, you should pay attention to the messages provided. In this case, the note informs you that there is a problem with unchecked or unsafe operations and encourages you to recompile with the .Xlint compiler option. When you add the .Xlint option, you get further information.


C:column26>"C:Program FilesJavajdk1.5.0_06binjavac"
   -Xlint -classpath . TestArrayList.java
TestArrayList.java:9: warning: [unchecked] unchecked call to add(E)
   as a member of the raw type java.util.ArrayList
   myList.add("One");
        ^
1 warning
C:column26>

The key part of this message is unchecked call to add(E). This relates to the use of <Generics>, which are a recent addition to the Java platform. Generics allow types as well as variables to be parameterized. You may recall that, when using collections of objects, casts are often required to specify the type being used. By specifying a generic, the correct type is returned, thus eliminating the need for certain casts. You will cover generics in much more detail in the near future.

However, the reason that generics come up at this point is because they are at the heart of the warning message you received in the previous compile. The error message indicates that the add() method performs an unchecked operation.

myList.add("One");

You are, in fact, adding a String to this ArrayList; however, you have not specified the type of the parameter in the definition of the ArrayList.

ArrayList myList = new ArrayList();

Note that, in the previous line of code, you have used original syntax (pre Java SE 1.5) when declaring the myList. Thus, any legacy code written prior to Java SE 1.5 will result in the warnings. Before you update the code to incorporate the generics, add some code to retrieve the value in the ArrayList in the earlier fashion.

Listing 2 demonstrates how to use an ArrayList to add and retrieve elements.

Listing 2: TestArrayList.java

import java.util.ArrayList;
import java.util.Iterator;

class TestArrayList{

   public static void main(String[] args){

      ArrayList myList = new ArrayList();

      myList.add("One");
      myList.add("Two");

      Iterator i = myList.iterator();
      while (i.hasNext()) {
         System.out.println(i.next());
      }
   }
}

When you run the code in Listing 2, you get the following output.


C:column26>"C:Program FilesJavajdk1.5.0_06binjava"
   -classpath . TestArrayList
One
Two
C:column26>

This output behaves in the same way that you would expect from the Vector implementation that you investigated in the previous article. However, when you move to Java SE 1.5, you want to take care of the issues pertaining to the generics.

Listing 3 includes code (in bold) that specifies the parameter type of ArrayList, in this case a String.

Listing 3: TestArrayList.java

import java.util.ArrayList;
import java.util.Iterator;

class TestArrayList{

   public static void main(String[] args){

      ArrayList<String> myList = new ArrayList<String>( );

      myList.add("One");
      myList.add("Two");

      Iterator i = myList.iterator();
      while (i.hasNext()) {
         System.out.println(i.next());
      }
   }
}

When this code is compiled, no warnings or errors are displayed.

The messages that were displayed before the generics were added complained about an unchecked call to add(E). See how this actually works, and whether the generics are really doing their job. To test this, send a non-string to the add() method as seen in Listing 4.

Listing 4: TestArrayList.java

import java.util.ArrayList;
import java.util.Iterator;

class TestArrayList{

   public static void main(String[] args){

      ArrayList<String> myList = new ArrayList<String>();

      myList.add("One");
      myList.add("Two");
      myList.add(3);

      Iterator i = myList.iterator();
         while (i.hasNext()) {
         System.out.println(i.next());
      }
   }
}

C:column26>"C:Program FilesJavajdk1.5.0_06binjavac"
   -Xlint -classpath . TestArrayList.java
TestArrayList.java:12: cannot find symbol
symbol  : method add(int)
location: class java.util.ArrayList<java.lang.String>
          myList.add(3);
            ^
1 error
C:column26>

Based on the error message, it appears that the generic specification is doing its job. When you try to pass an integer, the compiler catches the mistake. This is the key point here, the compiler catches the mistake. Type safety has been a major issue ever since the C Programming Language spawned C++, Java, C#, and so forth. As with all testing, it is better to catch errors as early as possible in the software life cycle. In this case, you are using generics to catch certain errors at compile-time rather than run-time. You should explore this issue a bit further.

Compile-Time vs. Run-Time

In Listing 4, you saw that the compiler actually issues an error when the code attempted to add an integer to an ArrayList that had a generic specification of a String. The major benefit here was that the compiler caught this and not the run-time environment. Why is this such a major advantage to software developers? Consider the code in Listing 5.

Listing 5: TestArrayList.java

import java.util.ArrayList;
import java.util.Iterator;

class TestArrayList{

   public static void main(String[] args){

      ArrayList myList = new ArrayList();

      myList.add("One");
      myList.add("Two");

      myList.add(3);

      Iterator i = myList.iterator();
      while (i.hasNext()) {
         System.out.println( (String) i.next());
      }
   }
}

In this example, the generic specification has been eliminated; however, you are using a cast to insure that only Strings are printed.

System.out.println( (String) i.next());

When you compile this code, you get the expected warnings.


C:column26>"C:Program FilesJavajdk1.5.0_06binjavac"
   -Xlint -classpath . TestArrayList.java
TestArrayList.java:10: warning: [unchecked] unchecked call to
   add(E) as a member of the raw type java.util.ArrayList
   myList.add("One");
        ^
TestArrayList.java:11: warning: [unchecked] unchecked call to
   add(E) as a member of the raw type java.util.ArrayList
   myList.add("Two");
        ^
TestArrayList.java:13: warning: [unchecked] unchecked call to
   add(E) as a member of the raw type java.util.ArrayList
   myList.add(3);
        ^
3 warnings
C:column26>

The key term here is warnings. They are not errors, thus, the compiler completes and the application can now be executed. However, when you do execute this code, you have a problem, as seen below.


C:column26>"C:Program FilesJavajdk1.5.0_06binjava"
   -classpath . TestArrayList
One
Two
Exception in thread "main" java.lang.ClassCastException:
   java.lang.Integer at TestArrayList.main(TestArrayList.java:17)
C:column26>

Rather than receiving a compiler error, and then having to fix it before the application is released, you now have a program crash. Instead of the problem being dealt with early in the development cycle, you now have a serious problem that actual users have to deal with. Crashes occurring after release of an application are infinitely more expensive than attending to these problems prior to the release. Designing code to catch potential problems early is one of the basic tenets of Object-Oriented development. In this case, the development system itself, Java SE 1.5, now has the infrastructure in place to enforce certain type checking.

Iterator vs. Foreach

You have already used the Iterator interface in Listing 2. However, the SDK now provides yet another way to work your way through a List—the Foreach statement.

Note: Even though this statement does not actually use the keyword Foreach (actually the keyword for is used), it is called the Foreach statement to fall in line with similar statements in other languages).

The syntax for the Foreach statement is as follows:

for (String d : myList)
   System.out.println(d);
}

The design of the Foreach is intended to make it a lot easier to use than the Iterator. It is interesting to note that the Iterator itself was an improvement over an earlier technique called an Enumerator.

Listing 6 walks through the list using both an Iterator and a Foreach statement.

Listing 6: TestArrayList.java

import java.util.ArrayList;
import java.util.Iterator;

class TestArrayList{

   public static void main(String[] args){

      ArrayList<String> myList = new ArrayList<String>();

      myList.add("One");
      myList.add("Two");

      System.out.println("Using an iterator");

      Iterator i = myList.iterator();
      while (i.hasNext()) {
         System.out.println(i.next());
      }
      System.out.println("nUsing Foreach");

      for (String: myList) {
         System.out.println(pos);
      }
   }
}

When you execute this program, you can see that the output is identical.


C:column26>"C:Program FilesJavajdk1.5.0_07binjava"
   -classpath . TestArrayList

Using an iterator
One
Two

Using Foreach
One
Two
C:column26>

The colors identifying parts of the following code fragment will help in understanding the syntax of the Foreach statement. In this case, the generic is <String> and the name of the list is myList. The use of the identifiers must be consistent.

ArrayList<String> myList = new ArrayList<String>();

for (String i : myList) {
   System.out.println(i);
}

It is expected that the use of Iterators will diminish as Java SE 1.5 gains widespread use. However, just as with the discussion on Vectors in the previous article, developers still need to understand the use of Iterators because Iterators will be encountered in code wriiten prior to the adoption of the Foreach statement.

More ArrayList Methods

Now that you have looked at some of the more interesting aspects of using an ArrayList, you can explore some of the other syntax for an ArrayList. For the most part, the design decisions made when using ArrayLists are similar to those of a Vector.

You have seen that, once the ArrayList has been created, you can walk though the list with a Foreach statement and process all the elements in the list. However, you also can update specific locations within the list by using the set() method as seen in listing 7. Note that, when this application is run, four distinct items are added to the myList collection. The set() method then is used to update the element at location 1, replacing the string “One” by “1”.

myList.set(1,"1");

The first parameter specifies the location in the list and the second parameter contains the actual value that the element will be set to. Be careful not to confuse the number ‘1’, which represents the location of the ArrayList, and the parameter “1”, which represents a string argument. The similarity is intentional to make you think about what the arguments actually are.

Listing 7: TestArrayList.java

import java.util.ArrayList;
import java.util.Iterator;

class TestArrayList{

   public static void main(String[] args){

      ArrayList<String> myList = new ArrayList<String>();

      myList.add("Zero");
      myList.add("One");
      myList.add("Two");
      myList.add("Three");

      System.out.println("nInitial List");

      for (String pos : myList) {
         System.out.println(pos);
      }

      myList.set(1,"1");

      System.out.println("nUpdated List");

      for (String pos : myList) {
         System.out.println(pos);
      }
   }
}

When you execute this program, you can see that the element in location 1 of myList has been successfully updated.


C:column26>"C:Program FilesJavajdk1.5.0_07binjava"
   -classpath . TestArrayList

Initial List
Zero
One
Two
Three

Updated List
Zero
1
Two
Three
C:column26>

Although you will continue your exploration of the ArrayList, and Lists in general, in the next article, the last topic you will look at this month is how to remove items from the ArrayList. Again, you can specify a location for removal of a particular item. This is accomplished with the remove() method.

myList.remove(1);

It is important to note that, when the element is removed, the subsequent elements move up one location in the list. This is to say that the internal pointers of the ArrayList are rebuilt so that the removed element is eliminated from the list.

Listing 8 contains code that will add items to myList, as before, and then remove the element at location 1.

Listing 8: TestArrayList.java

import java.util.ArrayList;
import java.util.Iterator;

class TestArrayList{

   public static void main(String[] args){

      int pos = 0;

      ArrayList<String> myList = new ArrayList<String>();

      myList.add("Zero");
      myList.add("One");
      myList.add("Two");
      myList.add("Three");

      System.out.println("nInitial List");

      pos = 0;
      for (String element : myList) {
         System.out.println(pos + ":" + element);
         pos++;
      }

      myList.remove(1);

      System.out.println("nRemove from List");

      pos = 0;
      for (String element: myList) {
         System.out.println(pos + ":" + element);
         pos++;
      }
   }
}

When you execute this program, you can see that the element in location 1 of myList has been successfully removed and all the other elements moved up a single location.


C:column26>"C:Program FilesJavajdk1.5.0_07binjava"
   -classpath . TestArrayList

Initial List
0:Zero
1:One
2:Two
3:Three

Remove from List
0:Zero
1:Two        // item a location 1 removed
2:Three
C:column26>

Conclusion

In this article, the syntax of an ArrayList was introduced and you transitioned from the Vector to the ArrayList. You also introduced the concept of generics and how they impact code written for the Java SE 1.5 as well as code written before the Java SE 1.5 release.

Next month, you will explore in much more detail that topic of generics and the parent classes of the ArrayList. When you look at the API for the class ArrayList, as presented below, you can see that there are many other classes that play a part in the implementation of an ArrayList, and Lists in general.

java.util: Class ArrayList<E>

java.lang.Object
   java.util.AbstractCollection<E>
      java.util.AbstractList<E>
         java.util.ArrayList<E>

All Implemented Interfaces:

Direct Known Subclasses:

References

  • www.sun.com
  • Just Java 2, 6th Edition. Peter van der Linden. 2004, Sun Microsystems.

About the Author

Matt Weisfeld is a faculty member at Cuyahoga Community College (Tri-C) in Cleveland, Ohio. Matt is a member of the Information Technology department, teaching programming languages such as C++, Java, and C# .NET as well as various web technologies. Prior to joining Tri-C, Matt spent 20 years in the information technology industry gaining experience in software development, project management, business development, corporate training, and part-time teaching. Besides the first edition of The Object-Oriented Thought Process, Matt has published two other computer books, and more than a dozen articles in magazines and journals such as Dr. Dobb’s Journal, The C/C++ Users Journal, Software Development Magazine, Java Report, and the international journal Project Management.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories