Before you begin

This unit is part of the “Intro to Java programming” learning path. Although the concepts discussed in the individual units are standalone in nature, the hands-on component builds as you progress through the units, and I recommend that you review the prerequisites, setup, and unit details before proceeding.

Unit objectives

  • Understand the purpose of the Java Collections Framework
  • Know how to declare and use Java arrays, lists, sets, and maps
  • Understand boxing and unboxing
  • Know how to make a collection iterable

The Java Collections Framework

Most real-world applications deal with collections of things like files, variables, records from files, or database result sets. The Java language has a sophisticated Collections Framework that you can use to create and manage collections of objects of various types. This unit introduces you to the most commonly used collection classes and gets you started with using them.

Arrays

Most programming languages include the concept of an array to hold a collection of things, and the Java language is no exception. An array is basically a collection of elements of the same type.

You can declare an array in one of two ways:

  • Create the array with a certain size, which is fixed for the life of the array.
  • Create the array with a certain set of initial values. The size of this set determines the size of the array — it’s exactly large enough to hold all of those values, and its size is fixed for the life of the array.

Declaring an array

In general, you declare an array like this:


new elementType arraySize

You can create an integer array of elements in two ways. This statement creates an array that has space for five elements but is empty:


// creates an empty array of 5 elements:
int[] integers = new int[5];

This statement creates the array and initializes it all at once:


// creates an array of 5 elements with values:
int[] integers = new int[] { 1, 2, 3, 4, 5 };

or


// creates an array of 5 elements with values (without the new operator):
int[] integers = { 1, 2, 3, 4, 5 };

The initial values go between the curly braces and are separated by commas.

Another way to create an array is to create it and then code a loop to initialize it:


int[] integers = new int[5];
for (int aa = 0; aa < integers.length; aa++) {
  integers[aa] = aa+1;
}

The preceding code declares an integer array of five elements. If you try to put more than five elements in the array, the Java runtime will throw an exception. You’ll learn about exceptions and how to handle them in Unit 14.

Loading an array

To load the array, you loop through the integers from 1 through the length of the array (which you get by calling .length on the array — more about that in a minute). In this case, you stop when you hit 5.

Once the array is loaded, you can access it as before:


Logger l = Logger.getLogger("Test");
for (int aa = 0; aa < integers.length; aa++) {
  l.info("This little integer's value is: " + integers[aa]);
}

This syntax also works, and (because it’s simpler to work with) I use it throughout this unit:


Logger l = Logger.getLogger("Test");
for (int i : integers) {
  l.info("This little integer's value is: " + i);
}

The element index

Think of an array as a series of buckets, and into each bucket goes an element of a certain type. Access to each bucket is gained via an element index:


element = arrayName [elementIndex];

To access an element, you need the reference to the array (its name) and the index that contains the element that you want.

The length attribute

Every array has a length attribute, which has public visibility, that you can use to find out how many elements can fit in the array. To access this attribute, use the array reference, a dot (.), and the word length, like this:


int arraySize = arrayName.length;

Arrays in the Java language are zero-based. That is, for any array, the first element in the array is always at arrayName[0]arrayName[0], and the last is at arrayName[arrayName.length – 1]arrayName[arrayName.length - 1].

An array of objects

You’ve seen how arrays can hold primitive types, but it’s worth mentioning that they can also hold objects. Creating an array of java.lang.Integer objects isn’t much different from creating an array of primitive types and, again, you can do it in two ways:


// creates an empty array of 5 elements:
Integer[] integers = new Integer[5];


// creates an array of 5 elements with values:
Integer[] integers = new Integer[] { 
Integer.valueOf(1),
Integer.valueOf(2),
Integer.valueOf(3),
Integer.valueOf(4),
Integer.valueOf(5)
};

Boxing and unboxing

Every primitive type in the Java language has a JDK counterpart class, as shown in Table 1.

Table 1. Primitives and JDK counterparts
Primitive JDK counterpart
boolean java.lang.Boolean
byte java.lang.Byte
char java.lang.Character
short java.lang.Short
int java.lang.Integer
long java.lang.Long
float java.lang.Float
double java.lang.Double

Each JDK class provides methods to parse and convert from its internal representation to a corresponding primitive type. For example, this code converts the decimal value 238 to an Integer:


int value = 238;
Integer boxedValue = Integer.valueOf(value);

This technique is known as boxing, because you’re putting the primitive into a wrapper, or box.

Similarly, to convert the Integer representation back to its int counterpart, you unbox it:


Integer boxedValue = Integer.valueOf(238);
int intValue = boxedValue.intValue();

Autoboxing and auto-unboxing

