Intro to Java Collections

This entry is part 7 of 13 in the series Intro to Java

I don’t want to get too deep into every little thing in the collections classes here. In fact, you already know a fair bit about them, because we have used them with some of our Animal class examples. This is more an overview of some of the basic Collections classes.

What do they look like?

There are three main types of collections: Lists, Maps, and Sets. Technically you could try to argue that Map doesn’t extend Collection, so therefore it is not a Collection, but I think of it as part of the group.
They are used for groups of objects, or exactly as its name would imply, collections of objects.

List

A List is just that, a list of objects. The List interface provides a way to add, remove and get objects from a list. It however is just an interface. ArrayList is one of the more popular concrete collections that implements the List interface. Each implementation of the List interface may behave slightly different, but once you understand how a List works, you have a good start on understanding any List.

An ArrayList can be thought of in some ways like an Array, but there are some differences. One of the biggest is that you can keep adding or removing from an ArrayList (or any List) without needing to have defined it in a constructor.

List aList = new ArrayList();

String[] aArray = new String[5];

On the first line we created a generic list that can hold ANY type of object, and of any amount of them. On the second line, we created an Array that always has a size of 5, and can only hold Strings. (The type limitation can be seen as a positive or a negative, and we will look at how we can do similar things with Lists when we get to Generics in a different lesson.)

Some of the typical methods of List you will use are :

• add(Object o) : to add an element to the end of the list
• add(int index, Object o) : to add an object at a particular index
• boolean remove(Object o) : remove the specific Object
• remove(int index) : remove the Object at a particular index
• boolean contains(Object o) : if an object exists in the list
• int indexOf(Object o) : position of an index in the list
• Object get(int index) : return the Object from a particular index
• Iterator iterator() : return an Iterator for the list
• size() : number of elements in the list
• Object[] toArray(Object[] o) : return an Array of type o

public class CollectionsTest1 {
   public static void main(String[] args) {
      // List is just an interface. We need to create something that
      // implements List, and ArrayList is one such Class that is pretty
      // common, but not the only one.
      List aList1 = new ArrayList();
      aList1.add(new Bird("Tweety"));
      aList1.add(new Fish("Swimmy"));
      aList1.add(new Mammal());
      // Does compiler complain? Does it work at runtime?
      // aList1.add(new String("Johnny"));

      // To prove that our List is a Collection, we have a static method that
      // will print out any item from a Collection
      CollectionUtilities.printAnimalList(aList1);
   }
}
public class CollectionUtilities {

   /**
    * Any object that extends Collection can be printed here. It works because
    * you can get an iterator from a collection object.
    * 
    * @param pCollection
    */
   public static void printList(Collection pCollection) {

      for (Iterator iterator = pCollection.iterator(); iterator.hasNext();) {
         Object aObject = iterator.next();
         System.out.println(aObject);
      }
   }

   /**
    * Note that for now, we are only specifying a list, there is nothing to
    * stop you from passing in a list of any type of object. See the generics
    * lesson for more info.
    * 
    * @param pAnimalList
    */
   public static void printAnimalList(List pAnimalList) {
      for (Iterator iterator = pAnimalList.iterator(); iterator.hasNext();) {
         // notice the need for a cast
         Animal aAnimal = (Animal) iterator.next();
         System.out.println(aAnimal.getName() + ":" + aAnimal.getType());
      }
   }

   /**
    * Sorts a List of elements of type Animal useing the AnimalComparator
    * 
    * @param pAnimalList
    */
   public static void sortAnimals(List pAnimalList) {
      Collections.sort(pAnimalList, new AnimalComparator());
   }

   /**
    * Figured if we had a comparator, we should use it on an array too.
    * 
    * @param pAnimalArray
    */
   public static void sortAnimals(Animal[] pAnimalArray) {
      Arrays.sort(pAnimalArray, new AnimalComparator());
   }

   /**
    * Shows how we can take in a list and return a set
    * 
    * @param pAnimalList
    * @return
    */
   public static Set findBirds(List pAnimalList) {
      Set aBirdSet = new HashSet();
      for (Iterator iterator = pAnimalList.iterator(); iterator.hasNext();) {
         Animal aAnimal = (Animal) iterator.next();
         if ( aAnimal instanceof Bird ) {
            aBirdSet.add(aAnimal);
         }
      }
      return aBirdSet;
   }
}

CollectionsTest1.java is a very basic class that uses an ArrayList. One of powerful features of Java is the use of Interfaces. Using them, we can create methods that know enough to get the job done, but little enough that they are still generic. That is the idea behind CollectionUtilities. The printList method can print anything that is a Collection. The printAnimalList can print more details as long as the item is a List of Animals.

