Skip to content
Advertisement

How to define a general interface for several type-specific classes?

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:

  1. The consume(Container<String> container) method accepts other types than StringContainer.
  2. In consume(Container<String> container), the parametrized type Container<String> has to be used when processing container. I can’t assign it to StringContainer variables (without type checks or casts).
  3. The alternative consume(StringContainer container) method is defined for StringContainer objects, but can’t be called from a Container<String> reference.
  4. Finally, to me, the line Container<String> stringContainer = new StringContainer(); has an awkward-looking notation that suggests a diamond operator is missing in new 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 StringContainers? 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) {
    }
}
User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement