Samuel Carrijo
Samuel Carrijo

Reputation: 17929

Java enums: Gathering info from another enums

I made a similar question a few days ago, but now I have new requirements, and new challenges =). As usual, I'm using the animal enums for didactic purposes, once I don't want to explain domain-specific stuff

I have a basic enum of animals, which is used by the whole zoo (I can add stuff to it, but must keep compatibility):

public enum Animal {
  DOG,
  ELEPHANT,
  WHALE,
  SHRIMP,
  BIRD,
  GIRAFFE;
}

I need to categorize them in a few, non-related categories, like gray animals (whale (my whale is gray) and elephant), small animals (bird, shrimp and dog), sea animals (whale and shrimp).

I could, as suggested in my previous questions, add a lot of booleans, like isGray, isSmall and isFromSea, but I'd like an approach where I could keep this somewhere else (so my enum doesn't need to know much). Something like:

public enum Animal {
  DOG,
  ELEPHANT,
  WHALE,
  SHRIMP,
  BIRD,
  GIRAFFE;

  public boolean isGray() {
    // What comes here?
  }
}

Somewhere else

public enum GrayAnimal {
  WHALE,
  ELEPHANT;
}

How is this possible? Am I requesting too much from Java?

Upvotes: 3

Views: 885

Answers (6)

Bill K
Bill K

Reputation: 62759

Remember that enums are only useful when you need to differentiate objects in your code--they are useless except that they can be typed in as code.

This is relevant because you are introducing elements into your software that will turn out to be bad code smells in the long run.

For instance, how do you use these except in statements like:

if(critter.type == WHALE)
    critter.movement=WATER;
else if(critter.type == ELEPHANT)

This should instantly alert any OO programmer--switches are a bad code smell since they almost always indicate bad OO design).

The alternative is to create a finite set of objects, initialized by data, preferably not code.

You might have an instance of critter with the attributes of a whale--perhaps whale.move() would use an instance of WaterMovement whereas the elephant contains and uses an instance of LandMovement.

In general, programming in OO instead of using switches and enums will collapse an amazing amount of code.

Every time you write a method, remember the mantra "Don't ask an object for data and then operate upon the object, instead ask the object to do an operation for you".

Upvotes: 1

Dean Povey
Dean Povey

Reputation: 9446

I think that it is best not to pollute your enum with this sort of categorization. It is best to decouple the categories from the enum so you can add more later without affecting your enum. This follows the seperation of concerns and single responsibility principle for class design.

To do this, just use an EnumSet to hold the instances, viz:

public enum Animal {
  DOG,
  ELEPHANT,
  WHALE,
  SHRIMP,
  BIRD,
  GIRAFFE;
}

public static final EnumSet<Animal> GRAY_ANIMALS = EnumSet.of(ELEPHANT, WHALE);

If you want to add functionality above simple membership, or you want a bit more syntactic sugar extend EnumSet

public class GrayAnimals extends EnumSet<Animal> {
    public static final GrayAnimals INSTANCE = new GrayAnimals(ELEPHANT, WHALE);
    private GrayAnimals(Animal...animals) {
        Collections.addAll(this, animals);
    }
    public boolean isGray(Animal animal) { return contains(animal); }
    // ... other methods
}

Upvotes: 0

Ray Tayek
Ray Tayek

Reputation: 10003

maybe something like this:

package p;
import java.util.*;
enum Type {
    small,big,grey;
}
enum Animal {
    bird(EnumSet.of(Type.small)),whale(EnumSet.of(Type.big, Type.grey)),elephant(EnumSet.of(Type.big, Type.grey));
    Animal(final EnumSet<Type> types) { this.types=types; }
    EnumSet<Type> types=EnumSet.noneOf(Type.class);
    boolean is(final Type type) { return types!=null?types.contains(type):false; }
    public static void main(String[] arguments) {
        for(Animal a:values()) {
            System.out.println(a+" "+a.types);
        }
    }
}

Upvotes: 0

Mihir Mathuria
Mihir Mathuria

Reputation: 6539

Did you try EnumSet or EnumMap?

You can create a method

Set<Animal> grayAnimals(){
   return EnumSet.of(Animal.WHALE, Animal.ELEPHANT);
}

Upvotes: 8

sfussenegger
sfussenegger

Reputation: 36096

I think it would be best to store such properties in the enum instances themselves, i.e.

public enum Animal {
  DOG(NOT_GRAY),
  ELEPHANT(GRAY),
  WHALE(GRAY),
  SHRIMP(NOT_GRAY),
  BIRD(NOT_GRAY),
  GIRAFFE(NOT_GRAY);

  private static boolean GRAY = true;
  private static boolean NOT_GRAY = !GRAY;

  private Animal(boolean isGray) {
    // snip
  }
}

You could even encode several boolean properties into one byte (or use BitSet instead);

public enum Animal {
  DOG(),
  ELEPHANT(GRAY | BIG),
  WHALE(GRAY | BIG),
  SHRIMP(),
  BIRD(),
  GIRAFFE(BIG);

  private static byte GRAY = 0x01;
  private static byte BIG = GRAY << 1;

  private final byte _value;

  private Animal() {
    this(0x00);
  }

  private Animal(byte value) {
    _value = value;
  }

  public boolean isGray() {
    return _value & GRAY != 0x00;
  }

  public boolean isBig() {
    return _value & BIG != 0x00;
  }
}

Nevertheless, what about simply doing this:

public class GrayAnimal {
  public static final Animal ELEPHANT = Animal.ELEPHANT;
  public static final Animal WHALE = Animal.WHALE;
}

or something like this

public enum Animal {
  DOG,
  ELEPHANT,
  WHALE,
  SHRIMP,
  BIRD,
  GIRAFFE;

  // thanks to Mihir, I would have used a regular HashSet instead
  public static final Set<Animal> GRAY = Collections.unmodifiableSet(EnumSet.of(ELEPHANT, WHALE));
}

Upvotes: 2

wsorenson
wsorenson

Reputation: 5961

I don't know why you want to put it in another enum, when you could put it in that function:

public boolean isGray() {
     return this == WHALE || this == ELEPHANT;
}

Upvotes: 0

Related Questions