Reputation: 62045
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.
Upvotes: 1
Views: 200
Reputation: 5239
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 );
}
}
Upvotes: 2
Reputation: 88727
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.
Upvotes: 3