public class CollectionsTest2 {
   public static void main(String[] args) {
      List aList1 = new ArrayList();
      aList1.add(new Bird("Birdy"));
      // size() method will print number of elements in the list
      System.out.println(aList1.size());
      // clear() method will remove all elements from list
      aList1.clear();
      System.out.println(aList1.size());

      Bird aTweety = new Bird("Tweety");
      aList1.add(aTweety);
      aList1.add(new Fish("Swimmy"));
      aList1.add(new Mammal());

      // contains() method is used to check the list for a particular object
      if (aList1.contains(aTweety)) {
         System.out.println("found tweety");
      } else {
         System.out.println("tweety has flown the coop");
      }
      // remember, this is object equals
      if (aList1.contains(new Fish("Swimmy"))) {
         System.out.println("found swimmy");
      } else {
         System.out.println("swimmy has left the building");
      }

      // get() method to retrieve an object from the list
      Animal aMammal1 = (Animal) aList1.get(2);
      System.out.println("aMammal1(index 2):" + aMammal1.getName());

      // This object is "live". The list only contains pointers to the real
      // objects. Change an object from the list...
      aMammal1.setName("Rover");

      // and the object in the list is changed.
      Animal aMammal2 = (Animal) aList1.get(2);
      System.out.println("aMammal2(index 2):" + aMammal2.getName());
   }
}

CollectionsTest2 shows some of the other methods that are commonly used with Lists.

Another interesting thing that is often done with Lists, is to sort them. You can sort Objects in anyway imaginable that makes sense to your for your particular object. One way to do this is to create a reusable class called a Comparator. AnimalComparator is a Comparator that can be used to sort based on first the type, then the name of the Animal. CollectionsTest3 shows how to use this comparator to sort both a List and an Array. This class also shows how you can take a List and turn it into an Array.

/**
 * This Comparator can be used to sort animals. A null animal is < an actual
 * animal. The order is alphabetical on type, then alphabetical on name.
 * 
 * Note: not null safe on type and name. If running against VERY large lists you
 * may want to try to avoid creating all the Strings for type and name.
 * 
 * @author Chris Ward 
 */
public class AnimalComparator implements Comparator {
   public int compare(Object pAnimal1, Object pAnimal2) {
      if (pAnimal1 == null && pAnimal2 == null) {
         return 0; // equal
      }
      if (pAnimal1 == null) {
         return -1; // pAnimal1 < pAnimal2
      }
      if (pAnimal2 == null) {
         return 1; // pAnimal 1 > pAnimal2
      }

      String aType1 = ((Animal) pAnimal1).getType();
      String aType2 = ((Animal) pAnimal2).getType();
      
      int aTypeCompare = aType1.compareTo(aType2);
      // If different, we don't need to compare anymore
      if (aTypeCompare != 0) {
         return aTypeCompare;
      }

      String aName1 = ((Animal) pAnimal1).getName();
      String aName2 = ((Animal) pAnimal2).getName();
      return aName1.compareTo(aName2);
   }
}
public class CollectionsTest3 {
   public static void main(String[] args) {
      List aList1 = new ArrayList();
      aList1.add(new Bird("Tweety"));
      aList1.add(new Fish("Swimmy"));
      aList1.add(new Mammal());
      aList1.add(new Bird("Robby Robbin"));
      aList1.add(new Mammal("Zeke"));

      System.out.println();
      CollectionUtilities.printAnimalList(aList1);

      // sort using a comparator
      CollectionUtilities.sortAnimals(aList1);

      System.out.println();
      CollectionUtilities.printAnimalList(aList1);

      System.out.println();
      List aList2 = new ArrayList();
      aList2.add(new Bird("Tweety"));
      aList2.add(new Fish("Swimmy"));
      aList2.add(new Mammal());
      aList2.add(new Bird("Robby Robbin"));
      aList2.add(new Mammal("Zeke"));

      // A List can convert to an Array
      Animal[] aAnimalArray = (Animal[]) aList2.toArray(new Animal[0]);

      // You can sort an array with the same comparator
      CollectionUtilities.sortAnimals(aAnimalArray);

      System.out.println();
      for (int i = 0; i < aAnimalArray.length; i++) {
         System.out.println(aAnimalArray[i].getName());
      }
   }
}

Map

A Map is like a look up table, or index table. For every object that goes into the Map, you have to give an index to where it is. A List can know where each object is by a numeric index, but if you removed the first object, you would no longer know the index of any of the Objects in your list! By using a Map, you can use any type of index you wish. You can use Integers, Strings, or any Object you can come up with. As with List, Map is just an Interface. We will use the HashMap in our examples.

