Generics in Java Compared to C++ Templates

Summary

In this post, I will introduce Generic in Java. The post may be useful when you are working with Java with prior knowledge of C++.

Context

The context is that I want to define an AbstractBaseClass which is a top-level abstract class but different subclasses require different types of arguments for some member functions.

With the knowledge of C++, we know that things can be done through class template and specialization (refer to my another post)

Conclusion

As usual, we provide the conclusion first.

  1. Java is not truly Generic as C++ is. Java pretends to be generic leveraging Java compiler.
  2. In Java, there is no concept of specialization, but we can use inherence to implement specialization.
// Base.java
public class Base<T> {
    public abstract void foo(T a);
}
// Derived.java
public class StringDerived extends Base<String> {
    public abstract void foo(String a) {
        System.out.println(a);
    }
}

Details

Let’s dive deeper to see what happens in Java’s Generics.

1. Type Erasure

The Type Erasure is the main technique under Java’s Generics. The main characteristic of the technique is the elimination of the parameterization types during source-code translation to JVM bytecodes. See the codes below.

Vector<Integer> iv = new Vector<Integer>();
Vector<String>  sv = new Vector<String>();

// correct
iv.add(new Integer(1));
sv.add(new String("abc"));
Integer ival = iv.get(0);
String  sval = sv.get(0);

// illegal, could not compile
sv.add(new Integer(1));
iv.add(new String("abc"));

It will be translated into the following codes by Java Generics compiler.

Vector iv = new Vector();
Vector sv = new Vector();

iv.add(new Integer(1));
sv.add(new String("abc"));

Integer ival = (Integer) iv.get(0);
String  sval = (String) sv.get(0);

We found that all instantiations of the parameterized class Vector<> are replaced with its nongeneric alternative Vector.

The good thing is the compiler helps programmers to do the type checks and casts. And the compiler finishes them better and safer. And all parameterized types share the same class or interface at runtime.

2. Is Instance

Consider the following codes.

// C++
bool test() {
    vector<int>    intv;
    vector<string> strv;
    // return false
    return typeid(intv) == typeid(strv);
}
boolean test() {
// Java
    Vector<Integer> intv;
    Vector<String>  strv;
    // return true
    return intv.getClass()  == strv.getClass();
}

The C++ test correctly returns false and the Java test yields true. It shows that parameterization in Java is literally “skin-deep” (or rather, compiler-deep). In C++, we know that class templates are templates that generate different classes.

3. <T extends Base>

Java Generics has a convenient <T extends C> construct to restrict T parameterization only for Base-derived types. In C++, people have to implement it manually.

template<class T>
void foo(T const & inst) {
    C const* test = &inst;
}

4. Limitations

There are some notable limitations in Java’s Generic.

  1. Java Generics does not allow primitives (int, float, double, and so on) as parameterization types, though instead, people can use class Integer, Float, etc.
  2. Java Generics does not allow specialization of a generic class.
  3. Java Generics does not have run-time type information for parameterized types. Since those types do not exist beyond the source code, which makes things complicated. See the codes below.
// not valid in Java
class Allocator<T> {
    public T allocate() { 
        return new T(); 
    }
}
// // not valid in Java
class Foo<T> {
    public void foo(T inst) {
        inst.do_something();
    }
}

To achieve this, you have to use a Factory model for the Allocator example and impose unnecessary polymorphism in the Foo example (like implement a specialized subclass).

Reference

This Post at Dr.Dobb’s

Leave a Reply

Your email address will not be published. Required fields are marked *