I have a lovely bunch of coconuts, here they are a standing in a row...
But how did we get them standing in a row, how did we make them a bunch? In Java there are multiple ways that you can store your coconuts. You can put them into bag without caring about the order (this would be a
HashSet); you can line them up in a row (
ArrayList/
Vector); you can sort them from smallest to biggest (
TreeSet); you can name them (
HashMap); and you can name them, and then sort them by their name (
TreeMap).
So, lets start from the beginning... There are actually more ways to store your coconuts, but I find these to be the most useful. All of these classes are derived from either the
Collection or
Map interfaces. A Collection is basically just a group that stores your coconuts, where as a map stores your objects with an easy way to get at them, for example, by name.
The Coconut Class
We're going to start off with a simple coconut class and add more to it later. The song does say that we have big ones and small ones, so we should probably have a way to tell the size of the Coconut.
public class Coconut {
private Integer size;
public Integer getSize() {
return size;
}
public void setSize(Integer size) {
this.size = size;
}
public Coconut(Integer size){
setSize(size);
}
}
HashSet
A
HashSet is kinda of like throwing your coconuts in a bag, you're never really sure which one you're going to grab out. The HashSet is designed for speed (if set-up properly), but does not make it easy to get back any specific element.
Example:
HashSet<Coconut> myCoconuts = new HashSet<Coconut>();
Woah! "What's up with the angled brackets?" Well, all the Collections and Map classes are parameterized, also known as generic, classes (as of Java 5). Using parameterized classes means that some method or property of that class is going to use the class that you specified in between the <>. What this means for Collection and Map classes is that the Collection or Map that you create will only accept the specified type of value.
Note: This is a half-truth, parameterized classes work with inheritance, and at run-time, it is possible, due to erasure, that other items could get shoved in there, but you shouldn't need to worry about this second point right now.
And now you ask, "What would happen if I didn't make them parameterized?" Well, the classes will work fine if they are not parameterized, however, they will assume that anything going into them is an Object, which will mean that you'll probably have to cast it back to the correct class before trying to use it. This is actually how these classes worked before Java 5.
OK, back to HashSet. The first thing you'll probably want to do is add an item to the HashSet.
myCoconuts.add(new Coconut(1));
So, it's in there, how do I get it out? Well, HashSets aren't really made to get at one specific item, they're more meant to be iterated over. Iterating over a collection is very easy, and there is a special for loop syntax just for that purpose.
for (Coconut coconut : myCoconuts){
System.out.println(String.format("Coconut that is %d cm around.",
coconut.getSize()));
}
In the code above I use the special for loop syntax to iterate through all the coconuts. For each iteration of the loop the Coconut object that is in myCoconuts is stored in a variable named coconut, which you can access within the loop. So, let's try all of this.
import java.util.HashSet;
public class HashSetTut {
public static void main(String... args) {
HashSet myCoconuts = new HashSet();
for (int i = 1; i <= 10; i++)
myCoconuts.add(new Coconut(i));
for (Coconut coconut : myCoconuts)
System.out.println(String.format("Coconut that is %d cm around.",
coconut.getSize()));
}
}
Now, when we run this, you'll get an output similar to:
Coconut that is 4 cm around.
Coconut that is 8 cm around.
Coconut that is 7 cm around.
Coconut that is 6 cm around.
Coconut that is 1 cm around.
Coconut that is 2 cm around.
Coconut that is 9 cm around.
Coconut that is 3 cm around.
Coconut that is 5 cm around.
Coconut that is 10 cm around.
What happened? That's not the order I put them in. Well, that's what I was saying earlier, when I said "it's like putting them in a bag." They're not organized in the hash set the way you'd expect, and I'm not really going to explain this. If you want to read more about HashSet, see the
HashSet JavaDoc, but all you need to know at this point is that it's not going to return the elements to you in any sort of order. In fact, if you run the same example again, it will probably return the items in a different order.
Let's move on...
ArrayList
An array list is not like a HashSet, the array list stores the information in the order you entered it. It also allows you to retrieve a specific object by using an index. So let's modify the above code to use an ArrayList instead. With some really simple modifications we get:
import java.util.ArrayList;
public class ArrayListTut {
public static void main(String... args) {
ArrayList myCoconuts = new ArrayList();
for (int i = 1; i <= 10; i++)
myCoconuts.add(new Coconut(i));
for (Coconut coconut : myCoconuts)
System.out.println(String.format("Coconut that is %d cm around.",
coconut.getSize()));
}
}
And now when we run it, everything is returned in the order we entered them in.
Coconut that is 1 cm around.
Coconut that is 2 cm around.
Coconut that is 3 cm around.
Coconut that is 4 cm around.
Coconut that is 5 cm around.
Coconut that is 6 cm around.
Coconut that is 7 cm around.
Coconut that is 8 cm around.
Coconut that is 9 cm around.
Coconut that is 10 cm around.
A couple things to note here:
- It was really easy to change between HashSet and ArrayList because they both implement the Collections interface
- All the items returned in the order they were entered
Now, with ArrayList, we also have the ability to get at a specific element using an index. ArrayLists are zero-based arrays and therefore when we specify
myCoconuts.get(2);
We will get the third element that we inserted, in this case, the coconut with the size of 3.
Vector
The vector class has been around since Java 1.0. At this time the collections and list interfaces did not exist, but it was retrofitted in Java 2. ArrayList and Vector are essentially the same, but Vector is synchronized whereas ArrayList is not. What does that mean?... Well, it basically means that the Vector class is safe for use in multi-threading, and ArrayList is less safe. This is not to say that you couldn't use ArrayList in multi-threaded applications, you'd just have to be more careful with it.
TreeSet
The TreeSet class adds a new object into the mix, and there's two way to handle it. The first way is to make Coconut implement
Comparable. So, let's take a look at that. We'll need to modify our Coconut class. We'll do two things to the class:
- Add the implements Comparable statement to the class definition.
- Add the compareTo(Coconut o) method to the class.
public class Coconut implements Comparable{
private Integer size;
public Integer getSize() {
return size;
}
public void setSize(Integer size) {
this.size = size;
}
public Coconut(Integer size){
setSize(size);
}
public int compareTo(Coconut o) {
return getSize().compareTo(o.getSize());
}
}
The compareTo method returns an integer. If the returned integer is 0, then the two items are equal. If two items are equal, the TreeSet will not add the second item. If the integer is greater than 0, then this item is greater than the item that was passed in. If the integer is less than 0, then this item is less than the item that was passed in. Lets modify our ArrayListTut code a bit more to support TreeSets.
import java.util.TreeSet;
public class TreeSetTut {
public static void main(String... args) {
TreeSet myCoconuts = new TreeSet();
for (int i = 1; i <= 10; i+=2)
myCoconuts.add(new Coconut(i));
for (int i = 2; i <= 10; i+=2)
myCoconuts.add(new Coconut(i));
for (Coconut coconut : myCoconuts)
System.out.println(String.format("Coconut that is %d cm around.",
coconut.getSize()));
}
}
I've modified how the elements are added. All the odd numbers between 1 and 10 are added first, and then the even numbers. This is so we can see the TreeSet at work. If I were using ArrayLists in this example I would receive the following output:
Coconut that is 1 cm around.
Coconut that is 3 cm around.
Coconut that is 5 cm around.
Coconut that is 7 cm around.
Coconut that is 9 cm around.
Coconut that is 2 cm around.
Coconut that is 4 cm around.
Coconut that is 6 cm around.
Coconut that is 8 cm around.
Coconut that is 10 cm around.
But with TreeSets I receive:
Coconut that is 1 cm around.
Coconut that is 2 cm around.
Coconut that is 3 cm around.
Coconut that is 4 cm around.
Coconut that is 5 cm around.
Coconut that is 6 cm around.
Coconut that is 7 cm around.
Coconut that is 8 cm around.
Coconut that is 9 cm around.
Coconut that is 10 cm around.
So what happened here... Basically, when adding the item to the set, the TreeSet uses the compareTo method of the Coconut class to determine the position in the set.
Note:If an item is modified after being added to the set, in a way that would affect it's position, it's position in the tree will not change.
What about the other way? Well, the other way is to use a Comparator that is passed in to the TreeSet constructor. Lets see that:
import java.util.Comparator;
import java.util.TreeSet;
public class TreeSetTut {
public static void main(String... args) {
TreeSet myCoconuts = new TreeSet();
for (int i = 1; i <= 10; i += 2)
myCoconuts.add(new Coconut(i));
for (int i = 2; i <= 10; i += 2)
myCoconuts.add(new Coconut(i));
for (Coconut coconut : myCoconuts)
System.out.println(String.format("Coconut that is %d cm around.",
coconut.getSize()));
}
private static class CoconutComparator implements Comparator {
public int compare(Coconut o1, Coconut o2) {
return o2.getSize().compareTo(o1.getSize());
}
}
}
This results in:
Coconut that is 10 cm around.
Coconut that is 9 cm around.
Coconut that is 8 cm around.
Coconut that is 7 cm around.
Coconut that is 6 cm around.
Coconut that is 5 cm around.
Coconut that is 4 cm around.
Coconut that is 3 cm around.
Coconut that is 2 cm around.
Coconut that is 1 cm around.
A couple new things to note here:
- It sorted in reverse order. This is because in the CoconutComparator I'm comparing them in reverse. If I compared o1.getSize() to o2.getSize() they would appear in the normal order of things.he comparator can do any arbitrary thing you want it to do, I have it comparing the sizes, but you can make compare the hashCodes if you want.
- The comparator took precedence over the Comparable interface on the Coconut class. This is useful if a class that you did not write, or do not want to change already implements Comparable but you need to sort it in a different way.
Note: If sorting speed is of utmost importance to your application, you might be wise to use your own sorting algorithm rather than using the TreeSet. I'm sure the TreeSet implementation is better than a bubble sort, but there are probably many better sorting algorithms that could be used.
HashMap
Maps are different than Collections because they require to entries. The first being the key, and the second being the value. The following shows how you'd create a HashMap:
HashMap myCoconuts = new HashMap();
myCoconuts.put("Ford",new Coconut(5));
myCoconuts.put("Arthur",new Coconut(2));
myCoconuts.put("Zaphod",new Coconut(3));
myCoconuts.put("Marvin",new Coconut(20));
myCoconuts.put("Trillian",new Coconut(8));
Notice that we're specifying two types in the angle brackets here. The first type specifies the type of the key, and the second specifies the type of the value. You do not have to use strings as your key value, although they do prove very useful. The key,value pairing provides some really useful features, mainly, being able to get at a value using the key. For example:
myCoconuts.get("Arthur");
This will retrieve the coconut that we associated with "Arthur." From this, you can easily use it to retrieve any of values contained just by supplying the key.
Iterating through a Map is a bit different than iterating though a Collection. If you're only interested in the values stored with the Map, you can use:
for(Coconut c:myCoconuts.values())
//doStuff
If you are interested in both the key and value, then you need to use the entrySet() method. The entrySet() method returns a set of Entry objects. These Entry objects hold both the key and value.
for(Entry cocoEntry:myCoconuts.entrySet()){
System.out.println(String.format("%s - %d", cocoEntry.getKey(),
cocoEntry.getValue().getSize()));
We are now iterating through the set of Entry objects, rather than a set of Coconuts. To access the Coconut value, we're using cocoEntry.getValue(). To access the key, we use cocoEntry.getKey().
When all put together, the code looks like
import java.util.HashMap;
import java.util.Map.Entry;
public class HashMapTut {
public static void main(String... args) {
HashMap myCoconuts = new HashMap();
myCoconuts.put("Ford",new Coconut(5));
myCoconuts.put("Arthur",new Coconut(2));
myCoconuts.put("Zaphod",new Coconut(3));
myCoconuts.put("Marvin",new Coconut(20));
myCoconuts.put("Trillian",new Coconut(8));
for(Entry cocoEntry:myCoconuts.entrySet()){
System.out.println(String.format("%s - %d", cocoEntry.getKey(),
cocoEntry.getValue().getSize()));
}
}
}
And provides output like:
Ford - 5
Arthur - 2
Marvin - 20
Trillian - 8
Zaphod - 3
Again, the output provided by HashMap is unpredictable, in terms of ordering.
TreeMap
A TreeMap provides much of the same functionality as the HashMap. The TreeMap however will sort the map based on the key values. Again you have the option to provide a comparator into the constructor. If we simply replace the HashMap in the previous example with a TreeMap you will get the following output:
Arthur - 2
Ford - 5
Marvin - 20
Trillian - 8
Zaphod - 3
You can see here that it is sorted by the Key and not the Value.
I hope this has been helpful for you. I tried to give as much information about these main Collection and Map classes as I could without boring you to death. It is very important to know these classes and how to use them, and I now find it very rare to need an array (because
ArrayList is so much nicer). Maybe later I'll give them "a flick of the wrist." After all, that's what the show man says.