Home » Java » About Java subinterface subtype

About Java subinterface subtype

Posted by: admin December 28, 2021 Leave a comment

Questions:

This seems a basic java question.

I have one interface, Pipeline, which has a method execute(Stage).

Then I create a sub interface to extend from Pipeline, say BookPipeline, I like the method to be execute(BookStage).

BookStage extends from Stage.

Seems this kind of definition could not pass java compile.

Any suggestion on that?

Answers:

You may want to consider using generics.

public interface Pipeline<T extends Stage> {
     public void execute(T stage);
}

public interface BookPipeline extends Pipeline<BookStage> {
     @Override
     public void execute(BookStage stage);
}

###

In addition to what @Jeffrey wrote as a possible solution, it is important to understand why you cannot do it.

Assume you had an interface Pipeline with a method execute(Stage), and an extending interface BookPipeline with execute(BookStage).

Also assume you have some class Conc that implements BookPipeline.

Consider the following

Pipeline p = new Conc();
p.execute(new Stage());

What will happen? It will be unsafe!

Java wants to avoid it, and thus prevent this situations from the first place.

The rule is an extending class/interface can add behavior, but not reduce it.

###

Just to elaborate on @amit ‘s answer, the code snippet is unsafe as the Conc.execute method takes a BookStage as a parameter and this would be trying to squeeze a Stage in place of that (and of course, not all Stages are BookStages).

However, imagine we wanted to go the other way, that is, make the parameter type of the BookePipeline.executea super type of Stage, such as Object.

So just to clarify, we would have:

interface Pipeline
{
  void execute(Stage s);
}

interface BookPipeline extends Pipeline
{
  @Override
  void execute(Object s);
}

And where Conc implements BookPipeline:

Pipeline p = new Conc();
p.execute(new Stage());

This would, in theory, be safe because Liskov Substitutability has not been violated – we could safely pass a Stage into any implementation that took a Stage parameter or greater. This is known as contravariance. Java does not support contravariant argument types, however there are languages that do.

Your original question relates to covariant argument types which is unsafe for the reasons specified (however strangely enough, a language called Eiffel allows this).

Java does however support covariant return types. Imagine Pipeline had a

Stage getAStage();

it would be perfectly legal for BookPipeline to override this method like so:

@Override
BookStage getAStage();

Then imagine we had:

public void someMethodSomewhere(Pipeline p)
{
  Stage s = p.getAStage();
  //do some dance on Stage
}

Assuming we had some class Donc which implemented Pipeline and overrode getAStage() exactly as it is defined in Pipeline (so still returning Stage), both of these calls are OK:

someMethodSomewhere(new Conc());
someMethodSomewhere(new Donc());

Because we can always put a Stage or anything less (e.g. BookStage) in a variable of type Stage.

So to reword the rule to relate specifically to method overriding, an extending class/interface that overrides methods, can only make those methods more general in what they accept and more specific in what they return. (although in the case of Java, only more specific return types are allowed.)

Just remember, PECS – Producer Extends, Consumer Super (Joshua Bloch, Effective Java)