III. Generic Types
A. Polymorphism: refers to the ability of the same code to perform the
same action on different types of data. There are two primary
types of polymorphism:
1. Parametric polymorphism: The code takes the type as a parameter,
either explicitly (as in C++/Java templates) or implicitly (as in
Lisp, which determines the type from the data being used). Some
compilers, such as the C++ compiler, generate multiple copies of
the code, one for each type. Other compilers, such as the Java
compiler, generate a single copy of the code which should work
with all types. This is why Java's templates cannot support
primitive types, because a single copy of the code cannot use
built-in operations, such as + or <. Instead it must rely
on method calls, and hence it must work with objects.
2. Inheritance polymorphism: The code is shared between the super
class and its children via a non-virtual method. Hence
the same code works with multiple objects of different types, but
the types all share a common supertype.
B. Polymorphism in C, C++, and Java
1. Java
a. Original approach: the developers of Java felt that C++ templates
were too complicated, so their original solution was to provide
the implicit master class Object.
i. Generic code uses the type Object whenever it needs to refer
to a specific piece of data
ii. Programs downcast the Object type to a specific type when they
retrieve an object from a generic data structure
iii. Disadvantages
1. downcasting can be dangerous
2. Object supports generic data structures but not generic
algorithms, such as quicksort, which require a common
comparator method
iv. Advantages: One copy of the code works with multiple types
b. All this downcasting was both bothersome to use, and ran the
risk of run-time type conversion errors that could not be
type-checked by the compiler. Eventually the Java developers
introduced a simplified template mechanism. When programmers
declare a variable to use a template type, the declaration looks
much like a C++ template. However, the definition of a template
type is simpler. Here's a simple declaration of a wrapper
type:
public class Box<T> {
// T stands for "Type"
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
As you can see, the type looks like a parameter to a
function. You are allowed to pass multiple types to a
template. For example, a hash table would have types for
both the key and the value objects.
Here is a declaration of a variable of type Box, and its
use:
class Test {
static public void main(String args[]) {
Box<String> b = new Box<String>();
b.add(args[0]);
String myArg = b.get();
System.out.println(myArg);
}
}
i. Java generics have the restriction that they must be
instantiated with a user-defined class. Hence generics
may not be instantiated with primitive types, such as ints.
If you want to use a primitive type, use its wrapper type
instead, such as Integer.
ii. Generic Methods: You can also declare generic methods within
either non-generic classes, or within a generic class that
has a different type. For example, a generic printing method
that I place inside Box.java might look as follows:
public <V> void print(V data[]) {
for (V val : data) {
System.out.println(val);
}
}
Notice that there is a <V> in front of the void and
after the public keywords. If I had instead used T, I would
not have been required to use the leading <T>.
When I invoke a generic method, I may or may not have to
prefix it with the type of the object I am passing in:
Box b;
b.print(args); // always works
b.print(args) // usually works
If you do not prefix the method call with the type of the
object you are passing as an argument, then the java compiler
will attempt to use type inference to determine the type
of the parameter. Usually this will be successful. If the
java compiler cannot determine the type and gives you an
error message, then you will have to explicitly prefix the
method with the type of the object you are passing to it.
iii. Bounded Type Parameters: Sometimes you will want to
guarantee that a type implements a certain interface. For
example, a sort method wants to have certain comparator
methods defined, such as equals and lessThan. You can
require a type to implement either an interface or class
using the keyword extends:
public <V extends Comparator> void sort(V data[]){...}
2. C++ provides templates
a. Disadvantages
i. the code is very complicated to write, to debug, and to
understand
ii. a copy of the code must be created for each different type
b. Advantages: Supports both generic data structures and generic
functions
3. C provides void *'s
a. The programmer can create generic data types by declaring values
to be of type "void *"
b. The programmer can pass in type-specific functions (e.g.,
comparator functions) that take void *'s as parameters and
that downcast the void *'s to the appropriate type before
manipulating the data
c. Advantages
i. One copy of the code works with multiple objects
ii. The approach supports both generic data structures and
generic algorithms--Note that this approach cannot be used
to support generic algorithms in Java since functions cannot
be passed as pointers
d. Disadvantages
i. Downcasting can be dangerous, even more so than in Java,
since run-time type checks are not performed in C
ii. The code often has a cluttered appearance (note how your
jval code looks)
3. Both Java and C++ provide a set of generic data structures.
a. C++ provides the standard template library (STL), which includes
both templates for common data structures, such as lists and
hash tables, as well as common algorithms, such as quicksort.
b. Java provides a set of standard data structures, such as lists
and hash tables, in its java.util package.