Summary
I need to use the collective-consciousness of the Artima community, like genetic algorithms, to come up with a set of canonical use cases for generics.
Advertisement
Trying to pick them apart in previous weblogs and in the chapter, I finally realized that each example of generics that you see is usually comprised of multiple use cases, which can make them confusing to learn from. So I've started a list of use cases, where I try to focus on only one issue of generics for each item in the list.
What I'm hoping you will do is to look over this list and see if there are any (A) improvements and (B) additions that you can make. The resulting list will not only ensure that I cover everything in the chapter, but I think it will make a very nice summary of generic issues.
Thanks for your help.
import java.util.*;
// 1. Just want to call a specific method inside your
// code. (You don't need generics for this; just pass
// the class or interface directly).
interface MyInterface1 { void method(); }
class CallMethod {
void call(MyInterface1 mi) { mi.method(); }
}
// 2. Return a specific type, rather than a base type:
interface MyInterface2<T> {
T returnMySpecificType();
// ...
}
class Pet {}
class Dog extends Pet {}
class Waffle {}
class MI2Test1 implements MyInterface2<Pet> {
public Pet returnMySpecificType() { return new Pet(); }
}
class MI2Test2 implements MyInterface2<Dog> {
public Dog returnMySpecificType() { return new Dog(); }
}
class MI2Test3 implements MyInterface2<Waffle> {
public Waffle returnMySpecificType() {
return new Waffle();
}
}
// 3. Constrain the use of (2):
interface MyInterface3<T extends Pet> {
T returnMySpecificType();
// ...
}
class MI3Test1 implements MyInterface3<Pet> {
public Pet returnMySpecificType() { return new Pet(); }
}
class MI3Test2 implements MyInterface3<Dog> {
public Dog returnMySpecificType() { return new Dog(); }
}
// Error: type parameter Waffle is not within its bound:
/*!
class MI3Test3 implements MyInterface3<Waffle> {
public Waffle returnMySpecificType() {
return new Waffle();
}
} */
// 4. Pass a generic class into a method:
class GenericUseCases4 {
static <T> T method(List<T> list, T item) {
// Modify the list:
list.remove(1);
list.clear();
// Can call a method that takes a T argument:
list.add(item);
// Can call a method that returns a T argument:
return list.get(0);
}
static void test() {
method(new ArrayList<Integer>(), 1);
}
}
// 5. Constrained form of the above:
class GenericUseCases5<U> {
<T extends U> T method(List<T> list, T item) {
// Modify the list:
list.remove(1);
list.clear();
// Can call a method that takes a T argument:
list.add(item);
// Can call a method that returns a T argument:
return list.get(0);
}
static void test() {
new GenericUseCases5<Number>().method(
new ArrayList<Integer>(), 1);
}
}
// 6. Pass a generic class or a class holding a subtype:
class GenericUseCases6 {
<T> T method(List<? extends T> list, T item) {
// Modify the list:
list.remove(1); // (Method with a non-T argument)
list.clear();
// Cannot call a method that takes a T argument:
//! list.add(item);
// Error message: cannot find symbol : method add(T)
// Can call a method that returns a T argument:
return list.get(0);
}
}
// 7. Loosen constraints but still write to the list:
class GenericUseCases7 {
<T> void method(List<? super T> list, T item) {
// Modify the list:
list.remove(1);
list.clear();
// Can call a method that takes a T argument:
list.add(item);
// Requires an unsafe cast for a T return value:
T x = (T)list.get(0);
// Warning: uses unchecked or unsafe operations
}
}
// 8. Simplify usage with type inference:
class GenericUseCases8<T> {
private T type;
public GenericUseCases8(T type) {
this.type = type;
}
public static <T> GenericUseCases8<T> inferType(T t) {
return new GenericUseCases8<T>(t);
}
static void test() {
GenericUseCases8<Waffle> w = inferType(new Waffle());
}
}
// 9. Utilize capture conversion:
class GenericUseCases9 {
public static void captureType(List<?> list) {
explicitType(list);
}
private static <T> void explicitType(List<T> list) {
// ...
}
static void test() {
captureType(new ArrayList<Waffle>());
}
}
I agree with most of your list, however I don't get 9. Your examples:
void captureType( List< ? > list )
< T > void explicitType( List< T > list )
are the same (apart from their names!). Using a wildcard or explicitly named type is just a matter of style when there is only one mention of the type. Sun recommend wildcards, see ref. below. Perhaps just reword this example to say it is just a matter of style which one you choose.
Points 4, 6, and 7 might benifit from some extra words describing them. 4 is read/write from/to the list, but the type of list and type of list must be exactly the same as the type of the item. 6 is read only from the list, but List of T1 and item of type T2 can be passed in so long as T1 is T2 or T1 extends T2. 7 is write only to the list, but List of T1 and item of type T2 can be passed in so long as T1 is T2 or T2 extends T1 (same as T1 super T2).
The other useful generic idiums are:
10. Wildcards as fields, locals etc., where you can't use explicityly named types. EG:
List< ? > l = new ArrayList< Integer >; // OK
< T > List< T > l = new ArrayList< Integer >; // Error
11. Using abstract methods for type specific operations in the style of having a prototype instance that you manipulate, e.g.:
interface Prototype< T extends Prototype > {
T instance();
T[] array( int size );
// etc.
}
class ArrayListP< E extends Prototype< E > > extends AbstractList< E > {
privatefinal E[] values;
ArrayListP( final E prototype, finalint size ) {
values = prototype.array( size );
for ( int i = 0; i < size; i++ ) values[ i ] = prototype.instance();
}
public E get( finalint index ) { return values[ index ]; }
publicint size() { return values.length; }
}
class PInteger implements Prototype< PInteger > {
publicint value;
public PInteger instance() { returnnew PInteger(); }
public PInteger[] array( finalint size ) { returnnew PInteger[ size ]; }
}
publicclass Main {
publicstaticvoid main( final String[] notUsed ) {
final PInteger pi = new PInteger();
final ArrayListP< PInteger > pil = new ArrayListP< PInteger >( pi, 2 );
out.println( pil );
}
}
12. Using instances of Class for type specific operations
class ArrayListC< E > extends AbstractList< E > {
privatefinal E[] values;
ArrayListC( final Class< E > prototype, finalint size ) throws InstantiationException, IllegalAccessException {
values = (E[])newInstance( prototype, size ); // ignore unchecked warning
for ( int i = 0; i < size; i++ ) values[ i ] = prototype.newInstance();
}
public E get( finalint index ) { return values[ index ]; }
publicint size() { return values.length; }
}
publicclass Main {
publicstaticvoid main( final String[] notUsed ) throws InstantiationException, IllegalAccessException {
final ArrayListC< Object > col = new ArrayListC< Object >( Object.class, 2 );
out.println( col );
}
}
Any drawing will typically contain a number of shapes. Assuming that they are represented as a list, it would be convenient to have a method in Canvas that draws them all:
Now, the type rules say that drawAll() can only be called on lists of exactly Shape: it cannot, for instance, be called on a List<Circle>. That is unfortunate, since all the method does is read shapes from the list, so it could just as well be called on a List<Circle>. What we really want is for the method to accept a list of any kind of shape:
There is a small but very important difference here: we have replaced the type List<Shape> with List<? extends Shape>. Now drawAll() will accept lists of any subclass of Shape, so we can now call it on a List<Circle> if we want.
I dont see that we are making use of polymorphism !
We know that this Shape example is traditional example we use to explain " polymorphism" but now using Generics we are not making use of polymorphism since we are forced to put the additional statement "<? extends " ! so that the abstract method "drawAll()" accept the subclass of Shape .
I think Generics adds some thing good but at the same time it ignores a fundamental concept in JAVA and OOP !
You mean 'pattern' or 'idiom', not 'use case'. A use case is a description of a system's external behaviour, in the form of a sequence of actions that provide the user with a result of value.
> You mean 'pattern' or 'idiom', not 'use case'. A use case > is a description of a system's external behaviour, in the > form of a sequence of actions that provide the user with a > result of value.
I considered that, and decided that in this case *we* (programmers) are the "users" and so it's not such a terrible warping of the term. It seems to me that these really are the different use cases of generics for programmers.
> We know that this Shape example is traditional example we > use to explain " polymorphism" but now using Generics we > are not making use of polymorphism since we are forced to > put the additional statement "<? extends " ! so that the > abstract method "drawAll()" accept the subclass of Shape
Not exactly. This is one of the things that get tricky. Polymorphism is still working here. The issue that covariance (List<? extends something>) deals with is the "type of the container." A List-of-Shape, AS A CONTAINER, has a completely different type than a List-of-Circle as a container. That's the problem.
The compiler sees them as two different types, so if you write a method:
f(List<Shape> list) {}
The compiler will give you an error if you call it with:
f(new ArrayList<Circle>());
Since the two types of List (including the type contained in the list as part of the List's type) are different, it refuses to accept it.
However, we *expect* that code that can handle a List<Shape> should be able to handle a List<Circle>. So wildcards allow us to express this expectation by saying:
f(List<? extends Shape> list) {}
This says that the 'list' argument can be a List<Shape>, or a List<anything derived from Shape>.
The objects in the List<Shape> or the List<Circle> can be something derived from those types. You can put a Circle in a List<Shape> and you can put BigCircles and SmallCircles in a List<Circle>. When you call methods on those objects, polymorphism still works.
publicvoid drawAll( final List< Shape > shapes ) for ( final Shape s : shapes ) { s.draw( this ); }
and say it is not polymorphic. It is polymorphic in the sense that both the following are legal:
drawAll( new ArrayList< Shape >() );
drawAll( new LinkedList< Shape >() );
since both ArrayList and LinkedList implement List. The issue is whether the container (the List) is read, write, or read/write. Therefore since drawAll only needs read access (this is the most common case) the declaration should be:
publicvoid drawAll( final List< ? extends Shape > shapes ) { for ( final Shape s : shapes ) s.draw( this ); }
the ? extends Shape bit says that it is OK for read operations on things that extend Shape, e.g. List< Circle > is fine.
The reason for requiring the extra ? extends Shape bit is that they wanted to tighten the type system so that more errors are caught at compile time. Arrays don't do this, i.e. a Circle[] can be passed to a Shape[], and therefore you could write:
publicvoid drawAll( final Shape[] shapes ) for ( final Shape s : shapes ) { s.draw( this ); }
and this would be fine with a Circle[]. However the disadvantage of allowing a Circle[] to be a Shape[] is:
publicvoid fillAll( final Shape[] shapes ) for ( int i; i < shapes.length; i++ ) { shapes[i] = new Shape(); }
will fail with an ArrayStoreException at runtime if it is given a Circle[]. The equivelent using generics:
publicvoid fillAll( final List< ? super Shape > shapes ) { for ( int i; i < shapes.size(); i++ ) shapes.set( i, new Shape() ); }
will be a compile time error for a List< Circle > since Circle is not a superclass of Shape (it is a subclass!).
Therefore when Sun designed the generics they went that little bit further and eliminated another possible runtime exception. Was it a good idea to add this extra compile time checking? I guess only time will tell.
One of the things that I did with generics that works really well is using an interface as a key in a service lookup scenario. For example: interface Lookup { template<T> T findService( Class<T> service ); }
interface Output { void send( String message ); }
class Example { public static main(String[] args) { Lookup manager = init(); // can be any impl.
That's pretty fun stuff. It could make something like JNDI much more pleasant to work with. A simple implementation of the Lookup interface would work like this:
class MyLookup extends Lookup { private Map<Class<?>,Object> services = new HashMap<Class<?>,Object>();
static < T > void rev( final List< T > list ) {
final ListIterator< T > fwd = list.listIterator();
final ListIterator< T > rev = list.listIterator( list.size() );
finalint mid = list.size() >> 1;
for ( int i = 0; i < mid; i++ ) {
T tmp = fwd.next();
fwd.set( rev.previous() );
rev.set( tmp );
}
}
staticvoid reverse( final List< ? > list ) { rev( list ); }
This is close to a pointless example; sure you can't write the body of rev using wildcards because the body of rev needs to have a name for the type, the body uses T 3 times! The only point in the reverse declaration is that you think it reads better than the rev declaration and therefore you prefer it in your Javadoc. But if you are happy with the signature of rev then simply eliminate reverse.
By way of a further example (see above post):
void drawAll( final List< ? extends Shape > shapes ) { for ( final Shape s : shapes ) s.draw(); }
could be re-written as:
< S extends Shape > void drawAll( final List< S > shapes ) { for ( final Shape s : shapes ) s.draw(); }
the two forms are identical because S is used exactly once and therefore could be anonymouse since you clearly don't need a name if you only use it once. The first form is shorter, but that's it.
> This is close to a pointless example; sure you can't write > the body of rev using wildcards because the body of rev > needs to have a name for the type, the body uses T 3 > times! The only point in the reverse declaration is that > you think it reads better than the rev declaration and > therefore you prefer it in your Javadoc. But if you are > happy with the signature of rev then simply eliminate > reverse.
I'm confused. Are you saying that capture conversion is not a useful feature, or that neither my example or Langer's example shows its value?
If the latter, is there a simple example that shows the necessity of capture conversion? If the former -- well, I guess I need some deeper insight because I thought it had some kind of value.
Thanks Bruce, Have always been a big fan....and I guess this post of yours is going to make a big difference. I hope pple come up with new ideas and we (at least I) be able to move from 'syntax of using generics' to 'what u can do with generics' phase... :) However, I wanted to write a method to merge two arrays of beans(can be of any type) without any casting. I ended up with this solution. Being this is my first method using generics I don't know if it's a valid way to get it done. Would sure like to have some comments if any chance of improvement is still there.
public static <T> T[] mergeArrays(T[] a1, T[] a2) { Class cType = a1.getClass().getComponentType(); T[] arr =(T[]) Array.newInstance(cType, a1.length+a2.length);
for(int i = 0; i < a1.length; i++) { arr[i] = a1[i]; }
for(int i = 0; i < a2.length; i++) { arr[a1.length + i] = a2[i]; }
> I'm confused. Are you saying that capture conversion is > not a useful feature, or that neither my example or > Langer's example shows its value?
I am saying it is of limited value. The examples are fine, it is just that capture conversion isn't that useful! Consider:
staticvoid reverse( List< ? > list )
which could be re-written as:
static < T > void reverse( List< T > list )
the two forms are identical because T is used exactly once and therefore could be anonymouse since you clearly don't need a name if you only use it once. The first form is shorter, but that's it!
If you prefer a shorter public declaration (you still need the longer form but it can be private); then capture conversion is useful, but if you don't mind the longer form then you don't need capture conversion.
Slightly off topic: but I did suggest to Sun when they were asking for feedback on generics before 5 came out that they change the syntax so that the ? introduced a type name, e.g. the above would be:
staticvoid reverse( List< ? T > list )
This would eliminate the need for the prefix notation since ?T would declare generic argument T and T on its own would use the generic argument. The argument would be declared on first use, like a local variable. The advantages I saw in this syntax is that: it would be more like a normal variable declaration ( think of ? as the type of types), you wouldn't have two sytax for essentially the same thing, and thirdly you could have local type variables:
/* The idea below is that l1 and l2 are of the same type, T,
but that exact type is an unkown (i.e. I don't care what
it is so long as they are the same) */
List< ? T > l1 = ...; // declares and uses T
List< T > l2 = ...; // uses T
Which is what I really wanted! You can't do this currently, instead you need to encapsulate the code in a method so that you can introduce the type variable. Also the '< ? [name] >' syntax is similar to the C++ '< class [name] >' syntax and I proposed its use for type definitions, like C++, e.g.:
interface List< ? E > extends Collection< E > { ... }
However I think I made my suggestion known to Sun too late in the day and they were committed to the prefix form (or they just didn't think it was a good idea!). (The purpose of showing this alternate syntax is to give another example of how the wildcard and prefix syntax are similar.)
Flat View: This topic has 39 replies
on 3 pages
[
123
|
»
]