CollectionsTest4 shows how to use a Map and how the concept of keys works.

public class CollectionsTest4 {
   public static void main(String[] args) {
      Integer aIntegerKey = new Integer(10);

      Map aMap1 = new HashMap();
      aMap1.put("bird", new Bird());
      aMap1.put("fish", new Fish());
      // Notice that there is no restriction on the key type
      aMap1.put(aIntegerKey, new Mammal());

      // Note: Map doesn't actually implement the Collection interface
      // CollectionUtilities.printList(aMap1);

      // A map stores Objects, must cast to Animal
      Animal aBird = (Animal) aMap1.get("bird");
      System.out.println(aBird);

      // Non Strings work as keys just fine
      Mammal aMammal = (Mammal) aMap1.get(aIntegerKey);
      System.out.println(aMammal);
   }
}

Some of the more common methods of a Map are:

• Object put(Object key, Object value)
• Object get(Object key)
• boolean containsKey(Object key) : check to see if a particular key exists in the Map
• boolean containsValue(Object value) : check to see if a particular value exits in the Map
• Object remove(Object key) : remove the entry that has the particular key
• int size()

If you get a lot of Birds in a cage, it is tough to remember what bird is inside, so let's use a HashMap to create a cache of Bird objects. BirdCage will use the bird's name as the key, and the actual Bird object as the value. See CollectionsTest5 to see this implemented.

/**
 * Using a HashMap to implement a type of cache. We use the name of our Bird's
 * as the key. The Bird its self is the value. If the bird does not exist in our
 * cache, we add it. It it is there, we return the one we found.
 * 
 * @author Chris Ward 
 */
public class BirdCage {

   private static BirdCage birdCage;
   private Map birdCache;

   /**
    * The BirdCage is a singleton. We have decided that there will be only one
    * bird cage in per java machine.
    */
   private BirdCage() {
      birdCache = new HashMap();
      System.out.println("Creating a BirdCage. There can be only one bird with a given name.");
   }

   public static BirdCage getInstance() {
      if (birdCage == null) {
         birdCage = new BirdCage();
      }
      return birdCage;
   }

   public Bird checkCage(String pName) {
      if (birdCage == null) {
         getInstance();
      }

      if (!birdCache.containsKey(pName)) {
         System.out.println(pName + " not found... adding.");
         Bird aBird = new Bird(pName);
         birdCache.put(pName, aBird);
         return aBird;
      }
      System.out.println("The name " + pName + " was found");
      System.out.println("Returning original " + pName);
      return (Bird) birdCache.get(pName);
   }

   public int getSize() {
      return birdCache.size();
   }

   public boolean isInCage(String pName) {
      return birdCache.containsKey(pName);
   }
}
public class CollectionsTest5 {
   public static void main(String[] args) {
      BirdCage aCage = BirdCage.getInstance();

      Bird aTweety1 = aCage.checkCage("Tweety");
      Bird aRobby = aCage.checkCage("Robby Robbin");
      //will Tweety be added twice?
      Bird aTweety2 = aCage.checkCage("Tweety");
      Bird aSam = aCage.checkCage("Sam");

      //What will be the size?
      System.out.println("size:" + aCage.getSize());

      //Will these two birds be equal?
      System.out.println("Tweety1 == Tweety2 : " + aTweety1.equals(aTweety2));
      System.out.println("Frank is in cage? " + aCage.isInCage("Frank"));
      System.out.println("Sam is in cage? " + aCage.isInCage("Sam"));
   }
}

Set

A set is like a partial List. They are often returned from a collection when you have asked for some subset of the whole. For example, if we had a big list of Animals, and we wanted to find out what of these Animals were Birds, we could get a Set back. The methods of Set are much like the List, but with a Set, no two elements are equal within a Set. There are no duplicates.

As an example, take a look at the CollectionsTest6 class. This creates a list, then asks a method from the CollectionUtilities to return a Set of just the Birds.

public class CollectionsTest6 {
   public static void main(String[] args) {
      List aAnimalList = new ArrayList();
      aAnimalList.add(new Bird("Tweety"));
      aAnimalList.add(new Fish());
      aAnimalList.add(new Mammal());
      aAnimalList.add(new Bird());
      aAnimalList.add(new Bird("Sam"));

      Set aBirdSet = CollectionUtilities.findBirds(aAnimalList);
      CollectionUtilities.printList(aBirdSet);
   }
}

More Information

Java collections trail: http://java.sun.com/docs/books/tutorial/collections/index.html

Series NavigationIntro to Java ReflectionIntro to Java Generics