1

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.

Mike Nakis
  • 56,297
  • 11
  • 110
  • 142

2 Answers2

3

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.

Thomas
  • 87,414
  • 12
  • 119
  • 157
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 );
    }
}
Judge Mental
  • 5,209
  • 17
  • 22
  • Please see the 'EDIT' I added to my question. – Mike Nakis Nov 25 '13 at 16:44
  • So then, would you say that basically what it boils down to is that the problem lies with java not supporting proper variance declaration at the definition of generic classes? – Mike Nakis Nov 25 '13 at 18:38
  • That is correct. Scala does allow it. In your case you have to make all the clients of `Predicate< T >` objects accept `Predicate< ? super T >` for whatever `T` they are planning to pass to `evaluate`. Such a declaration makes it clear to the compiler that the object is being used in a contravariant fashion. – Judge Mental Nov 25 '13 at 19:56
  • Incidentally, although this could be considered a defect in the Java type system, it is more flexible than if we were forced to declare variance at class definition time. The Java system lets you use naturally invariant objects as if they were covariant or contravariant by disabling (type-wise) the methods of the opposite variance. – Judge Mental Nov 25 '13 at 20:22