The Artima Developer Community
Sponsored Link

Weblogs Forum
Use cases for Generics

39 replies on 3 pages. Most recent reply: Jan 12, 2006 1:59 PM by Mo Welch

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 39 replies on 3 pages [ 1 2 3 | » ]
Bruce Eckel

Posts: 874
Nickname: beckel
Registered: Jun, 2003

Use cases for Generics (View in Weblogs)
Posted: Oct 26, 2005 5:40 PM
Reply to this message Reply
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>());
  }
}


Howard Lovatt

Posts: 321
Nickname: hlovatt
Registered: Mar, 2003

Re: Use cases for Generics Posted: Oct 27, 2005 2:06 AM
Reply to this message Reply
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 > {
    private final E[] values;
    ArrayListP( final E prototype, final int size ) {
        values = prototype.array( size );
        for ( int i = 0; i < size; i++ ) values[ i ] = prototype.instance();
    }
    public E get( final int index ) { return values[ index ]; }
    public int size() { return values.length; }
}
 
class PInteger implements Prototype< PInteger > {
    public int value;
    public PInteger instance() { return new PInteger(); }
    public PInteger[] array( final int size ) { return new PInteger[ size ]; }
}
 
public class Main {
    public static void 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 > {
    private final E[] values;
    ArrayListC( final Class< E > prototype, final int 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( final int index ) { return values[ index ]; }
    public int size() { return values.length; }
}
 
public class Main {
    public static void main( final String[] notUsed ) throws InstantiationException, IllegalAccessException {
        final ArrayListC< Object > col = new ArrayListC< Object >( Object.class, 2 );
        out.println( col );
    } 
}


Also see http://java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf which has some great examples and is nicely written.

Hope this is helpful.

ibrahim ALShehri

Posts: 6
Nickname: ibh3000
Registered: Oct, 2005

Re: Use cases for Generics Posted: Oct 27, 2005 6:51 AM
Reply to this message Reply
thanks Bruce

i have question about Bounded Wildcards
in Generics and i hope

discussing the answer will benefit all of us

When I read the PDF file that Howard Lovatt put

I found this example

public abstract class Shape
 {
 public abstract void draw(Canvas c);
 }
 
public class Circle extends Shape 
 
{
 private int x, y, radius;
public void draw(Canvas c) { ... }
}
 
public class Rectangle extends Shape
 
{
 private int x, y, width, height;
public void draw(Canvas c) { ... }
}
 



And they are saying that :


These classes can be drawn on a canvas:

public class Canvas { public void draw(Shape s) { s.draw(this);
}
}


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:

public void drawAll(List<Shape> shapes) { for (Shape s: shapes) { s.draw(this);
}
}

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:

 public void drawAll(List<? extends Shape> shapes) { ... } 

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 don’t 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 !

Do u agree with me !?


Harrison Ainsworth

Posts: 57
Nickname: hxa7241
Registered: Apr, 2005

terminological correction alert! Posted: Oct 27, 2005 9:59 AM
Reply to this message Reply
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.

Bruce Eckel

Posts: 874
Nickname: beckel
Registered: Jun, 2003

Re: terminological correction alert! Posted: Oct 27, 2005 10:31 AM
Reply to this message Reply
> 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.

Bruce Eckel

Posts: 874
Nickname: beckel
Registered: Jun, 2003

Re: Use cases for Generics Posted: Oct 27, 2005 10:52 AM
Reply to this message Reply
> 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.

Bruce Eckel

Posts: 874
Nickname: beckel
Registered: Jun, 2003

Re: Use cases for Generics Posted: Oct 27, 2005 11:50 AM
Reply to this message Reply
Howard:

Note that captureType() turns around and calls explicitType() after it has captured the type. Capture conversion is described here:
http://www.langer.camelot.de/GenericsFAQ/FAQSections/TechnicalDetails.html#Wildcard%20Capture

That said, the example needs to show more about why you would want to do this, to justify the existence of capture conversion.

Howard Lovatt

Posts: 321
Nickname: hlovatt
Registered: Mar, 2003

Re: Use cases for Generics Posted: Oct 27, 2005 7:03 PM
Reply to this message Reply
@ibrahim ALShehri

You give the example of the method:
public void 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:
public void 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:
public void 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:
public void 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:
public void 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.

Berin Loritsch

Posts: 5
Nickname: bloritsch
Registered: Jun, 2005

How about service lookup? Posted: Oct 27, 2005 7:34 PM
Reply to this message Reply
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.

Output component = manager.findService(Output.class);
component.send("Hello World!");
}
}


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>();

