Developer

Proposed Java generics specification could make coding safer and more efficient

Generics is one of the specifications being considered for J2SE 1.5. This scheme can eliminate the need for runtime type checking by moving that function to compile time. This fundamental change will make Java applications more secure.


Sun plans to improve Java 2 Standard Edition (J2SE) in its next release with one main goal—ease of use. In that direction, J2SE 1.5 (Tiger release) is the next significant step; its beta version is scheduled to be released in late 2003.

J2SE 1.5 proposes to introduce seven major language changes: generics, enhanced-for loop ("foreach"), autoboxing/unboxing, typesafe enums, varargs, static import, and metadata.

Many JSRs are being evaluated for Tiger release
Sun is evaluating various Java Specification Requests (JSRs) for the Tiger release. Generics and its possible addition are being discussed at JSR 14. We'll focus here on the new generics scheme and how to use it effectively.

Advantages of proposed generics specification
One of the foremost advantages of generics is that it removes need for runtime type checking. The type checking currently present in the language is limited because the compiler knows only so much. This limitation is more evident when developers encounter runtime exceptions because of wrong casting.

A generics-enabled compiler will bring down the type-checking formerly done at runtime to be performed at compile time. In the process, the code becomes safer to use.

The other advantage to developers is that there is no need to write extra classes (such as template classes in C++) to support generics. In this new scheme, there are only minor differences to the existing way of making class or interface declarations. You will see how in the discussion and sample programs below.

How does the proposed generics scheme work?
The generics scheme introduces a new syntax of angle brackets < >. These angle brackets are present next to the names of classes being declared. Here's a comparison of the current way and the generics way of class declarations:
class Account              // existing way of class declarations
{
  /* details */
}

class Account<xxx>        // generics way of class declarations
{
  /* details */
}


In the above example, xxx is included inside angle brackets and can be any identifier of your choice. The angle brackets syntax can also be used when declaring methods, which I will discuss later in the article. Note that only class types or interface types can be parameters; primitive types can't be. Now, let's look at generics in detail, point by point.

Generics and types
The new generics scheme introduces two fundamental concepts to the language:
  • Parameterized type: A parameterized type is simply the class/interface type declaration with angle brackets after its name. For example, Account<xxx> is a parameterized type. In this context, you will see a new term being used by programmers: type parameter section. The type parameter section is nothing but the angle brackets along with their contents. For instance, <xxx> would be termed a type parameter section.
  • Type variable or parameters: Type variables or parameters are the contents of the angle brackets. For example, xxx is a type variable. If the angle brackets contain more than one type variable, those variables are conventionally separated by commas. Note that type variables are unqualified identifiers.

In Listing A, Account<NUM> is a parameterized type, and NUM is a type variable.

Notice how the class is declared in the listing. The type variable NUM (also called as parameter ) is referred everywhere inside the class. Also notice the main() method. It shows the legal and illegal ways of instantiation of the parameterized type Account<NUM>.

In the main() method in Listing A, the object named objAcc is created by passing the actual parameter Integer inside the angle brackets. As soon as you create an object like this, all the generics-enabled methods and constructors inside the parameterized class (Account) are going to obey the rule of generics. They all start expecting Integer inside the class by mapping it to the formal parameter NUM.

That is why the method call objAcc.setAccountNum(new Float(2222.22f)); becomes a compilation error. Even the constructor supports generics. You could not do this:
objAcc = new Account<Integer>(new Float(1111.11));

You may be thinking that the rules of generics look pretty strict. Is there any flexibility at all—at least a little bit more? The answer is yes. You can specify what is called as bounds in the type parameter section.

Generics and types with bounds
The rules of type safety are a little more flexible with bounds. Bounds specifies inheritance relationships inside the type parameter section. In other words, a type parameter can extend an existing type. Listing B shows how this is done and how the compiler ensures type safety in the client class. Here, the definition of the Account class in the previous section is modified to look like this:
class Account<NUM extends Number>

Note that this Number is the regular abstract class from JDK (java.lang.Number).

After this class definition, the compiler will catch any attempts to instantiate the Account class with actual type parameters other than java.lang.Number type. You will not be able to compile Account<String> objAcc = new Account<String>(new String("1111")); because java.lang.String does not inherit from java.lang.Number.

One more variation is possible in the type parameter section: Parameterized declarations can also be nested inside it. Listing C provides an example. There, the class declaration:
class Account<INTERNAL_NUM extends AlphaNumericNumber,
   MAIN_NUM extends INTERNAL_NUM>


shows nested type parameter declarations.

Generics and method declarations
Method declarations can have a type parameter section just as classes/interfaces can. The only difference between the two is that the type parameter section precedes the result type of the method, whereas in classes/interfaces, it follows the name of class/interface. Such methods are called as generic methods.

For example, in Listing D, the static method declared in the GenericsTester class is a generic method whose formal argument type appears in the method's type parameter section. The advantage of having this generic method is not explicit in this case, but if you observe closely, you'll note that it indirectly puts a requirement on the Account class to behave as deductible (by having it implement the necessary interface). You can verify this by removing implements deductible in the Account class declaration. When you do, the GenericsTester class will not compile.

Generics and constructor declarations
Type arguments to a generics constructor are governed by the same rules as those associated with a generics method. The type parameter section also precedes the constructor name.

Generics and polymorphism
Overloading: A class may not have two methods with the same name and the same argument types. Generics does not change this current rule and adds its own flavor to the definition of same argument types. Generics says that any two methods have the same argument types if both of them have the same number of type parameters with the same bounds.

Overriding: An overriding method has the same return type, method name, and number and types of parameters as the overridden method. Generics relaxes the rule just a little bit. It says that the return type of an overriding method may be a subtype of the result types of all the methods it overrides.

Generics and integration with exceptions
Generics says that type variables are not allowed in catch clauses, but they are allowed in throws lists. For example, the debit() method below can throw an exception of parameter type EXCP:
abstract class Account<EXCP extends BalanceThresholdViolatedException> {
  void debit() throws EXCP;
}


Generics and changes necessary to construct expressions
Class instantiation expressions: When creating instances, a programmer uses the fully parameterized class name followed by the arguments expected by the constructor. For example:
Account<Integer> objAcc = new Account<Integer>(new Integer(1111));

Array creation expressions: Parameterized arrays can also be created using the fully parameterized name, like this:
Account<Integer>[] arrAcc = new Account<Integer>[100];

Cast expressions: The syntax of casting remains unchanged. When used with generics, casting is still easy to read. Listing E provides an example:

In main() method in this listing, try swapping CheckingAccount<Integer> with CheckingAccount<Float> when creating objCAcc. The compiler will immediately generate an error at the last statement that does casting.

Type checking
Additional information
If you are a registered member of the Sun Developer Network, you can download the early access J2SE 1.5 compiler.

The proposed generics specification moves much of the runtime type checking to compile-time type checking. In doing so, it does not require you to write any extra code in terms of new classes or interfaces or templates.

Editor's Picks