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.
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 );
}
}