Generics in J2SE 5.0
Pages: 1, 2
Using Generic Types Without Type Parameters
Now that the collection types in J2SE 5.0 have been made
generic, what about legacy codes that used the same types?
Fortunately, they will still work in Java 5, because you can use
generic types without type parameters. For example, you can still
use the List interface the old way, as demonstrated by
the following part of the earlier GenericListTest.
List stringList1 = new ArrayList();
stringList1.add("Java 1.0 - 5.0");
stringList1.add("without generics");
String s1 = (String) stringList1.get(0);
A generic type used without parameters is called a raw type. This means that code written for JDK 1.4 and earlier versions will continue working in Java 5.
One thing to note, though, is that the JDK 5 compiler expects you to
use generic types with parameters. Otherwise, the compiler will
issue warnings, thinking that you may have forgotten to define type
variables with the generic type. For example, compiling this code
gives you the following warning, because the first List was used as
a raw type.
Note: com/brainysoftware/jdk5/app16/GenericListTest.java
uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
You have these options at your disposal if you do not want to get warnings when working with raw types:
- Compile with the
-source 1.4flag. - Use the
@SupressWarnings("unchecked")annotation. - Upgrade your code to use
List<Object>. Instances ofList<Object>can accept any type of object and behave like a raw typeList. However, the compiler will not complain.
Warning: Raw types are available for backward compatibility. New development should shun raw types--it is possible that future versions of Java will not allow them.
Using the ? Wildcard
I mentioned that if you declare a
List<aType>, the List instance
works with instances of aType, and you can store
objects of one of these types:
- An instance of
aType. - An instance of a subclass of
aType, ifaTypeis a class. - An instance of a class implementing
aType, ifaTypeis an interface.
Note, however, that a generic type is a Java type by itself, just
like java.lang.String or java.io.File.
Passing different lists of type variables to a generic type results
in different types. For example, list1 and
list2 below reference different types of objects.
List<Object> list1 = new ArrayList<Object>();
List<String> list2 = new ArrayList<String>();
list1 references a List of
java.lang.Objects and list2 references a
List of Strings. Even though
String is a subclass of Object,
List<String> has nothing to do with
List<Object>. Therefore, passing a
List<String> to a method that expects a
List<Object> will raise a compile-time error.
The following listing shows this.
package com.brainysoftware.jdk5.app16;
import java.util.ArrayList;
import java.util.List;
public class AllowedTypeTest {
public static void doIt(List<Object> l) {
}
public static void main(String[] args) {
List<String> myList = new ArrayList<String>();
// this will generate a compile error
doIt(myList);
}
}
This code won't compile, because you are trying to pass the wrong
type to the method doIt. doIt expects an instance of
List<Object> and you are passing it an instance of
List<String>.
The solution to this problem is the ? wildcard.
List<?> means a list of objects of any type.
Therefore, the doIt method should be changed to:
public static void doIt(List<?> l) {
}
There are circumstances where you want to use the wildcard. For
example, if you have a printList method that prints
the members of a List, you may want to make it accept
a List of any type. Otherwise, you would end up
writing many overloads of printList. The following
listing shows a printList method that uses the
? wildcard.
package com.brainysoftware.jdk5.app16;
import java.util.ArrayList;
import java.util.List;
public class WildCardTest {
public static void printList(List<?> list) {
for (Object element : list) {
System.out.println(element);
}
}
public static void main(String[] args) {
List<String> list1 = new ArrayList<String>();
list1.add("Hello");
list1.add("World");
printList(list1);
List<Integer> list2 = new ArrayList<Integer>();
list2.add(100);
list2.add(200);
printList(list2);
}
}
This code demonstrates that List<?> in the
printList method means a List of any
type. Note, however, that it is illegal to use the wildcard when
declaring or creating a generic type, like this:
List<?> myList = new ArrayList<?>(); // illegal
If you want to create a List that can accept any
type of object, use Object as the type variable, as in
the following line of code:
List<Object> myList = new ArrayList<Object>();
Using Bounded Wildcards in Methods
In the previous section, you learned that passing different type
variables to a generic type creates different Java types, despite a
parent-child relationship between the type variables. In many
cases, you may want a method to accept a List of
different types. For example, if you have a getAverage
method that returns the average of numbers in a list, you may want
to pass a list of integers, or a list of floats, or a list of another
number type. However, if you write List<Number>
as the argument type to getAverage, you won't be able
to pass a List<Integer> instance or a
List<Double> instance, because
List<Number> is a different type from
List<Integer> or
List<Double>. You can use List as a
raw type or use a wildcard, but this deprives you of type-safety
checking at compile time, because you can also pass a list of
anything, such as an instance of List<String>.
You could use List<Number>, but you must always
pass a List<Number> to the method. This would
make your method less useful, because you probably work with
List<Integer> or List<Long>
more often than you do with List<Number>.
J2SE 5.0 adds another rule to circumvent this restriction, by
allowing you to define an upper bound of a type variable. This way,
you can pass a type or its subtype. In the case of the
getAverage method, you may be able to pass a
List<Number> or a List of instances
of a Number subclass, such as
List<Integer> or
List<Float>.
The syntax for using an upper bound is as follows:
GenericType<? extends upperBoundType>. For
example, for the getAverage method, you would write:
List<? extends Number>. The following example
illustrates the use of such a bound.
package com.brainysoftware.jdk5.app16;
import java.util.ArrayList;
import java.util.List;
public class BoundedWildcardTest {
public static double getAverage(List<? extends Number> numberList)
{
double total = 0.0;
for (Number number : numberList)
total += number.doubleValue();
return total/numberList.size();
}
public static void main(String[] args) {
List<Integer> integerList = new ArrayList<Integer>();
integerList.add(3);
integerList.add(30);
integerList.add(300);
System.out.println(getAverage(integerList)); // 111.0
List<Double> doubleList = new ArrayList<Double>();
doubleList.add(3.0);
doubleList.add(33.0);
System.out.println(getAverage(doubleList)); // 18.0
}
}
Thanks to the upper bound, the getAverage method in
this listing allows you to pass a List<Number>
or a List of instances of any subclass of
java.lang.Number.
Lower Bounds
The extends keyword is used to define an upper
bound of a type variable. Though usable in very few applications,
it is also possible to define a lower bound of a type variable, by
using the super keyword. For example, using
List<? super Integer> as the type to a method
argument indicates that you can pass a
List<Integer> or a List of objects
whose class is a superclass of java.lang.Integer.
Writing Generic Types
The previous sections concentrated on using generic types, notably the ones in the Collections framework. Now it's time to learn to write your own generic types.
Basically, writing a generic type is not much different from
writing other types, except for the fact that you declare a list of
type variables that you intend to use somewhere in your class.
These type variables come in angle brackets after the type name.
For example, the Point class in the following is a
generic class. A Point object represents a point in a
coordinate system and has an X component (abscissa) and a Y
component (ordinate). By making Point generic, you can
specify the degree of accuracy of a Point instance.
For example, if a Point object needs to be very
accurate, you can pass Double as the type variable.
Otherwise, Integer will suffice.
package com.brainysoftware.jdk5.app16;
public class Point<T> {
T x;
T y;
public Point(T x, T y) {
this.x = x;
this.y = y;
}
public T getX() {
return x;
}
public T getY() {
return y;
}
public void setX(T x) {
this.x = x;
}
public void setY(T y) {
this.y = y;
}
}
In this example, T is the type variable for the
Point class. T is used as the return
value of both getX and getY, and as the
argument type for setX and setY. In
addition, the constructor also accepts two T type
variables.
Using Point is just like using other generic types.
For example, the following code creates two Point
objects: point1 and point2. The former
passes Integer as the type variable, the latter,
Double.
Point<Integer> point1 = new Point<Integer>(4, 2);
point1.setX(7);
Point<Double> point2 = new Point<Double>(1.3, 2.6);
point2.setX(109.91);
|
Related Reading
Java Threads |
Summary
Generics enable stricter type checking at compile time. Used
especially in the Collections framework, generics make two
contributions. First, they add type checking to collection types at
compile time, so that the type of objects that a collection can
hold is restricted to the type passed to it. For example, you can
now create an instance of java.util.List that holds strings and will
not accept Integers or other types. Second, generics eliminate the
need for type casting when retrieving an element from a
collection.
Generic types can be used without type variables; i.e., as raw types. This provision makes it possible to run pre-Java 5 code with JRE 5. For new applications, you are advised against using raw types, as future releases of Java may not support them.
You have also learned that passing different
type variables to a generic type results in different Java types.
That is, List<String> is a different
type than List<Object>. Even though
String is a subclass of java.lang.Object,
passing a List<String> to a method that expects
a List<Object> generates a compile error.
Methods that expect a List of anything can use the
? wildcard. List<?> means a
List of objects of any type.
Finally, you have seen that writing generic types is not that different from writing ordinary Java types. You just need to declare a list of type variables in angle brackets after the type name. You then use these type variables as the types of method return values or as the types of method arguments. By convention, a type variable name consists of a single uppercase letter.
Budi Kurniawan is a senior J2EE architect and author.
Return to ONJava.com.
-
Yes, I am using generics...
2005-07-08 13:12:01 pholser [View]