javainheritancebounded-types

How can I make a method with a bounded type parameter exclude one subclass?


Let us assume that gibbons, orangutans, gorillas, chimpanzees and humans are all apes. I have modeled this relationship accordingly.

class Ape {}
class Gibbon extends Ape {}
class Orangutan extends Ape {}
class Gorilla extends Ape {}
class Chimpanzee extends Ape {}
class Human extends Ape {}

I now want to write a hunt() method with a bounded type parameter for hunting apes.

public static <T extends Ape> void hunt(T type) {}

Let us assume that there is nothing wrong with hunting non-human apes because they are just animals. However, hunting humans is wrong because it is murder. How can I re-write the above bounded type parameter to exclude humans as a legal parameter? I am not interested in exceptions here. I do not want the hunt() method to compile at all if invoked with a human parameter.


Solution

  • You can't exclude a specific subclass the way you intend there. What you can do however, is create an interface 'isHuntable', implemented by the required animals you want to hunt, and you use that as a type for your method instead of the generic type bound. Another maybe less elegant solution is to create another level in your hierarchy called 'Monkeys' for example which extend from Ape, having all the animals extending from it, and you use that Monkey type for your method. I'd go with the first approach though. You could use explicit type checking, but you'd be breaking the 'open-closed' principle in you code, so it's better to leverage those checks to the type system.

    Just extending a bit on the concept of interface behaviour-contract which is powerful tool which is sadly underused. What you have among your Apes is a "is-a" relationship which tightly couples your subjects. Being "Huntable" on the other hand, is not an inherent or defining characteristic of the structure of that hierarchy, and is more an added condition/behaviour/check you'd like to add to a subset of that hierarchy. That is better achieved by adding contracts (interface implementation) to the subset you intend to have that behaviour. That will allow to add further contracts (interface implementations) in the future with minimal refactorings, maximal semantic clarity, less bugs and without tightly bounding a type to a behaviour. In short, you don't break the Liskov Substitution principle, nor the open closed principle, nor any SOLID principle.