Understanding Generics

In the previous article, we learned about the basics of generics and its usage. In this article, we’ll go one level down and understand the semantics of generics and how compiler handles generics code.

To quickly recap, generics provides compile-time safety. When we provide the type information in the angle brackets (< … >), the compiler will use this type of information to make sure that we do not put objects with different type into the collection. This safety check happens at compile time only.

Generics can also be used for method parameters for the type safety of arguments, and also generics can be used for return types. The following quick example summarizes the generics usage in method parameters and in return types.

void processMyStrings(List<String> listOfStrings) {
listOfStrings.add(“anotherString”); }

The above examples shows the type safety for method arguments. The compiler will not accept any value other than String to be inserted into the listOfStrings.

public List<String> getMyStrings() {
List<String> myList = new ArrayList<String>();
//…
return myList; }

The above example shows the type safety for method return type. Again, the compiler will not accept any list as return type which does not contain String objects.

So far, we have only used predefined Java types (for example String) with generics. We can also use any custom class types in the generics. For example, the following code snippet creates an ArrayList of Employees. This list accepts any object which is of type Employee and no other type. If we try to insert any other type into this list, it will give compile time error.

List<Employee> employees = new ArrayList<Employee>();

Because we have defined our employees list with generics of type Employee, compiler will only accept Employee objects to be inserted into the list.

Employee e1 = new Employee(‘John’, ‘Doe’);
Employee e2 = new Employee(‘John’, ‘Smith’);
employees.add(e1);
employees.add(e2);

Suppose, we want to retrieve the first employee object from the employees list, the following code will work perfectly without a cast.

Employee firstEmployee = employees.get(0); // This will surely give us an instance of Employee.

The instance firstEmployee is of type Employee, an explicit cast is not required.

Generics and Backward Compatibility

Since explicit cast is not required when we use generics to store objects of any type into Collections, in some cases, we may have non-generic code which does the type cast. One of the design aspect of generics is to support legacy code (which is non-generics code). In this kind of situations, the explicit cast will not do any harm. The following example will do a cast on type safe collection.

List<Integer> myNumbers = new ArrayList<Integer>();
myNumbers.add(1);
myNumbers.add(2);
Integer myNumber = (Integer)myNumbers.get(1);  // This is fine, the explicit cast won’t hurt though it is not required.

One important point to remember about generics is that, JVM (Java Virtual Machines) has no idea about type safety of our collections. This type safety information is not available at runtime. We are providing generics along with type information only to the compiler, so it can enforce appropriate type at compile time itself.

The compiler will remove all type information just before converting the source code to byte code (and it may insert explicit casts if necessary). Eventually compiled byte code will look exactly same like non-generics code, though we have provided type safe information to the compiler. This process is called ‘type erasure’. That means, generics type safety information is not available in the already compiled byte codehence not available at runtime, it is only meant for compile time safety.

The reason behind not having type safety information at runtime is simple: to provide support for legacy code. Since it is practically impossible to modify existing code to convert to generics, the type safety check happens only at compile time for the newly developed code. The legacy (or non-generics code) will be executed because at runtime all the code is ultimately same.

As a matter of fact, we do not need runtime safety until and unless we mix generic and non-Generic code. In such scenarios compiler will issue a warning as shown below.

Javac MyEmployeeProcessor.java

Note: MyEmployeeProcessor.java uses unchecked or unsafe operations. Note: Recompile with –Xlint:unchecked for details.

If we recompile with –Xlint:unchecked, we’ll get to see the exact error which does potentially unsafe operation.

As we mentioned above, when we mix generic and non-generic code, the compiler will gives us the warning and warnings are NOT compiler failures. It is developer’s responsibility to treat warnings appropriately and change code if required. The reason for this warning is that, compiler suspects that there might be a chance that an object of wrong type be inserted into the collection.

Understand more about Generics in Whizlabs OCPJP 6 Training Course.

About Aditi Malhotra

Aditi Malhotra is the Content Marketing Manager at Whizlabs. Having a Master in Journalism and Mass Communication, she helps businesses stop playing around with Content Marketing and start seeing tangible ROI. A writer by day and a reader by night, she is a fine blend of both reality and fantasy. Apart from her professional commitments, she is also endearing to publish a book authored by her very soon.
Scroll to Top