Reputation: 2925
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 Object
s 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
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
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