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.
- Java is not truly Generic as C++ is. Java pretends to be generic leveraging Java compiler.
- 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.
- Java Generics does not allow primitives (int, float, double, and so on) as parameterization types, though instead, people can use class Integer, Float, etc.
- Java Generics does not allow specialization of a generic class.
- 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).