|
Level: Intermediate
Neal Ford (nealford@nealford.com), Application Architect, ThoughtWorks
18 Oct 2005
Java™ 5 offers generics support, a feature developers requested for years. It represents a significant upgrade to the Java programming language. With something as complex as generics also comes challenges, both for tool vendors and developers. This article highlights how Eclipse has responded and the changes wrought by generics to the Java language. It shows how to take full advantage of generics within Eclipse, including support in Quick Assist, Quick Fix, refactoring, and project preferences. It also shows some subtle and important aspects of a fully generic language.
Generics in Java
The creators of Java technology have talked about adding generics support to the language almost since the first version. C++ supports generics through the Standard Template Library, but the implementation is hampered by the lack of a single unified parent class for all other classes (embodied in the Object class in Java). The generics support in the Java programming language is the most significant syntax change in its history. Tools support has proceeded more slowly than for other SDK upgrades for obvious reasons. Now, though, you have excellent support for these new language features in Eclipse V3.1. This article highlights some of these new features.
Java 5 Projects
To turn on generics support in Eclipse V3.1, you need Java 5 on your machine, which you can download from the usual places. The generics support comes along with project properties in the compiler settings pages. That means you can have separate SDK settings per project, just as in the past. To create a project that uses generics, you have to specify the language level during the creation of the project or through the project properties of an existing project.
Two specific property pages pertain to Java 5 settings. The first specifies compiler settings. Figure 1. Compiler-specific settings for Java 5 support
Unless you have set the default project settings in Eclipse for Java 5, you will need to override those settings for this project. The JDK compliance section allows you to determine the settings for source and class files. When setting level 5.0 for source files, you get a slew of new Content Assist and refactoring options.
The other pertinent properties dialog is the Errors/Warnings section in the tree view. Figure 2. The Errors/Warnings section of project properties
The wealth of J2SE 5 options control what kinds of errors and warnings (see Table 1) Eclipse will generate for your Java 5 code.
Table 1. Errors and warnings Eclipse will generate for Java 5 code
J2SE 5 options |
Type of warning |
---|
Unchecked generic type operation |
The compiler will issue an error or warning whenever it encounters an unchecked generic type operation. This includes operations on types like List or ArrayList without a type specified. This provides a warning any time you use an old-fashioned Collection class that holds objects. |
Generic type parameter declared with a final type bound |
The compiler will issue an error or warning whenever it encounters a type bound involving a final type. Consider this sample method signature:
public int doIt(List<? extends String> list)
Because String is final, the parameter cannot extend String , so it is more efficient to write it like this: public int doIt(List<String> list) |
Inexact type match for vararg arguments |
The compiler generates a warning when it cannot exactly determine the developer's intent from a varargs parameter. There are some varargs involving arrays that are ambiguous. |
Boxing and unboxing conversions |
Warns about automatic boxing operations, which may impact performance, and breaks assumptions about object identity for type wrapper objects. This is a minor warning ignored by default. |
Missing @Override annotation |
You should include the @Override annotation for any overridden method. A missing annotation may indicate that the developer didn't realize the method was being overridden. |
Missing @Deprecated annotation |
Warning about leaving off the @Deprecated flag. |
Annotation is used as super interface |
You shouldn't use the Deprecated class as a super interface. For example, this is frowned upon:
public interface BadForm extends Deprecated {
} . |
Not all enum constants covered on switch |
Missing enumerations on a switch statement, meaning that you may miss some enumerated options. |
Unhandled warning tokens in @SuppressWarnings |
Java 5 allows you to add annotations to suppress warnings from the compiler. If you misspell the warning or use a warning that doesn't exist, this flag will issue a warning. |
Enable @SuppressWarnings annotations |
Turns on the ability to programmatically (in code) suppress warnings you don't care about. |
Once you have all your project options set to your liking, you can start using generics in Eclipse.
Porting from Specific to Generic
Consider this simple class in Listing 1, which creates a list of Employee and Manager objects (Manager extends Employee ), prints them, gives them a raise, then prints them again. Listing 1. The HR class
package com.nealford.devworks.generics.generics;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class HR {
public HR() {
List empList = new ArrayList(5);
empList.add(new Employee("Homer", 200.0, 1995));
empList.add(new Employee("Lenny", 300.0, 2000));
empList.add(new Employee("Waylon", 700.0, 1965));
empList.add(new Manager("Monty", 2000.0, 1933,
(Employee) empList.get(2)));
printEmployees(empList);
System.out.println("----- Give everyone a raise -----");
for (int i = 0; i < empList.size(); i++)
((Employee) empList.get(i)).applyRaise(5);
printEmployees(empList);
System.out.println("The maximum salary for any employee is "+
Employee.MAX_SALARY);
System.out.println("Sort employees by salary");
Collections.sort(empList);
printEmployees(empList);
System.out.println("Sort employees by name");
Collections.sort(empList, new Employee.NameComparer());
printEmployees(empList);
System.out.println("Sort employees by hire year");
Collections.sort(empList, Employee.getHireYearComparator());
printEmployees(empList);
}
public void printEmployees(List emps) {
for (int i = 0; i < emps.size(); i++)
System.out.println(emps.get(i));
}
public static void main(String[] args) {
new HR();
}
}
|
If you turn on Java 5 support, a variety of warnings will light up for this code.
Quick Fix features
The Quick Fix feature in Eclipse appears as a lightbulb in the editor gutter anytime Eclipse wants to suggest an improvement to your code. In the code from Listing 1, you will see a variety of Quick Fixes. Figure 3. The Quick Fix lightbulbs indicate potential improvements to your code
Quick Fix provides a lightbulb and a wavy yellow line indicating improvements. If you roll over the wavy yellow line, you will see the suggestion that appears in Figure 4. Figure 4. The Quick Fix indicates what should be generified
The Quick Fix suggestion listed here is just that -- a suggestion. The lightbulbs in the gutter are offered to add a local variable for the return of the add() method of List , which returns a boolean, which is ignored, in this case.
To address the Quick Fix suggestion, move to the refactoring menu. A few refactorings in Eclipse directly relate to generics in Java 5. The "Infer Generic Type Arguments" refactoring will add generics support to the list. The first dialog allows you to pick options. Figure 5. Infer Generic Type Arguments choices dialog
The first option relates to the inference that the clone() method will return the receiver type, not another (related class). Well-behaved classes generally observe this rule; uncheck this box if you know that your class doesn't. The second option, when unchecked, will leave the "raw" (nongeneric) parameter in place, rather than infer the correct generic parameter type.
As in most refactorings in Eclipse, you can preview what changes will occur in your class. The Preview button on this dialog yields the dialog shown in Figure 6. Figure 6. Preview the generic refactoring
The updated code looks like this: Listing 2. Updated code
List<Employee> empList = new ArrayList<Employee>(5);
empList.add(new Employee("Homer", 200.0, 1995));
empList.add(new Employee("Lenny", 300.0, 2000));
empList.add(new Employee("Waylon", 700.0, 1965));
empList.add(new Manager("Monty", 2000.0, 1933,
empList.get(2)));
|
Two interesting changes have occurred to the code. First -- and most obvious -- the List and ArrayList declarations are now generic for Employee types. The second -- more subtle -- change is in the last line. If you look at the original empList addition of the Manager class, it requires a typecast to an Employee for the Assistant field in the last parameter. The Infer refactoring was smart enough to remove the now unnecessary type cast.
Before leaving Quick Fix, there is one other aspect that is interesting in the Java 5 support added by Eclipse: You can now receive suggestions for supplying annotations for methods, such as @Override . You also have Content Assist for annotations. Figure 7. Quick Fix and Content Assist extends to annotations
Quick Assist features
Eclipse V3.1 has added a Quick Assist to facilitate generics support in Java 5. Consider this mundane for() loop, found in the printEmployees() method shown in Listing 3. Listing 3. The for() loop
public void printEmployees(List<Employee> emps) {
for (int i = 0; i < emps.size(); i++)
System.out.println(emps.get(i));
}
|
Along with support for generics, Java 5 now supports a for...each loop . Quick Assist offers to change this for loop into a for...each , which yields the code shown in Listing 4. Listing 4. The for...each loop
public void printEmployees(List<Employee> emps) {
for (Employee emp : emps)
System.out.println(emp);
}
|
This version is much cleaner because it eliminates the i variable and the call to get() entirely.
Generic types
Eclipse V3.1 has also expanded its support for type operations to extend to generic types. This means:
- Generic types can be safely renamed.
- Type variables can be safely renamed.
- Generic methods can be extracted from or inlined into generic code.
- Code Assist can automatically insert appropriate type parameters in parameterized types.
The search facilities in Eclipse have also gotten more intelligent with generic types. Consider this: Listing 5. Generic types
public void doEmployeeAnalysis() {
List<Employee> empList = new ArrayList<Employee>(5);
List<Date> hireDates = new ArrayList<Date>(5);
List<Integer> departments = new ArrayList<Integer>(10);
List<? extends Employee> allMgrs = new ArrayList<Manager>(5);
. . .
|
If you highlight the first List<Employee> declaration and select Search > References > Project, Eclipse will show you all the list declarations. Figure 8. Eclipse has gotten smart about finding generic references
You can also filter these results via a well-hidden feature of the Search window. If you access the Search window menu (in the right-hand corner, beside the minimize and maximize buttons), you get generics-aware filtering options. Figure 9. The filter menu of the search window allows you to filter generics-aware results
If you filter the results using Filter Incompatible, it eliminates the two entries that are not Employee -based. The results are shown in Figure 10. Figure 10. Filter Incompatible filters out the non-Employee related entries in the search window
Digging deeper in generics
Unfortunately, Eclipse cannot solve all your generics problems. In fact, sometimes refactorings yield syntactically correct code that is not semantically correct for the problem you are trying to solve. Ironically, life was easier in the pre-generics days because you had to pass everything as generic collections of objects. Now you must be careful to pass the right type of collection.
Consider this example. In the HR application, you add a method that determines retirement age for employees. However, the age of the Employee comes from the Employee super class: Person . Writing a method that takes just employees would work in this one instance, but you do not want to restrict the application to work only with employees. What if you need to trace former employees (as Persons ) in the future?
The solution to this problem lies with flexible generic parameters. Consider the code in Listing 6, which accepts any class that extends Person . Listing 6. Sample SELECT statement
public List<Person> empsOverRetirementAge(
List<? extends Person> people) {
List<Person> retirees = new ArrayList<Person>(1);
for (Person e : people)
if (e.getAge() > 65)
retirees.add(e);
return retirees;
}
|
This method accepts any subclass of Person and is, therefore, more flexible. You should keep this in mind when using generics. In this case, the generic is actually more specific (they should have called this language feature "specifics," anyway). Carefully identifying your parameter types allows you to achieve the same level of flexibility in your code that you had prior to generics, but with the added type security that generics provide.
Summary
Generics support is a huge enhancement to the Java programming language, necessitating a long time for tool vendors to catch up. Now that you have good tool support, you should start taking advantage of this advanced language feature. It makes code more readable -- because you eliminate type casts -- and allows the compiler to do more work on your behalf. Anytime you can get compilers and other tools (like IDEs) to perform more work, it means less work for you.
Resources Learn
Get products and technologies
- Innovate your next open source development project with IBM trial software, available for download or on DVD.
Discuss
About the author
|
|
Neal Ford is an application architect at ThoughtWorks, an IT professional services company. Neal designs and develops applications, instructional materials, magazine articles, courseware, video/DVD presentations, and wrote the books Developing with Delphi: Object-Oriented Techniques, JBuilder 3 Unleashed, and Art of Java Web Development. His primary consulting focus is the building of large-scale enterprise applications. He has also been a speaker at developer conferences. |
|