Strictly speaking, you don’t need to box and unbox primitives explicitly. Instead, you can use the Java language’s autoboxing and auto-unboxing features:


int intValue = 238;

Integer boxedValue = intValue;
//
intValue = boxedValue;

I recommend that you avoid autoboxing and auto-unboxing, however, because it can lead to code-readability issues. The code in the boxing and unboxing snippets is more obvious, and thus more readable, than the autoboxed code; I believe that’s worth the extra effort.

Parsing and converting boxed types

You’ve seen how to obtain a boxed type, but what about parsing a numeric String that you suspect has a boxed type into its correct box? The JDK wrapper classes have methods for that, too:


String characterNumeric = "238";
Integer convertedValue = Integer.parseInt(characterNumeric);

You can also convert the contents of a JDK wrapper type to a String:


Integer boxedValue = Integer.valueOf(238);
String characterNumeric = boxedValue.toString();

Note that when you use the concatenation operator in a String expression (you’ve already seen this in calls to Logger), the primitive type is autoboxed, and wrapper types automatically have toString() invoked on them. Pretty handy.

Lists

A List is an ordered collection, also known as a sequence. Because a List is ordered, you have complete control over where in the List items go. A Java List collection can only hold objects (not primitive types like int), and it defines a strict contract about how it behaves.

List is an interface, so you can’t instantiate it directly. (You’ll learn about interfaces Unit 17.) You’ll work here with its most commonly used implementation, ArrayList. You can make the declaration in two ways. The first uses the explicit syntax:


List<String> listOfStrings = new ArrayList<String>();

The second way uses the “diamond” operator (introduced in JDK 7):


List<String> listOfStrings = new ArrayList<>();

Notice that the type of the object in the ArrayList instantiation isn’t specified. This is the case because the type of the class on the right side of the expression must match that of the left side. Throughout the remainder of this learning path, I use both types, because you’re likely to see both usages in practice.

Note that I assigned the ArrayList object to a variable of type List. With Java programming, you can assign a variable of one type to another, provided the variable being assigned to is a superclass or interface implemented by the variable being assigned from. In a later unit, you’ll look more at the rules governing these types of variable assignments.

Formal type

The <Object> in the preceding code snippet is called the formal type. <Object> tells the compiler that this List contains a collection of type Object, which means you can pretty much put whatever you like in the List.

If you want to tighten up the constraints on what can or cannot go into the List, you can define the formal type differently:


List<Person> listOfPersons = new ArrayList<Person>();

Now your List can only hold Person instances.

Using lists

Using Lists — like using Java collections in general — is super easy. Here are some of the things you can do with Lists:

  • Put something in the List.
  • Ask the List how big it currently is.
  • Get something out of the List.

To put something in a List, call the add() method:


List<Integer> listOfIntegers = new ArrayList<>();
listOfIntegers.add(Integer.valueOf(238));

The add() method adds the element to the end of the List.

To ask the List how big it is, call size():


List<Integer> listOfIntegers = new ArrayList<>();

listOfIntegers.add(Integer.valueOf(238));
Logger l = Logger.getLogger("Test");
l.info("Current List size: " + listOfIntegers.size());

To retrieve an item from the List, call get() and pass it the index of the item you want:


List<Integer> listOfIntegers = new ArrayList<>();
listOfIntegers.add(Integer.valueOf(238));
Logger l = Logger.getLogger("Test");
l.info("Item at index 0 is: " listOfIntegers.get(0));

In a real-world application, a List would contain records, or business objects, and you’d possibly want to look over them all as part of your processing. How do you do that in a generic fashion? Answer: You want to iterate over the collection, which you can do because List implements the java.lang.Iterable interface.

Iterable

If a collection implements java.lang.Iterable, it’s called an iterable collection. You can start at one end and walk through the collection item-by-item until you run out of items.

In the Loops unit, I briefly mentioned the special syntax for iterating over collections that implement the Iterable interface. Here it is again in more detail:


for (objectType varName : collectionReference) {
  // Start using objectType (via varName) right away...
}

The preceding code is abstract; here’s a more realistic example:


List<Integer> listOfIntegers = obtainSomehow();
Logger l = Logger.getLogger("Test");
for (Integer i : listOfIntegers) {
  l.info("Integer value is : " + i);
}

That little code snippet does the same thing as this longer one:


List<Integer> listOfIntegers = obtainSomehow();
Logger l = Logger.getLogger("Test");
for (int aa = 0; aa < listOfIntegers.size(); aa++) {
  Integer i = listOfIntegers.get(aa);
  l.info("Integer value is : " + i);
}

The first snippet uses shorthand syntax: It has no index variable (aa in this case) to initialize, and no call to the List ‘s get() method.

