Previous column

Next article


Some Examples of Generics in Java 1.5 and C# 2.0

Dr. Richard Wiener, Editor-in-Chief, JOT, Associate Professor of Computer Science, University of Colorado at Colorado Springs

 

space COLUMN

PDF Icon
PDF Version

The forthcoming releases of Java JDK v1.5 and C# v2.0 support generic classes (classes with generic type parameters) and generic methods. Furthermore, each also supports constrained generic types.

Much has been written about generic types. In “A Comparative Study of Language Support for Generic Programming” by Garcia et al (http://www.osl.iu.edu/publications/pubs/2003/comparing_generic_programming03.pdf), details of generic types and programming in C++, Haskell, Standard ML, Eiffel, Java and C# are compared. Useful details and generic coding examples from the soon to be released Java JDK 1.5 (now in beta) are presented in the paper “Generics in the Java Programming Language” by Gilad Bracha. Some of the examples used in this column were inspired by this paper.

Generic types in Java and C# introduce more expressiveness at the source code level and move type checking from run-time to compile-time when inserting objects into generic collections. In the current pre-generic versions of Java and C#, genericity in collections is obtained through the backdoor of using the universal super type Object as a polymorphic placeholder for the actual reference type that defines the objects to be inserted into the collection. Other than programmer comments, there is little in the source code that reveals the type of object that the programmer intends to hold in the collection. Only by carefully reverse-engineering the source code does this become apparent if in fact only a single type is inserted into the non-generic collection. Generic collections in both Java and C# provide useful self-documentation in the source code in addition to strengthening compile-time type checking.

Java and C# use invariant generic typing in contrast with Eiffel which uses covariant typing. In Java and C# a List<String> is not a subtype of a List<Object>. These are considered separate stand-alone classes. In Eiffel, a List<B> would be considered a subtype of a List<A> if B conforms to A (B is a descendent of A or equal to A). Although covariant typing generally leads to more flexibility in the use of generics, it has been shown to allow code that is statically correct but fails at runtime (see “Type-Save Covariance: Competent Compilers Can Catch All Catcalls” by Mark Howard et al). Wildcards have been introduced into the generic lexicon of Java 1.5 to provide more flexibility in the absence of covariant typing.

The declaration Collection<T> myCollection declares myCollection as a collection with some unknown element type.

// Assume class Person has been defined elsewhere
Collection<?> myCollection = new ArrayList<Person>();
myCollection.add(new Object()); // COMPILE-TIME ERROR

The add command cannot be invoked on the myCollection object since the wildcard represents some unknown type. Since we do not know the type, we are not allowed to pass anything except the value null into add.

There is no direct equivalent to wildcards in C# generics but the same functionality can be achieved indirectly. We consider an example used in Gilad Bracha’s “Generics in the Java Programming Language.” Thanks go to Peter Sestoft (Professor in Information Technology at the Department of Mathematics and Physics, http://www.matfys.kvl.dk/ of the Royal Veterinary and Agricultural University, http://www.kvl.dk, in Denmark) for his thoughtful comments concerning the C# version of the implementation.

We wish to store and maintain a list of lists that contain elements that conform to an abstract class Shape (are of type Shape or a descendent of Shape). We present the Java solution first using wildcards and then show how, without wildcards, we can achieve the same functionality using generic C#.

Listing 1 – List of generic lists extending Shape in Java



Program output

In draw method of class Circle. x = 3 y = 0 radius = 5
In draw method of class Circle. x = 1 y = 4 radius = 10
In draw method in class Rectangle. x = 0 y = 0 width = 10 height = 20
In draw method in class Rectangle. x = 3 y = 4 width = 20 height = 30
In draw method in class Rectangle. x = 20 y = 40 width = 1 height = 2
In draw method in class RightTriangle. x = 0 y = 0 base = 10 height = 20
In draw method in class RightTriangle. x = 3 y = 4 base = 20 height = 30
In draw method in class RightTriangle. x = 20 y = 40 base = 1 height = 2
In draw method in class RightTriangle. x = 200 y = 400 base = 11 height = 21
In draw method in class RightTriangle. x = 78 y = 42 base = 17 height = 21

In outputHistory method.
In draw method of class Circle. x = 3 y = 0 radius = 5
In draw method of class Circle. x = 1 y = 4 radius = 10
In draw method in class Rectangle. x = 0 y = 0 width = 10 height = 20
In draw method in class Rectangle. x = 3 y = 4 width = 20 height = 30
In draw method in class Rectangle. x = 20 y = 40 width = 1 height = 2
In draw method in class RightTriangle. x = 0 y = 0 base = 10 height = 20
In draw method in class RightTriangle. x = 3 y = 4 base = 20 height = 30
In draw method in class RightTriangle. x = 20 y = 40 base = 1 height = 2
In draw method in class RightTriangle. x = 200 y = 400 base = 11 height = 21
In draw method in class RightTriangle. x = 78 y = 42 base = 17 height = 21

Discussion of Listing 1

The declaration,

defines the static field history as an ArrayList containing a List of Shape objects where Shape is a polymorphic placeholder for any conforming sub-type (Circle, Rectangle or RightTriangle).

Method drawShapeList first adds a new List, shapes, to history. It then uses the new for loop construct (C#’s foreach loop) to iterate through the shapes and send each shape the draw command (which produces output to the console).

Finally, method outputHistory uses two nested for loops to iterate through the lists and then for each list to iterate through the shapes contained within that list.

We next examine how to achieve the same functionality in generic C#.

Listing 2 – List of generic lists extending Shape in C#




Program Output

In draw method of class Circle. x = 3 y = 0 radius = 5
In draw method of class Circle. x = 1 y = 4 radius = 10
In draw method in class Rectangle. x = 0 y = 0 width = 10 height = 20
In draw method in class Rectangle. x = 3 y = 4 width = 20 height = 30
In draw method in class Rectangle. x = 20 y = 40 width = 1 height = 2
In draw method in class RightTriangle. x = 0 y = 0 base = 10 height = 20
In draw method in class RightTriangle. x = 3 y = 4 base = 20 height = 30
In draw method in class RightTriangle. x = 20 y = 40 base = 1 height = 2
In draw method in class RightTriangle. x = 200 y = 400 base = 11 height = 21
In draw method in class RightTriangle. x = 78 y = 42 base = 17 height = 21

In OutputHistory method
In draw method of class Circle. x = 3 y = 0 radius = 5
In draw method of class Circle. x = 1 y = 4 radius = 10
In draw method in class Rectangle. x = 0 y = 0 width = 10 height = 20
In draw method in class Rectangle. x = 3 y = 4 width = 20 height = 30
In draw method in class Rectangle. x = 20 y = 40 width = 1 height = 2
In draw method in class RightTriangle. x = 0 y = 0 base = 10 height = 20
In draw method in class RightTriangle. x = 3 y = 4 base = 20 height = 30
In draw method in class RightTriangle. x = 20 y = 40 base = 1 height = 2
In draw method in class RightTriangle. x = 200 y = 400 base = 11 height = 21
In draw method in class RightTriangle. x = 78 y = 42 base = 17 height = 21

Discussion of Listing 2

It is not possible in C# to declare history as a stand-alone field with a constrained generic parameter as was done in Java. It is necessary to embed the history field inside of a static generic class Consts that explicitly establishes the generic constraint through,

This class also contains the public methods DrawShapeList and OutputHistory. Each of these methods utilizes a generic parameter F that extends the constrained generic parameter E.

The private static method AddShapeList requires two downcasts as well.

From the three method invocations,

the DrawShapeList method is able to infer the parameter F (inner list type).

Clearly there is more complexity and subtlety associated with the generic C# implementation than the generic Java implementation.

Since generics in Java was designed to be totally compatible with the existing JVM (Java virtual machine), it offers no performance improvement over straight non-generic Java. One may in fact view Java generics as an extension to the allowable syntax of standard Java with the compiler translating generic code first to standard non-generic code. So the declaration,

Collection<Integer> is seen by the virtual machine as Collection<Object>.

Generics were designed into the .NET framework so List<int> (aka List<Int32>) does not translate to List<Object>. There is no wrapping and unwrapping overhead required when constructing collections of value types such as int in generic C#. C# uses code specialization (like C++) when implementing collections of value types and uses code sharing (like Java) when implementing collections of non-value types (ordinary reference types). A simple experiment was designed to enable performance comparisons to be undertaken so that the efficiency of C# generics could be compared with that of generic Java both for value types (primitive types in Java) and reference types. Listings 3 and 4 contain the code that was used in this simple experiment. In each experiment, four stacks of base-type integer (int in C# and Integer in Java) were constructed by pushing one million integer objects onto the first stack and then popping that stack while loading the contents of the first stack onto the second stack and repeating this process until the fourth and last of the stacks is loaded with a million objects and then the objects popped from this last stack. The same scenario was repeated only using String as the base type rather than integer objects. Since String is a reference type in both languages this second experiment allows us to see whether any significant differences exist between the performance on generic reference types versus value types.

Listing 3 – Benchmark for Java Generics



Program Output

sum = 1999998000000
Elapsed time for intStack = 2.11 seconds.
sum = 1999998000000
Elapsed time for strStack = 0.937 seconds.

Listing 4 - Benchmark for Java Generics


Program Output

sum = 1999998000000
Elapsed time for int stack = 0.921875 seconds.
Elapsed time for str stack = 0.78125 seconds.

Discussion of Listing 3 and 4

The experiments were conducted on the same computer. The ratio of execution times is interesting.

For integer types (primitive type in Java and value type in C#), generic Java was 2.29 slower than generic C#. That is significant. For reference types, generic Java was 1.20 times slower. That is not significant and is in part due to the overall efficiency of Java JIT compared with the C# JIT (other experiments have suggested that the C# JIT is slightly more efficient than the Java 1.5 JIT).

In conclusion, the design decision to use code specialization for value types in C# has paid off in performance benefits. The wildcard semantics in generic Java appear to provide a more straight-forward mechanism for expressing complex generic constructions than the equivalent C# implementation.

 

 

About the author



space Richard Wiener is Associate Professor of Computer Science at the University of Colorado at Colorado Springs. He is also the Editor-in-Chief of JOT and former Editor-in-Chief of the Journal of Object Oriented Programming. In addition to University work, Dr. Wiener has authored or co-authored 21 books and works actively as a consultant and software contractor whenever the possibility arises.

Cite this column as follows: Richard Wiener: “Some Examples of Generics in Java 1.5 and C# 2.0”, in Journal of Object Technology, vol. 3, no. 8, September-October 2004, pp. 81-96. http://www.jot.fm/issues/issue_2004_09/column8


Previous column

Next article