tags:

views:

170

answers:

6

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?

A: 

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;
}
wsorenson
This looks good for a small example. But if you grow a little (say 5 categories, with 10 animals each), it starts being very big for a enum. I wanted it to be as dumb as possible
Samuel Carrijo
+8  A: 

Did you try EnumSet or EnumMap?

You can create a method

Set<Animal> grayAnimals(){
   return EnumSet.of(Animal.WHALE, Animal.ELEPHANT);
}
Mihir Mathuria
+1 didn't know about EnumSet. cool stuff!
sfussenegger
Was about to post a similar answer.. I find it cleaner to have sets instead of returning booleans. It's much easier to extend upon, too. +1
Enno Shioji
This seems to be a good one. Gonna try it (and accept it in case it makes me happy =P)
Samuel Carrijo
This also allows easy iteration over a category's members.
meriton
+1  A: 

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));
}
sfussenegger
A: 

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);
        }
    }
}
Ray Tayek
+1  A: 

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".

Bill K
A: 

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
}
Dean Povey