Because List extends java.util.Collection, which implements Iterable, you can use the shorthand syntax to iterate over any List.

Sets

A Set is a collections construct that by definition contains unique elements — that is, no duplicates. Whereas a List can contain the same object maybe hundreds of times, a Set can contain a particular instance only once. A Java Set collection can only hold objects, and it defines a strict contract about how it behaves.

Because Set is an interface, you can’t instantiate it directly. One of my favorite implementations is HashSet, which is easy to use and similar to List.

Here are some things you do with a Set:

  • Put something in the Set.
  • Ask the Set how big it currently is.
  • Get something out of the Set.

A Set‘s distinguishing attribute is that it guarantees uniqueness among its elements but doesn’t care about the order of the elements. Consider the following code:


Set<Integer> setOfIntegers = new HashSet<Integer>();
setOfIntegers.add(Integer.valueOf(10));
setOfIntegers.add(Integer.valueOf(11));
setOfIntegers.add(Integer.valueOf(10));
for (Integer i : setOfIntegers) {
  l.info("Integer value is: " + i);
}

You might expect that the Set would have three elements in it, but it only has two because the Integer object that contains the value 10 is added only once.

Keep this behavior in mind when iterating over a Set, like so:


Set<Integer> setOfIntegers = new HashSet();
setOfIntegers.add(Integer.valueOf(10));
setOfIntegers.add(Integer.valueOf(20));
setOfIntegers.add(Integer.valueOf(30));
setOfIntegers.add(Integer.valueOf(40));
setOfIntegers.add(Integer.valueOf(50));
Logger l = Logger.getLogger("Test");
for (Integer i : setOfIntegers) {
  l.info("Integer value is : " + i);
}

Chances are that the objects print out in a different order from the order you added them in, because a Set guarantees uniqueness, not order. You can see this result if you paste the preceding code into the main() method of your Person class and run it.

Maps

A Map is a handy collection construct that you can use to associate one object (the key) with another (the value). As you might imagine, the key to the Map must be unique, and it’s used to retrieve the value at a later time. A Java Map collection can only hold objects, and it defines a strict contract about how it behaves.

Because Map is an interface, you can’t instantiate it directly. One of my favorite implementations is HashMap.

Things you do with Maps include:

  • Put something in the Map.
  • Get something out of the Map.
  • Get a Set of keys to the Map— for iterating over it.

To put something into a Map, you need to have an object that represents its key and an object that represents its value:


public Map<String, Integer> createMapOfIntegers() {
  Map<String, Integer> mapOfIntegers = new HashMap<>();
  mapOfIntegers.put("1", Integer.valueOf(1));
  mapOfIntegers.put("2", Integer.valueOf(2));
  mapOfIntegers.put("3", Integer.valueOf(3));
  //...
  mapOfIntegers.put("168", Integer.valueOf(168));
return mapOfIntegers;
}

In this example, Map contains Integer s, keyed by a String, which happens to be their String representation. To retrieve a particular Integer value, you need its String representation:


mapOfIntegers = createMapOfIntegers();
Integer oneHundred68 = mapOfIntegers.get("168");

Using Set with Map

On occasion, you might find yourself with a reference to a Map, and you want to walk over its entire set of contents. In this case, you need a Set of the keys to the Map:


Set<String> keys = mapOfIntegers.keySet();
Logger l = Logger.getLogger("Test");
for (String key : keys) {
  Integer  value = mapOfIntegers.get(key);
  l.info("Value keyed by '" + key + "' is '" + value + "'");
}

Note that the toString() method of the Integer retrieved from the Map is automatically called when used in the Logger call. Map returns a Set of its keys because the Map is keyed, and each key is unique. Uniqueness (not order) is the distinguishing characteristic of a Set (which might explain why there’s no keyList() method).