public void setService(Class<?> role, Object impl) {
assert role.isAssignableFrom( impl.getClass() );

services.put(role, impl);
}

template<T> public T findService( Class<T> role ) {
return role.cast( services.get( role ) );
}
}

Howard Lovatt

Posts: 321
Nickname: hlovatt
Registered: Mar, 2003

Re: Use cases for Generics Posted: Oct 27, 2005 9:10 PM
Reply to this message Reply
@Bruce

The example in the FAQ you reference is:
static < T > void rev( final List< T > list ) {
    final ListIterator< T > fwd = list.listIterator();
    final ListIterator< T > rev = list.listIterator( list.size() );
    final int mid = list.size() >> 1;
    for ( int i = 0; i < mid; i++ ) {
        T tmp = fwd.next();
        fwd.set( rev.previous() );
        rev.set( tmp );
    }
}
    
static void 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.

Neil Greenwood

Posts: 1
Nickname: gneel
Registered: Oct, 2005

Re: Use cases for Generics Posted: Oct 28, 2005 4:04 AM
Reply to this message Reply
There's a small error in use case 7.

You need to
return x;
at the end of the method.

Bruce Eckel

Posts: 874
Nickname: beckel
Registered: Jun, 2003

Re: Use cases for Generics Posted: Oct 28, 2005 12:44 PM
Reply to this message Reply
> 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.

Sajid Moinuddin

Posts: 1
Nickname: sajid2045
Registered: Oct, 2005

Re: Use cases for Generics Posted: Oct 29, 2005 11:46 PM
Reply to this message Reply
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];
}

return (T[]) arr;
}

Vladimir Nesov

Posts: 27
Nickname: robotact
Registered: Aug, 2005

Re: Use cases for Generics Posted: Oct 30, 2005 10:42 AM
Reply to this message Reply
Using type-safe coupling of classes under hierarchy:
public interface Graph <V extends Vertex<V,G>, G extends Graph<V,G>>{
    V getVertexAt(int index);
}
 
public interface Vertex <V extends Vertex<V,G>, G extends Graph<V,G>>{
    G getGraph();
}
 
public class FlowGraph implements Graph<FlowVertex,FlowGraph> {
    private FlowVertex root=new FlowVertex(this);
    public FlowGraph(){}
    public FlowVertex getRoot(){return root;}
    public FlowVertex getVertexAt(int i){return root;}
}
 
public class FlowVertex implements Vertex<FlowVertex,FlowGraph> {
    private FlowGraph fg;
    FlowVertex(FlowGraph fg){this.fg=fg;}
    public FlowGraph getGraph(){return fg;}
    public FlowVertex getIdom(){return null;}
}
 
public class Test {
    public static void main(String args[]){
        FlowGraph tree=new FlowGraph();
        FlowVertex fvert=tree.getVertexAt(0);
        FlowGraph theSameGraph=fvert.getGraph();
        FlowVertex idom=fvert.getIdom();
    }
}

Howard Lovatt

Posts: 321
Nickname: hlovatt
Registered: Mar, 2003

Re: Use cases for Generics Posted: Oct 30, 2005 7:50 PM
Reply to this message Reply
@Bruce,

> 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:
static void 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:
static void 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 [ 1  2  3 | » ]
Topic: Error in  "Building More Flexible Types with Mixins" Previous Topic   Next Topic Topic: Eureka: Forget Interfaces what I really need are Traits!


Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2014 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use - Advertise with Us