Lecture 4, 2/6: Generics

DJW pp 166-167.

Generics allow you to, essentially, pass a class as an argument.

Generic classes

You can define a class with one or more generic argument.

MyList.java

Singly linked list of values of type T.
Each object contains

Methods in this class can then have variables or return values of type T MyList < T > etc.

TestMyList.java

A method that uses this calls MyList with a type argument which is a particular class.
Here MyList < String > , MyList < Integer > .

ListOfLists.java Class functions can be embedded.

Note:

Generic classes used in Methods

GenericApplier.java

Wrappers

A primitive type --- boolean, byte, char, double, float, int, long, short --- cannot be used as a argument for a generic type variable.

E.g. if you write Applier < int,int > you will get a compiler error.

So Java provide wrapper types, which are the same things as objects: Boolean, Byte, Char, Double, Float, Integer, Long, Short. These can be used as arguments to generics.

Java handles most of the back and translation between the primitive class and the corresponding wrapper automatically. For instance, you can write:

   int i = 3;
   Integer j = i;
   int k = j;

Since the second statement is creating an object, strictly speaking you should write:

   Integer j = new Integer(i);
like you would with other classes. (I think in early versions of Java, you did have to write this.) But Java figures out what you mean.

Wrapper classes are immutable, meaning i that you cannot change the value, you can only create a new object. For instance in the code

    Integer i = 3;  // Creates a new object.
    Integer j = i;  // j is a reference to same object as i (I think).
    Integer i = 5;  // Creates a new object. Value of j is unchanged.

Various important library functions associated with the type are provided as static methods associated with the wrapper type. E.g. Integer.parseInt(S) converts a String like "103" to the Integer or int 103.

Question: Why does Java bother with the primitive types at all? Why not just use the wrapper types? (Some languages do only use what corresponds to the wrapper types; e.g. some versions of LISP.)






Well, it's not called a wrapper for nothing. A wrapper class is like one of those big packages containing a small actual thing, with the rest filling will wrapping material. If I is an int then it uses 1 word (32 bits) of memory. If I is an Integer then it uses at least 3 words, possibly more: So it's a huge waste of space; but there is an even huger waste of time. Suppose you have a for loop;
   for (int I = 0; I < N; I++) { ... } 
The increment operation is just doing an arithmetic add. (If I is allocated in memory, this is three machine instruction -- fetch, add, load. If, as is more likely, I is in a register, then it is a single machine instruction.)

Now if you rewrite this using an Integer

   for (Integer I=0; I < N; I++) { ... }
it will do the same thing, but then the increment operation will involve the following steps:
  1. Look up the reference of I (pointer following).
  2. Look up the value;
  3. Add 1 to the value;
  4. Call memory management to get you an new Integer object, since an Integer is immutable. This involves a whole bunch of steps that you will learn in CS 201.
  5. Load the value into that new object.
  6. Save a reference to the new object as the value of I.
At some point, the memory management system has to recycle the old, discarded object once it is sure that no active data structure contains a reference to it.

Anomaly

A list of integers is not a list of objects.
MyList< Integer > LI = new Mylist< Integer >();
MyList< Object > LO = LI;   //  Compiler does not allow!
LO.setValue("Hello");
The underlying problem is that there are two views of ``B is a subclass of A''.

More about Generics

We will talk about more ways of dealing with generics as they come up over the course of the semester.