Test your understanding

  1. Which of these statements is true about Java arrays?
    1. Once created, the size of the array is fixed (that is, cannot be changed).
    2. To obtain the size of the array, use the .length property.
    3. Arrays are indexed, meaning each element of the array is obtained using a unique integer number.
    4. Arrays can contain primitive types as well as Java objects.
    5. All of the above.
  2. Write a program with a method called intInit() that initializes an array of int to the values 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 27, 29 and then returns that array to the caller. Call your Java class Unit10, and use that class for all of the remaining coding exercises in this quiz.
  3. Write a JUnit test case to test the intInit() method that verifies that the elements of your int[] are exactly as specified in Question 3. Use the element-access syntax (for example, array[0] to obtain the first element, and so on).
  4. Enhance the intInit() method you wrote for Question 2 to print out the elements of the int[] using the shorthand for loop syntax.
  5. Can you spot the error in the following code?
    
    public void question5() {
     int[] intArray = new int[4];
     
     intArray[0] = 1;
     intArray[1] = 2;
     intArray[2] = Integer.valueOf(3);
     intArray[3] = Integer.MAX_VALUE;
     
    }
    
    1. The intArray has too many values assigned to it. An exception will occur at runtime.
    2. Storing Integer objects and int in the same array is not allowed.
    3. The code contains no errors. Through auto-unboxing, the assignment of Integer object to the int array is achieved.
    4. None of the above.
  6. Add a method to your Unit10 class called problem6() that creates a List of the following: 32 This is a string Integer.valueOf(238) -410 null Write a JUnit test case to verify that your List was created in the correct order and contains the element values that you specified.
  7. Which of these interfaces allows only one instance of a particular value?
    1. List
    2. Map
    3. Set
    4. All of the above
  8. Which of these interfaces allows you to store a key/value pair using a unique key?
    1. List
    2. Map
    3. Set
    4. All of the above
  9. Which of these interfaces is guaranteed to preserve the order of the items it contains?
    1. List
    2. Map
    3. Set
    4. All of the above

Check your answers

  1. Which of these statements is true about Java arrays?

    1. Once created, the size of the array is fixed (that is, cannot be changed).
    2. To obtain the size of the array, use the .length property.
    3. Arrays are indexed, meaning each element of the array is obtained using a unique integer number.
    4. Arrays can contain primitive types as well as Java objects.
    5. All of the above.
  2. Write a program with a method called intInit() that initializes an array of int to the values 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 27, 29 and then returns that array to the caller. Call your Java class Unit10, and use that class for all of the remaining coding exercises in this quiz.

     public int[] intInit() {
    
        int[] intArray = {
           1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 27, 29
        };
    
        return intArray;
     }
    
  3. Write a JUnit test case to test the intInit() method that verifies that the elements of your int[] are exactly as specified in Question 3. Use the element-access syntax (for example, array[0] to obtain the first element, and so on).

     @Test
     public void testIntInit() {
        Unit10 testclass = new Unit10();
    
        int[] intArray = testclass.intInit();
    
        assertEquals(intArray[0], 1);
        assertEquals(intArray[1], 2);
           assertEquals(intArray[2], 3);
           assertEquals(intArray[3], 5);
           assertEquals(intArray[4], 7);
           assertEquals(intArray[5], 11);
           assertEquals(intArray[6], 13);
           assertEquals(intArray[7], 17);
           assertEquals(intArray[8], 19);
           assertEquals(intArray[9], 23);
           assertEquals(intArray[10], 27);
           assertEquals(intArray[11], 29);
        }
    
  4. Enhance the intInit() method you wrote for Question 2 to print out the elements of the int[] using the shorthand for loop syntax.

     public int[] intInit() {
    
        int[] intArray = {
           1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 27, 29
        };
    
        Logger l = Logger.getLogger(Unit10.class.getName());
    
        for (int number : intArray) {
        l.info("Number: " + number);
        }
    
        return intArray;
     }
    
  5. Can you spot the error in the following code?

    
    public void question5() {
     int[] intArray = new int[4];
     
     intArray[0] = 1;
     intArray[1] = 2;
     intArray[2] = Integer.valueOf(3);
     intArray[3] = Integer.MAX_VALUE;
     
    }
    

    1. The intArray has too many values assigned to it. An exception will occur at runtime.
    2. Storing Integer objects and int in the same array is not allowed.
    3. The code contains no errors. Through auto-unboxing, the assignment of Integer object to the int array is achieved.
    4. None of the above.
  6. Add a method to your Unit10 class called problem6() that creates a List of the following: 32 This is a string Integer.valueOf(238) -410 null Write a JUnit test case to verify that your List was created in the correct order and contains the element values that you specified.

     public List<Object> problem6() {
        List<Object> listOfObjects = new ArrayList<>();
    
        listOfObjects.add(32);
        listOfObjects.add("This is a string");
        listOfObjects.add(Integer.valueOf(238));
        listOfObjects.add(-410);
        listOfObjects.add(null);
    
        return listOfObjects;
     }
    
  7. Which of these interfaces allows only one instance of a particular value?

    1. List
    2. Map
    3. Set
    4. All of the above
  8. Which of these interfaces allows you to store a key/value pair using a unique key?

    1. List
    2. Map
    3. Set
    4. All of the above
  9. Which of these interfaces is guaranteed to preserve the order of the items it contains?

    1. List
    2. Map
    3. Set
    4. All of the above

Previous: LoopsNext: Archiving Java code