Collections

Bruce Eckel’s Thinking in Java Contents | Prev | Next

To
summarize what we’ve seen so far, your first, most efficient choice to
hold a group of objects should be an array, and you’re forced into this
choice if you want to hold a group of primitives. In the remainder of the
chapter we’ll look at the more general case, when you don’t know at
the time you’re writing the program how many objects you’re going
to need, or if you need a more sophisticated way to store your objects. Java
provides four types of collection
classes

to solve this problem:
Vector,
BitSet,
Stack,
and
Hashtable.
Although compared to other languages that provide collections this is a fairly
meager supply, you can nonetheless solve a surprising number of problems using
these tools.

Disadvantage:
unknown type

The
“disadvantage” to using the Java collections is that you lose type
information when you put an object into a collection. This happens because,
when the collection was written, the programmer of that collection had no idea
what specific type you wanted to put in the collection, and making the
collection hold only your type would prevent it from being a general-purpose
tool. So instead, the collection holds handles to objects of type
Object,
which is of course every object in Java, since it’s the root of all the
classes. (Of course, this doesn’t include primitive types, since they
aren’t inherited from anything.) This is a great solution, except for
these reasons:

  1. Since
    the type information is thrown away when you put an object handle into a
    collection,
    any
    type of object can be put into your collection, even if you mean it to hold
    only, say, cats. Someone could just as easily put a dog into the collection.
  2. Since
    the type information is lost, the only thing the collection knows it holds is a
    handle to an
    Object.
    You must perform a
    cast
    to the correct type before you use it.
On
the up side, Java won’t let you
misuse
the objects that you put into a collection. If you throw a dog into a
collection of cats, then go through and try to treat everything in the
collection as a cat, you’ll get an exception when you get to the dog. In
the same vein, if you try to cast the dog handle that you pull out of the cat
collection into a cat, you’ll get an exception at run-time.

Here’s
an example:

//: CatsAndDogs.java
// Simple collection example (Vector)
import java.util.*;
 
class Cat {
  private int catNumber;
  Cat(int i) {
    catNumber = i;
  }
  void print() {
    System.out.println("Cat #" + catNumber);
  }
}
 
class Dog {
  private int dogNumber;
  Dog(int i) {
    dogNumber = i;
  }
  void print() {
    System.out.println("Dog #" + dogNumber);
  }
}
 
public class CatsAndDogs {
  public static void main(String[] args) {
    Vector cats = new Vector();
    for(int i = 0; i < 7; i++)
      cats.addElement(new Cat(i));
    // Not a problem to add a dog to cats:
    cats.addElement(new Dog(7));
    for(int i = 0; i < cats.size(); i++)
      ((Cat)cats.elementAt(i)).print();
    // Dog is detected only at run-time
  }
} ///:~ 

The
classes
Cat
and
Dog
are distinct – they have nothing in common except that they are
Objects.
(If you don’t explicitly say what class you’re inheriting from, you
automatically inherit from
Object.)
The
Vector
class, which comes from
java.util,
holds
Objects,
so not only can you put
Cat
objects into this collection using the
Vector
method
addElement( ),
but you can also add
Dog
objects without complaint at either compile-time or run-time. When you go to
fetch out what you think are
Cat
objects using the
Vector
method
elementAt( ),
you get back a handle to an
Object
that you must cast to a
Cat.
Then you need to surround the entire expression with parentheses to force the
evaluation of the cast before calling the
print( )
method for
Cat,
otherwise you’ll get a syntax error. Then, at run-time, when you try to
cast the
Dog
object to a
Cat,
you’ll get an exception.

This
is more than just an annoyance. It’s something that can create some
difficult-to-find bugs. If one part (or several parts) of a program inserts
objects into a collection, and you discover only in a separate part of the
program through an exception that a bad object was placed in the collection,
then you must find out where the bad insert occurred. You do this by code
inspection, which is about the worst debugging tool you have. On the upside,
it’s convenient to start with some standardized collection classes for
programming, despite the scarcity and awkwardness.


