Copyright (c) 2004 by Charlie Calvert
This article is designed to show you the simplest possible example
of creating a Generic class in Java 1.5. The text was written against
Java 1.5 Beta 1, which can be downloaded generically from https://java.sun.com, or more
specifically from https://java.sun.com/j2se/1.5.0/download.jsp.
There are various complexities involved with using generics, and
various philosophical issues surrounding the particular implementation
of generics chosen by the designers of languages such as Java or C#. In
this article I'm going to side step those issues and just show you the
basics of how to use generics. I will, however, emphasize the role that
type checking plays in both the Java and C# implementation of generics.
This emphasis skirts around the edges of several controversial matters
without ever quite cluttering this article with confusing issues not
relevant to an understanding of the basics.
Generics in Java
The primary goal of generics in languages such as C++ and Java is to
allow a single class to work with a wide variety of types. For instance,
the ArrayList object has always had the ability to store
a list of any type of class. However, ArrayList has
always forced you to typecast the objects that you pulled out of the
list:
String myString = (String)myArrayList.get(0);
This system effectively destroys the benefits of a strongly typed
language. Java statements like the one just shown end up nullifying the
safety that accompanies built in type checking. As a result, the generic version of the ArrayList class
would be designed to work natively with any type of class, put to
preserve the benefits of type checking.
With the generic version of the ArrayList
class, your code would
look like this:
String myString = myArrayList.get(0);
Notice the abscence of the typecast. It would be a mistake, however,
to assume that you could assign anything to the return value of the get
method without needing a typecast. If you tried to assign anything else
besides a String to the output
of the get method, you would
encounter a
compile time type mismatch such as this one:
found : java.lang.String
required: java.lang.Integer
Integer data = list.get(0);
^
Take a brief look at the complete code for the example we have just
been discussing, but don't dwell on it too long:
ArrayList <String> list = new ArrayList<String>();
list.add("Generic String");
String data = list.get(0);
JOptionPane.showMessageDialog(this, data);
Glancing at this code, you probably noticed the word <String>
appearing in brackets. You can think about that syntax as simply being
a way of telling the Java compiler that you want this generic ArrayList to hold objects of type String. You are, in a sense, binding the
ArrayList object to the String type via the use of this syntax.
After
you have done this, it would be illegal to try to bind an Integer or
some other non-String type to the
result of the get function:
int data = list.get(); //
illegal!!! must be a String
because of ArrayList
<String> list declaration.
Declaring Your Own Generic Class.
For me, the easiest way to come to grips with generics, or many
other new technologies, is to see a
very simple example. Starting from this foundation, I can begin to
build a deeper understanding of how the technology works. Below you will
find the declaration for a very simple generic
class called BasicGeneric.
Beneath it you will find another simple
class called GenSample that
contains two methods, test01 and
test02, that exercise the
BasicGeneric class. Note that
this second class has a static
main method so
that it can be run as an application. I suggest putting both classes in
a single file called GenSample.java.
When compiling the example shown in Listing 1, you must add source "1.5" when invoking javac. It
is probably also helpful to add the -version flag to the call. In most
SDKs, version was not supported in Javac before 1.5, so it can help
alert you if you are accessing the wrong version of the binary:
javac -version -source "1.5" -sourcepath src -d classes src/applet_generics01/AppletGenerics.java
Listing 1: GenSample.java contains a very
simple generic class called BasicGeneric,
and a second class called GenSample
that calls it.
class BasicGeneric <A>
{
private A data;
public BasicGeneric(A data)
{
this.data = data;
}
public A getData()
{
return data;
}
}
public class GenSample
{
public GenSample()
{
}
public String test01(String input)
{
String data01 = input;
BasicGeneric<String> basicGeneric = new BasicGeneric<string>(data01);
String data02 = basicGeneric.getData();
return data02;
}
public int test02(int input)
{
Integer data01 = new Integer(input);
BasicGeneric <Integer> basicGeneric = new BasicGeneric<Integer>(data01);
Integer data02 = basicGeneric.getData();
return data02;
}
public static void main(String [] args)
{
GenSample sample = new GenSample();
System.out.println(sample.test01("This generic data"));
System.out.println(sample.test02(12));
}
}
Notice the declaration for BasicGeneric:
class BasicGeneric <A>
Here you can see the brackets that surround the capital letter A:
<A>. This syntax specifies that the class is a generic
type. Notice also that the class declares a variable of type A:
private A data;
This syntax can be confusing at first glance. But it is quite
sensible when you begin to understand generics. BasicGeneric does not
work with any specific type. As a result, we don't assign a type to A.
It is a generic type. But when you declare an instance of this class,
you must specify the type with which you want to work:
BasicGeneric<String> basicGeneric
Notice the <String>
syntax after the declaration BasicGeneric.
That is where you declare that this instance of BasicGeneric is going to work
variables
of type String. You can also
ask it work with variables of type Integer,
or of any other type that
catches your fancy. Here is an example of how to declare an instance of
this type that will work with an Integer:
BasicGeneric<Integer> basicGeneric
Once you understand this much, the rest should start to fall into
place. Notice, for instance, the declaration of the getData method:
public A getData()
{
return data;
}
This method returns a value of type A, which is to say that it will
return a generic type. But that does not mean that the function will
not have a type at run time, or even at compile time. After you declare
an instance of BasicGeneric,
you will have specified the type of A.
After that, BasicGeneric will
act as if it were declared from the very
beginning to work with that specific type, and that type only.
You should now be able to look at the two instances of using the
BasicGeneric type and make sense of them:
BasicGeneric<String> basicGeneric = new BasicGeneric<string>(data01);
String data02 = basicGeneric.getData();
BasicGeneric <Integer> basicGeneric = new BasicGeneric<Integer>(data01);
Integer data02 = basicGeneric.getData();
In one case the BasicGeneric
type is designed to work with Strings,
in the other case it is designed to work with Integers. What could be
simpler or more straight forward?
Summary
In the end, the basic facts about generics turn out to be fairly
easy to understand. The
syntax for using them is clean, and relatively intuitive once you get
over a few fairly low conceptual hurdles. The power of the syntax
should also be fairly obvious.
I should add, however, that this subject can get a bit trickier once
you start digging beneath the surface that we have mapped out in this
simple introductory article. Also, there are certain intentional limitations
in the Java and C# implementations of generics which are not inherent
in the C++ implementation. Hopefully in the future I will find time
to explain some of these complexities.
However, it should be clear that the basic syntax for using this
tool is simple and straight forward. It should also not
be hard to imagine some obvious
ways to use this syntax in your own programs. The best thing to do
then, is to download a copy of the Java J2SE 1.5 and copy my example
and get to work experimenting with this very fascinating new tool.
Connect with Us