Collections | CodeGuru

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 […]

Written By
CodeGuru Staff
CodeGuru Staff
Mar 1, 2001
6 minute read
CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More

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.

Among


their other characteristics –


Stack

,


for example, implements a LIFO (last-in, first-out) sequence, and


Hashtable

is an

associative
array

that lets you associate any object with any other object – the Java
collection classes will automatically resize themselves. Thus, you can put in
any number of objects and you don’t need to worry about how big to make
the collection while you’re writing the program.

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
  }
} ///:~ 

You


can see that using a


Vector

is straightforward: create one, put objects in using

addElement( ),
and later get them out with
elementAt( ).
(Note that
Vector
has a method
size( )
to let you know how many elements have been added so you don’t
inadvertently run off the end and cause an exception.)

The


classes


Cat

and


Dog

are distinct – they have nothing in common except that they are


Object

s.


(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


Object

s,


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

It


turns out that in some cases things seem to work correctly without casting back


to your original type. The first case is quite special: the


String

class has some extra help from the compiler to make it work smoothly. Whenever


the compiler expects a


String

object and it hasn’t got one, it will automatically call the

toString( )
method that’s defined in
Object
and can be overridden by any Java class. This method produces the desired
String
object, which is then used wherever it was wanted.

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));

After


the ‘


+


sign the compiler expects to see a

String
object.
elementAt( )
produces an
Object,
so to get the desired
String
the compiler implicitly calls
toString( ).
Unfortunately, you can work this kind of magic only with
String;
it isn’t available for any other type.

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


Object

s,


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

This


kind of problem isn’t isolated – there are numerous cases in which


you need to create new types based on other types, and in which it is useful to


have specific type information at compile-time. This is the concept of a

parameterized
type
.
In C++, this is directly supported by the language in
templates.
At one point, Java had reserved the keyword
generic
to someday support parameterized types, but it’s uncertain if this will
ever occur.
Contents

|

Prev

|

Next
CodeGuru Logo

CodeGuru covers topics related to Microsoft-related software development, mobile development, database management, and web application programming. In addition to tutorials and how-tos that teach programmers how to code in Microsoft-related languages and frameworks like C# and .Net, we also publish articles on software development tools, the latest in developer news, and advice for project managers. Cloud services such as Microsoft Azure and database options including SQL Server and MSSQL are also frequently covered.

Property of TechnologyAdvice. © 2026 TechnologyAdvice. All Rights Reserved

Advertiser Disclosure: Some of the products that appear on this site are from companies from which TechnologyAdvice receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. TechnologyAdvice does not include all companies or all types of products available in the marketplace.