Jason
Jason

Reputation: 2925

Extending the contained type when implementing an Interface

I've created two Java interfaces, Tree<T> and BinarySearchTree<T extends Comparable<T>>, where the second interface extends the first one. I have an implementation of BinarySearchTree, called LinkedBinarySearchTree. The following code obviously complains at compile-time:

Tree<Object> a = new LinkedBinarySearchTree<Object>();

since LinkedBinarySearchTree implements an interface that has specified T as a Comparable type, and Objects are not Comparable. I understand this perfectly and it does not constitute a problem with my implementations.

What I'm looking to understand is whether this approach might be dangerous in practice. I cannot envision a scenario where the "narrowing down" of the data type from Tree to BinarySearchTree would give me a runtime error (ClassCastException, perhaps?) but I was looking to confirm this with more knowledgeable people. In essence, is "narrowing down" the type held by an interface when extending that interface considered bad practice and, if so, why?

Upvotes: 2

Views: 65

Answers (2)

OldCurmudgeon
OldCurmudgeon

Reputation: 65889

Quite the opposite in fact - it is actually good practice.

Remember that using generics is a technique to avoid coding mistakes by catching as many mistakes as possible at compile time. By tightening the specificity of your BinarSearchTree interface you are actually making it more difficult for people to misuse it accidentally. Not only that but their mistakes will be discovered at compile time.

Upvotes: 1

PNS
PNS

Reputation: 19915

It does not seem bad practice at all. Whether you get a ClassCastException depends on what you try to do, not on the implementation approach.

A common case of the "narrowing down" is when one wants to specialize the types of certain implementations, deriving from a common base. For example, to implement some calculator, with a different return type each time:

public interface Calculator<T> {
    public Number calculate(T... arguments);
}

public interface NumberCalculator<T extends Number> extends Calculator<T> {
    @Override
    public T calculate(T... arguments);
}

public interface IntegerNumberCalculator extends NumberCalculator<Integer> {
    @Override
    public Integer calculate(Integer... arguments);
}

public interface FloatNumberCalculator extends NumberCalculator<Float> {
    @Override
    public Float calculate(Float... arguments);
}

The arguments above may be specified to be something different to <T>, while the return type can continue to be set to <T> for every subclass, thereby offering much better type consistency compared to having to return a Number object all the time.

Upvotes: 1

Related Questions