Java Collections
Most real-world applications deal with collections of things: files, variables, records from files, database result sets, and so forth. The Java language has a sophisticated Collections Framework that allows you to create and manage collections of objects of various types. This section won't teach you everything about Java Collections, but it will introduce the most commonly used collection classes and get you started with using them.
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 nothing more than a collection of elements of the same type.
You can declare an array in one of two ways:
- Create it with a certain size, which is fixed for the life of the array.
- Create it with a certain set of initial values. The size of this set determines the size of the array — it will be exactly large enough to hold all of those values, and its size is fixed for the life of the array.
In general, you declare an array like this:
new elementType [arraySize] |
There are two ways to create an integer array of elements. This statement creates an array with space for five elements, but it 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 };
|
The initial values go between the curly braces and are separated by commas.
A harder way to create an array would be 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;
}
|
This code declares the integer array of five elements. If you try to put more than five elements in the array, the Java runtime will complain and throw an exception. You'll learn about exceptions and how to handle them in Part 2.
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 (new since JDK 5) also works:
Logger l = Logger.getLogger("Test");
for (int i : integers) {
l.info("This little integer's value is: " + i);
}
|
I find the newer syntax simpler to work with, and I'll use it throughout this section.
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 using an index:
element = arrayName [elementIndex]; |
To access an element, you need the reference to the array (its name) and the index where the element you want resides.
A handy method, as you've already seen, is length. It's a built-in method, so its syntax doesn't include the usual parentheses. Just type the word length and it will return — as you would expect — the size of the array.
Arrays in the Java language are zero-based. So, for some array named array, the first element in the array always resides at array[0], and the last resides at
array[array.length - 1].
You've seen how arrays can hold primitive types, but it's worth mentioning that they can also hold objects. In that sense, the array is the Java language's most utilitarian collection.
Creating an array of java.lang.Integer objects isn't much different from creating an array of primitive types. Once again, you have two ways to do it:
// 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));
|
Every primitive type in the Java language has a JDK counterpart class, which you can see in Table 4:
Table 4. 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 would unbox it, like so:
Integer boxedValue = Integer.valueOf(238); int intValue = boxedValue.intValue(); |
Strictly speaking, you don't need to box and unbox primitives explicitly. Instead, you could use the Java language's autoboxing and auto-unboxing features, like so:
int intValue = 238; Integer boxedValue = intValue; // intValue = boxedValue; |
I recommend that you avoid autoboxing and auto-unboxing, however, because it can lead to code problems. The code in the boxing and unboxing snippets is more obvious, and thus more readable, than the autoboxed code, and 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 String you suspect has a boxed type into its proper 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.
A List is a collection construct that is by definition 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, and it defines a strict contract about how it behaves.
List is an interface, so you can't instantiate it directly.
You'll work with its most commonly used implementation, ArrayList:
List<Object> listOfObjects = new ArrayList<Object>(); |
Note that we have assigned the ArrayList object to a variable of
type List. Java programming allows you to assign a variable of one type to another
so long as the variable being assigned to is a superclass or interface
implemented by the variable being assigned from. We will look more at
how variable assignments are affected in Part 2 in
the Inheritance section.
What's with the <Object> in the above code snip? It's called the formal type, and it 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 wanted to tighten up the constraints on what could or could not go into the List, you'd define it differently:
List<Person> listOfPersons = new ArrayList<Person>(); |
Now your List can only hold Person instances.
Using Lists is super easy, like Java collections in general. Here are some of the things you will want to do with Lists:
- Put something in the
List. - Ask the
Listhow big it currently is. - Get something out of the
List.
Let's try some of these out. You've already seen how to create an instance of List by instantiating its ArrayList implementation type, so you'll start from there.
To put something in a List, call the add() method:
List<Integer> listOfIntegers = new ArrayList<Integer>(); 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<Integer>();
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<Integer>();
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 would possibly want to look over them all as part of your
processing. How do you do that in a generic fashion? You want to iterate over the
collection, which you can do because List implements the
java.lang.Iterable interface. (You'll learn about
interfaces in Part 2.)
If a collection implements java.lang.Iterable, it is called an iterable collection. That means you can start at one end and walk through the collection item-by-item until you run out of items.
You've already seen the special syntax for iterating over collections that implement the Iterable interface, in the Loops section. Here it is again:
for (objectType varName : collectionReference) {
// Start using objectType (via varName) right away...
}
|
That previous example was abstract; now here's a more realistic one:
List<Integer> listOfIntegers = obtainSomehow();
Logger l = Logger.getLogger("Test");
for (Integer i : listOfIntegers) {
l.info("Integer value is : " + i);
}
|
That little code snip 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 snip uses shorthand syntax: there is 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.
A Set is a collections construct that by definition contains unique elements — that is, no duplicates. Whereas a List can contain the same object hundreds of times, a Set can only contain a given instance 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, so I'll show you one of my favorite implementations: HashSet. HashSet is easy to use and is similar to List.
Here are some things you will want to do with a Set:
- Put something in the
Set. - Ask the
Sethow 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 in fact it only has two because the Integer object that contains the value 10 will only be added 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 the objects will print out in a different order than you added them in because a Set guarantees uniqueness, not order. You'll see this for yourself if you paste the code above into the main() method of your Person class and run it.
A Map is a handy collection construct because it lets you 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, so I'll show you one of my favorite implementations: HashMap.
Here are some of the things you will want to do with Maps:
- Put something in the
Map. - Get something out of the
Map. - Get a
Setof keys to theMap— 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<String, Integer>();
mapOfIntegers.put("1", Integer.valueOf(1));
mapOfIntegers.put("2", Integer.valueOf(2));
mapOfIntegers.put("3", Integer.valueOf(3));
// . . .
mapOfIntegers.put("168", Integer.valueOf(168));
}
|
In this example, Map contains Integers, 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");
|
On occasion, you may find yourself with a reference to a Map, and you simply want to walk over its entire set of contents. In this case, you will 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 doesn't return a List of its keys because the Map is keyed, and each key is unique; uniqueness is the distinguishing characteristic of a Set.


