Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Sets" of a particular enum type but with generics

Let's say I have an abstract class

public abstract class Trainer<T extends Animal>{} 

I have specific trainers like :

public DogTrainer extends Trainer<Dog>{} public HorseTrainer extends Trainer<Horse>{} 

Each of these 'trainers' has a set of fixed tricks they can train the animal to do, which I'd like to use Enums for. So I have an interface:

public interface TrainingActions<T extends Animal>{} 

and in each of the trainers, I have an Enum that implements this interface. So:

public DogTrainer extends Trainer<Dog>{   public enum Trainables implements TrainingActions<Dog>{     BARK, BITE, ROLLOVER, FETCH;   } }  public HorseTrainer extends Trainer<Horse>{   public enum Trainables implements TrainingActions<Horse>{     JUMP, TROT, REARUP;   } } 

Now in the each of the Trainer classes, I'd like a method say 'trainingComplete' that takes one of the Enums as an input and saves it to a set. So

public DogTrainer extends Trainer<Dog>{   public enum Trainables implements TrainingActions<Dog>{     BARK, BITE, ROLLOVER, FETCH;   }   public Set<Trainables> completed = new HashSet<Trainables>();   public void trainingComplete(Trainables t){completed.add(t);} } 

However, instead of defining the 'completed' set in each of the specific Trainers and the 'trainingComplete' method in each of the trainers, I'd like something in the parent 'Trainers' class that can be Enum-type enforced... So it's a weird combination of Enums and generics.

Is this possible?

like image 633
anishthecoder Avatar asked Oct 11 '13 03:10

anishthecoder


People also ask

Can enums be generic?

However, it is possible to use enums in generics. The MSDN article for Enum gives the following type definition for the class Enum . This definition can be used to get enum s working as generic types by constraining the generic type to those of Enum .

Can enum have different types?

I must answer a resounding no because actually you can't. Enums have their own data type and each enum is essentially a new data type.

Can enums be generic Java?

Java enums will be enhanced with generics support and with the ability to add methods to individual items, a new JEP shows. Since both features can be delivered with the same code change, they are bundled together in the same JEP. The change only affects the Java compiler, and therefore no runtime changes are needed.

Can enums be subclassed?

We've learned that we can't create a subclass of an existing enum. However, an interface is extensible. Therefore, we can emulate extensible enums by implementing an interface.


2 Answers

To specify a bound to an interface and that it be an enum, you need a generic intersection, which looks like:

class MyClass<T extends Enum<T> & SomeInterface> {} 

Note that when intersecting a class and an interface(s), the class must appear before the interface(s).

In this case, the generics Kung Fu you want is one level more complicated, because the interface of the TrainingActions enum must itself refer back to the animal type.

class Trainer<A extends Animal, T extends Enum<T> & TrainingActions<A>> {} 

A complete working example, based on your posted code, that compiles is:

public class Animal {}  public interface TrainingActions<T extends Animal> {}  /**  * A trainer that can teach an animal a suitable set of tricks  * @param <A> The type of Animal  * @param <T> The enum of TrainingActions that can be taught to the specified Animal  */ public abstract class Trainer<A extends Animal, T extends Enum<T> & TrainingActions<A>> {     private Set<T> completed = new HashSet<T>();     public void trainingComplete(T t) {         completed.add(t);     } }  public class Dog extends Animal {};  public class DogTrainer extends Trainer<Dog, DogTrainer.Trainables> {     public enum Trainables implements TrainingActions<Dog> {         BARK, BITE, ROLLOVER, FETCH;     } } 

But I would go one step further and define several TrainingActions enums in the class of the particular Animal to which they apply:

public class Dog extends Animal {     public enum BasicTrainables implements TrainingActions<Dog> {         SIT, COME, STAY, HEEL;     }     public enum IntermediateTrainables implements TrainingActions<Dog> {         BARK, BITE, ROLLOVER, FETCH;     }     public enum AdvacedTrainables implements TrainingActions<Dog> {         SNIFF_DRUGS, FIND_PERSON, ATTACK, GUARD;     } };  public class PuppyTrainer extends Trainer<Dog, Dog.BasicTrainables> {}  public class ObedienceTrainer extends Trainer<Dog, Dog.IntermediateTrainables> {}  public class PoliceTrainer extends Trainer<Dog, Dog.AdvacedTrainables> {} 
like image 100
Bohemian Avatar answered Oct 09 '22 07:10

Bohemian


Despite design analysis, I think the most your-situation (closest to your already created design) answer is:

  • your DogTrainer.Trainables is TrainingActions<Dog>
  • your HorseTrainer.Trainables is TrainingActions<Horse>

Generally, your Trainables is already a TrainingActions<T extends Animal>

so you can just provide Set<TrainingActions<T>> completed, because you already have your animal information in T:

abstract class Trainer<T extends Animal> {     Set<TrainingActions<T>> completed = new HashSet<TrainingActions<T>>();      public void add(TrainingActions<T> t ) {         completed.add( t );     } } 

And you have exactly what you want.

Usage:

DogTrainer tr = new DogTrainer(); tr.add( DogTrainer.Trainables.BITE ); 

If you are sure you want to enforce an Enum for your interface:

public <E extends Enum<?> & TrainingActions<T>> void add( E t ) {    completed.add( t ); } 
like image 21
Piotr Müller Avatar answered Oct 09 '22 06:10

Piotr Müller