Reputation: 2173
My question is, in short, whether, given
interface Intf<T extends Bound> {}
class Clss<T extends Bound> {}
is there no way to subclass/implement them other than repeating the type bound:
class Impl<T extends Bound> implements Intf<T> {}
class Subc<T extends Bound> extends Clss<T> {}
I'm not complaining of the need to repeat extends Bound
, I even understand the compiler logic, but I'd like to know if this is really this way. Because using <T>
in both places would look semantically sufficient to me.
Upvotes: 1
Views: 80
Reputation: 272657
Note that although the Foo<T [constraints]> implements Bar<T>
pattern is very common, it's only one possibility. Here are some others (non-exhaustive):
Foo<T extends DerivedBound> implements Bar<T>
Foo<T> implements Bar<DerivedBound>
Foo<T> implements Bar<ParameterisedBound<T>>
Foo<T, U extends Bound> implements Bar<U>
Foo<T extends BarBound & BazBound> implements Bar<T>, Baz<T>
So there's no redundancy in having to specify the bound twice - you're supplying two independent pieces of information to the compiler.
In theory, the language designers could have added a special case to Java, in order to save a few characters. But assuming they even considered this option, I imagine they decided that the extra complexity was not a good trade-off.
Upvotes: 1
Reputation: 8833
Yes, this is this way. And here is the full explanation for everyone's sake.
Here is another version of your example (imagine Integer can be extended)
interface Intf<T extends Number> {}
class Clss<T extends Number> {}
class Impl<T extends Integer> implements Intf<T> {}
class Subc<T extends Integer> extends Clss<T> {}
class ImplSubc<T extends Integer> extends Clss<T> implements Intf<T> {}
The <T extends Integer>
is the initialization tag, and the further <T>
tags are basically "pass along" references. The two tags are visually identical, but they are different things. Notice the following is illegal
class Subc<T extends Integer> extends Clss<T extends Integer> {}
Because the pass along tag needs SOMETHING ELSE to initialize it. So You can tell which is which because the initialize tag gets something passed to it eventually, and the pass along tag is something that is passed along (or back) based on what the initialization tag is set to. So if you just have the following
class Subc<T> extends Clss<T> {}
The compile unit says "allow this to be set to anything, and pass that on to Clss". Since you can pass the initialization tag on to multiple pass along tags, each with possible different boundaries, The initialization tag NEEDS to be explicitly set to be sure that the passed in <T>
really is expected to be compatible with everything you pass it along to. While this technically can be inferred, it is expensive, complicated, and asking for horrible production breaking bugs, so the compiler just forces you to know what you are doing.
Update:
Here is an example of how a bug can enter production if you let the compiler decide what your code is suppose to be. Assuming the compiler did infer limits, If
class Clss<T extends Number> {}
class Subc<T> extends Clss<T> {}
becomes
class Clss<T extends BigDecimal> {}
class Subc<T> extends Clss<T> {}
Without extends Integer
set on Subc and letting it be inferred, this change would compile (if this is compiled separate from what uses it as an API), and probably still work until that one case it doesn't. Because you say you explicitly want to work with Integer or Number, changing the type of Clss can now be determined to be a safe or bad change at compile time instead of run-time. Java is a safety first type language, which means that code stability/reliability detection is a core value of its features. So when in doubt, Java complains.
Upvotes: 1