Sometimes
it works right anyway

Thus,
all you need to do to make objects of your class print out is to override the
toString( )
method, as shown in the following example:

//: WorksAnyway.java
// In special cases, things just seem
// to work correctly.
import java.util.*;
 
class Mouse {
  private int mouseNumber;
  Mouse(int i) {
    mouseNumber = i;
  }
  // Magic method:
  public String toString() {
    return "This is Mouse #" + mouseNumber;
  }
  void print(String msg) {
    if(msg != null) System.out.println(msg);
    System.out.println(
      "Mouse number " + mouseNumber);
  }
}
 
class MouseTrap {
  static void caughtYa(Object m) {
    Mouse mouse = (Mouse)m; // Cast from Object
    mouse.print("Caught one!");
  }
}
 
public class WorksAnyway {
  public static void main(String[] args) {
    Vector mice = new Vector();
    for(int i = 0; i < 3; i++)
      mice.addElement(new Mouse(i));
    for(int i = 0; i < mice.size(); i++) {
      // No cast necessary, automatic call
      // to Object.toString():
      System.out.println(
        "Free mouse: " + mice.elementAt(i));
      MouseTrap.caughtYa(mice.elementAt(i));
    }
  }
} ///:~ 

You
can see the redefinition of
toString( )
in
Mouse.
In the second
for
loop in
main( )
you find the statement:

System.out.println("Free
mouse: " + mice.elementAt(i));

A
second approach to hiding the cast has been placed inside
Mousetrap.
The
caughtYa( )
method accepts not a
Mouse,
but an
Object,
which it then casts to a
Mouse.
This is quite presumptuous, of course, since by accepting an
Object
anything could be passed to the method. However, if the cast is incorrect
– if you passed the wrong type – you’ll get an exception at
run-time. This is not as good as compile-time checking but it’s still
robust. Note that in the use of this method:

MouseTrap.caughtYa(mice.elementAt(i));

no
cast is necessary.


Making
a type-conscious Vector

You
might not want to give up on this issue just yet. A more ironclad solution is
to create a new class using the
Vector,
such that it will accept only your type and produce only your type:

//: GopherVector.java
// A type-conscious Vector
import java.util.*;
 
class Gopher {
  private int gopherNumber;
  Gopher(int i) {
    gopherNumber = i;
  }
  void print(String msg) {
    if(msg != null) System.out.println(msg);
    System.out.println(
      "Gopher number " + gopherNumber);
  }
}
 
class GopherTrap {
  static void caughtYa(Gopher g) {
    g.print("Caught one!");
  }
}
 
class GopherVector {
  private Vector v = new Vector();
  public void addElement(Gopher m) {
    v.addElement(m);
  }
  public Gopher elementAt(int index) {
    return (Gopher)v.elementAt(index);
  }
  public int size() { return v.size(); }
  public static void main(String[] args) {
    GopherVector gophers = new GopherVector();
    for(int i = 0; i < 3; i++)
      gophers.addElement(new Gopher(i));
    for(int i = 0; i < gophers.size(); i++)
      GopherTrap.caughtYa(gophers.elementAt(i));
  }
} ///:~ 

This
is similar to the previous example, except that the new
GopherVector
class has a
private
member
of type
Vector
(inheriting from
Vector
tends to be frustrating, for reasons you’ll see later), and methods just
like
Vector.
However, it doesn’t accept and produce generic
Objects,
only
Gopher
objects.

Because
a
GopherVector
will accept only a
Gopher,
if you were to say:

gophers.addElement(new
Pigeon());

you
would get an error message
at
compile time
.
This approach, while more tedious from a coding standpoint, will tell you
immediately if you’re using a type improperly.

Note
that no cast is necessary when using
elementAt( )
– it’s always a
Gopher.


Parameterized
types

More by Author

Must Read