While trying to program to interfaces, I regularly find myself in the following situation:
- I have several very similar classes representing containers or algorithms for different types.
- I would like to define a common interface for these classes.
Consider, e.g., a string container. Such a container will likely have string processing methods. Since those methods are easily represented using generic interfaces, I am ignoring them. Here, I want to focus on methods that can be used to process or provide references to other string containers:
public class StringContainer { StringContainer produce() { return new StringContainer(); } void consume(StringContainer stringContainer) { } }
This class can be used just fine in code like:
public class Main { public static void main(String[] args) { StringContainer stringContainer = new StringContainer(); stringContainer.produce(); stringContainer.consume(stringContainer); } }
The problem is: I’m using a concrete class and not an interface to refer to the string container. What if I want to introduce a double container or a list container later and want to leave the rest of the code as is?
Maybe generics could form a solution to this problem? Here is my try. I first define a generic container class:
interface Container<T> { Container<T> produce(); void consume(Container<T> container); }
I then create type-specific implementations of the form:
public class StringContainer implements Container<String> { @Override public Container<String> produce() { return new StringContainer(); } @Override public void consume(Container<String> container) { } public void consume(StringContainer container) { } }
The above classes can be used as follows:
public class Main { public static void main(String[] args) { Container<String> stringContainer = new StringContainer(); stringContainer.produce(); stringContainer.consume(stringContainer); } }
However, the above approach has several drawbacks:
- The
consume(Container<String> container)
method accepts other types thanStringContainer
. - In
consume(Container<String> container)
, the parametrized typeContainer<String>
has to be used when processingcontainer
. I can’t assign it toStringContainer
variables (without type checks or casts). - The alternative
consume(StringContainer container)
method is defined forStringContainer
objects, but can’t be called from aContainer<String>
reference. - Finally, to me, the line
Container<String> stringContainer = new StringContainer();
has an awkward-looking notation that suggests a diamond operator is missing innew StringContainer()
.
What is the idiomatic way to define a general interface for several type-specific classes, which doesn’t have (all) the above drawbacks?
Should I ignore point 4 and address points 1 and 2 by adding type checks/casts, throwing an UnsupportedOperationException
or IllegalArgumentException
in case passed objects aren’t StringContainer
s?
Or is there another way to use generics? Can type bounds help me, for example?
Or should I look for a solution outside of generics?
Update:
Based on the answers given so far, I have come to realize that I had conflicting goals:
- On the one hand, I wanted to restrict the types accepted by container methods (as described by points 1 and 2).
- On the other hand, I wanted to address and pass container types using an interface reference (as hinted at by point 3 and my implicit desire to keep the second
main
method as is).
I now see that these goals cannot both be reached statically. Since I don’t want to rephrase my question in retrospect, I’ll forget about my second (rather implicit) goal and mark the first-posted solution that addressed points 1 and 2 as the answer.
Advertisement
Answer
Is this what you’re looking for? It’s called a recursive type bound.
interface Container<T extends Container<T>> { T produce(); void consume(T container); } class StringContainer implements Container<StringContainer> { @Override public StringContainer produce() { return new StringContainer(); } @Override public void consume(StringContainer container) { } }