Generic programming is the ability to abstract away concrete types and was first introduced in the 1970s. It facilitates code maintenance, reduces redundant code, and enables optimizations. Rather than writing the same algorithm again and again, but with different data types each time, the programmer only needs to write it out once. Eg, defining
size_t sum(T) rather than three definitions for
size_t sum(long), and
C++ allows generic programming via templates, which were included in the 1998 standard but had appeared years earlier in The Annotated C++ Reference Manual. Java allows generic programming via generics, which was introduced with Java 5 in 2004.
Two Different Paradigms
They might look similar on the surface but have contrasting design paradigms. C++ templates uses code generation whereas Java generics uses type erasure.
Type erasure removes the type of an object and adds a cast in the compiled bytecode. It doesn’t actually add anything new to the language. For example, as described in Java Generics and Collections, the bytecode of:
List words = new ArrayList(); words.add(“Hello”); String s = (String) words.get(0);
List<String> words = new ArrayList<>(); words.add(“Hello”); String s = words.get(0)
are identical! This is curious, if the syntax is so different, why would the Java committee decide they should output the same bytecode? After all, it seems like inserting a cast and removing the type would prevent potential optimizations.
The alternative is for generics to introduce different bytecode. However, code that uses generics and compiled with Java 5 would no longer be compatible with code compiled written without generics before it. Businesses that were already using Java would be hesitant to migrate to the newer version. Type erasure was a compromise between business needs and language enhancements.
Type erasure also ensures that there would only ever be one implementation of a generic type. For example,
List<MyClass> all have the same implementation of List. Contrasting this with C++ templates, which was included in the standards from the beginning and uses code generation,
vector<MyClass> each has its own implementation. Although this may increase the size of each translation unit and the final executable, implementation-specific types offer the programmer greater flexibility, including:
- Specialization of template functions and classes to enable optimizations. An example is vector<bool>, which is a space-efficient version of
- Creating member variables of the same type. In Java, type erasure makes is almost impossible to instantiate a variable of the type, such as
T t = new T; In addition, static member variables can’t refer to a generic type
- Static polymorphism, such as STL containers and using typedefs to make generic operations, such as using iterators
- A lot of complicated stuff like policy traits and meta-programming, which can compute prime numbers at compile-time!
The impact of code generation vs type erasure affects the performance and readability of the languages. While templates offer more flexibility to the programmer, they’re harder to use because of tricky rules that cause inconvenient bugs. Also, nothing’s quite as dry as reading cryptic error messages that span hundreds of characters.
To briefly summarize, C++ and Java have different models to enable generic programming. C++ uses templates and code generation whereas Java uses generics and type erasure.