|
Re: Generics and covariant return types
|
Posted: Nov 8, 2005 8:20 PM
|
|
This is an interesting generic pattern that crops up in a number of places, e.g. generic factories. The solution I use:
abstract class HasF< T extends HasF< T > > {
abstract T fClass();
@SuppressWarnings( "unchecked" ) T f() {
System.out.println( "HasF.f()" );
return (T)this;
}
}
class HasFClass extends HasF< HasFClass > {
HasFClass fClass() {
System.out.println( "HasFClass.fClass()" );
return this;
}
}
public class Manipulate< T extends HasF< T > > {
T obj;
Manipulate( final T x ) { obj = x; }
T manipulate() { return obj.f(); }
T manipulateClass() { return obj.fClass(); }
public static void main( final String[] notUsed ) {
final HasFClass f = new HasFClass();
final Manipulate< HasFClass > m = new Manipulate< HasFClass >( f );
final HasFClass f2 = m.manipulate();
final HasFClass f3 = m.manipulateClass();
}
}
I expanded the example to show the two cases of a function in a base class, f, and in a derived class, fClass. The pattern uses a generic-abstract base, either an abstract class or an interface, that is recursively generic, HasF, in the above. Then a concrete, non-abstract, and non-generic class is derived, HasFClass. The class Manipulate programs to the the generic-abstract base but is parameterized with the concrete-non-generic class.
The difference between f and fClass is the cast in f. This is because of the lack of a MyType in Java as Jay Sachs has pointed out (but note it is not associated with erasure - both Pizza and Scala that have MyType use erasure). The problem is that Java takes this to be of type HasF whereas in fact it is of type extends HasF, which because of the recursive definition of T is T, and hence the cast won't fail (it is just that Java doesn't know it won't fail!).
Scala is a newish programming language from Martic Odersky, who also wrote Pizza -> GJ -> Java 5. In the paper Odersky wrote about Scala, http://scala.epfl.ch/docu/files/ScalaOverview.pdf, he gives a couple of examples of this same construct and how two type extensions in Scala generics help. The examples he gives in Scala are coded in Java as:
abstract class CBase< T extends CBase< T > > {
protected int x = 0;
@SuppressWarnings( "unchecked" ) T incr() { ++x; return (T)this; }
public String toString() { return Integer.toString( x ); }
}
class C extends CBase< C > {}
class D extends CBase< D > {
D decr() { --x; return this; }
}
public class ThisType {
public static void main( final String[] notUsed ) {
final D d = new D();
d.incr().decr();
System.out.println( d );
final C c = new C();
c.incr();
System.out.println( c );
}
}
The interesting line in this example is d.incr().decr() which is written without casts which is not possible with covarient return types alone. The second example given in the paper and coded in Java is:
abstract class Subject< S extends Subject< S, O >, O extends Observer< S, O > > {
private final List< O > observers = new ArrayList< O >();
void subscribe( O obs ) { observers.add( obs ); }
void publish() {
for ( final O obs : observers )
obs.notify( (S)this );
}
}
abstract class Observer< S extends Subject< S, O >, O extends Observer< S, O > > {
abstract void notify( S sub );
}
class Sensor extends Subject< Sensor, Display > {
double value = 0.0;
void changeValue( final double v ) {
value = v;
publish();
}
}
class Display extends Observer< Sensor, Display > {
void notify( Sensor sub ) {
System.out.println( sub + " has value " + sub.value );
}
}
public class SubjectObserver {
public static void main( final String[] notUsed ) {
final Display o = new Display();
final Sensor s = new Sensor();
s.subscribe( o );
s.changeValue( 1 );
}
}
This is a nice variation on the ame theme where mutually recursive generic definitions are used.
|
|