Question

Consider the following interfaces and classes:

interface BaseInterface { }

interface DerivedInterface extends BaseInterface { }

class BaseClass
{
    void baseFunc1( BaseInterface foo ) { }
    void baseFunc2( Collection<BaseInterface> foo ) { }
    void baseFunc3( Collection<? super DerivedInterface> foo ) { }
}

class DerivedClass extends BaseClass
{
    void derivedFunc1( DerivedInterface foo )
    {
        baseFunc1( foo ); //no problem here.
    }
    void derivedFunc2( Collection<DerivedInterface> foo )
    {
        baseFunc2( foo ); //error!
    }
    void derivedFunc3( Collection<DerivedInterface> foo )
    {
        baseFunc3( foo ); //fixed it, but the fix is unreasonable.
    }
}

When derivedFunc1() invokes baseFunc1() there is no problem, because DerivedInterface is assignable from BaseInterface.

But when derivedFunc2() invokes baseFunc2(), there is a problem, because Collection<DerivedInterface> apparently is not assignable from Collection<BaseInterface>.

Given my (admittedly not crystal clear) understanding of covariance and contravariance in java, the only way I can think of for fixing the problem is by declaring baseFunc3() as accepting a Collection<? super DerivedInterface>, which is assignable from Collection<DerivedInterface>.

Of course this is unreasonable, because the design of BaseClass cannot be bothered to know anything about some DerivedInterface, let alone put a cap on the derivation chain of BaseInterface.

This is a very frequently occurring issue in the kind of code I write, and the way currently handle it whenever I come across it is by adding conversion logic to the runtime.

My question: is there any nice and simple way of having derivedFunc2 pass its Collection<DerivedInterface> to baseFunc2 without making any unreasonable changes to BaseClass (such as adding that baseFunc3()) and without the expense of a runtime conversion?

EDIT:

The interface that I am using is not really the Collection interface, (Of course, I would not expect to be able to treat a collection of DerivedInterface as a collection of BaseInterface, because of the possibility that someone may add to that collection an object which implements BaseInterface but not DerivedInterface.)

I am using a Predicate interface which contains a method which accepts a BaseInterface as a parameter. Objects implementing that interface never need to store the instances of BaseInterface that are passed to them, they only need to invoke some methods of that BaseInterface, but as Thomas points out, it does not make any difference.

So, since a BaseInterface must be passed to the Predicate.evaluate() method, declaring baseFunc2 to accept a <? extends BaseInterface> will not work.

Was it helpful?

Solution 2

BaseClass.baseFunc2 should accept Collection< ? extends BaseInterface > as its parameter. In Java, the covariance or contravariance of a generic instance is declared at the point of its use, rather than at the class definition itself.

Why is T not part of the class hierarchy in your design?

class BaseClass< T > {
    void baseFunc1( T foo ) { }
    void baseFunc2( Collection< ? extends T > foo ) {
        // use foo in covariant fashion,
        // e.g., foo.contains( t ) 
        // can accept Collection< T >, Collection< S > (where S <: T)
    }
    void baseFunc3( Collection< ? super T > foo ) {
        // use foo in contravariant fashion,
        // e.g., foo.add( t )
        // can accept Collection< T >, Collection< S > (where S >: T)
    }
    void baseFunc4( Collection< T > foo ) {
        // use foo in invariant fashion,
        // e.g., foo.add( foo.iterator().next() )
        // can only accept Collection< T >
    }
}

Now you can do

class DerivedClass extends BaseClass< DerivedInterface > {
    void derivedFunc1( DerivedInterface foo ) {
        baseFunc1( foo );
    }
    void derivedFunc2( Collection< DerivedInterface > foo ) {
        baseFunc2( foo );
    }
    void derivedFunc3( Collection< DerivedInterface > foo ) {
        baseFunc3( foo );
    }
}

If you can't use T in BaseClass, you can only do

class DerivedClass extends BaseClass {
    void derivedFunc1( DerivedInterface foo ) {
        baseFunc1( foo );
    }
    void derivedFunc2( Collection< DerivedInterface > foo ) {
        baseFunc2( foo );
    }
    void derivedFunc3( Collection< BaseInterface > foo ) {
        baseFunc3( foo );
    }
}

OTHER TIPS

Change your method to void baseFunc2( Collection<? extends BaseInterface> foo ) { }. That way you allow any collection of BaseInterface or derived types to be passed, even subclasses of Collection, e.g. Set<DerivedInterface>.

Just one note: because the type of the collection can now be any subtype of BaseInterface the compiler won't allow you to add elements to the collection.

If you need to do that, the best option would be to pass a Collection<BaseInterface> anyways, but if you're sure about what you're doing, you could also disable type checking by casting to Collecion only or use your third approach, which as you said, is not optimal from a design point of view.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top