I have an enum with > 10 items each having 8 static properties. Contrived example:

enum JavaTypes {
    INTEGER,
    BOOLEAN,
    STRING,
    ...;

    boolean isPrimitive() {
    }

    boolean isNumeric() {
    }
    ...
}

I am trying to find the most readable and maintainable (easy to add properties) style.

  1. The standard override getters doesn't work very well, as each enum body gets quite long and it's difficult to compare the same property across items.

  2. Setting all the properties in the constructor. Plagued by the problem of which argument is which. I tried to align the arguments into a table, but defeated by the hard 120-char limit we have.

  3. Non-final fields and instance initialiser: e.g.

    INTEGER {{ primitive = true; numeric = true; }},
    
    boolean primitive, numeric;
    
    boolean isPrimitive() {
        return primitive;
    }
    

    This feels a bit dirty though.

Is 3 acceptable? Is there any better option.

Edit:

Just to clarify, not all of the properties are booleans.

有帮助吗?

解决方案

One other option would be to use a second enum and a varargs constructor, along with an EnumSet:

enum JavaType {
    INTEGER(TypeProperty.PRIMITIVE, TypeProperty.NUMERIC),
    BOOLEAN(TypeProperty.PRIMITIVE),
    STRING(),
    /* ... */;

    private EnumSet<TypeProperty> properties;
    private JavaType(TypeProperty... properties) {
        this.properties = EnumSet.copyOf(Arrays.asList(properties));
    }

    public boolean isPrimitive() {
        return properties.contains(TypeProperty.PRIMITIVE);
    }

    public boolean isNumeric() {
        return properties.contains(TypeProperty.NUMERIC);
    }
    /* ... */

    private enum TypeProperty {
        PRIMITIVE,
        NUMERIC,
        /* ... */
    }
}

This has the benefit of not requiring to specify a bunch of false values in a constructor (the problem with #2) but also doesn't require a bunch of inner classes for each enum. The TypeProperty enum doesn't even need to be visible outside of JavaType for this to work.

Alternatively, TypeProperty can be made public and there can be a is(TypeProperty) method. Whether you want this or not depends on how you plan on using JavaType; it can be convenient to expose it (eg for making a allWith(TypeProperty) method) but if it's not something you'd be likely to use outside, don't expose it. Whether or not you actually want it depends on how you're going to be using your enum.

If TypeProperty is exposed, it could be combined with a static import so that you can just reference the enum constant names from within the type. This can help with readability if used well, but be careful with it. TypeProperty must be exposed to be imported, although it can still be mostly private if needed (it can still be within JavaType if needed, it just can't be private). Here's a sample:

package com.example;
import static com.example.TypeProperty.*;

enum JavaType {
    INTEGER(PRIMITIVE, NUMERIC),
    BOOLEAN(PRIMITIVE),
    STRING(),
    /* ... */;

    private EnumSet<TypeProperty> properties;
    private JavaType(TypeProperty... properties) {
        this.properties = EnumSet.copyOf(Arrays.asList(properties));
    }

    public boolean isPrimitive() {
        return properties.contains(PRIMITIVE);
    }

    public boolean isNumeric() {
        return properties.contains(NUMERIC);
    }
    /* ... */
}

enum TypeProperty {
    PRIMITIVE,
    NUMERIC,
    /* ... */
}

Just to clarify, not all of the properties are booleans.

If you have a few non-Boolean properties, you can put them at the start of the constructor and then follow it with the varargs param. You still can run into the unlabeled issue, but it lets you organize it more. Say, something like this:

enum JavaType {
    INTEGER("int", Integer.class, TypeProperty.PRIMITIVE, TypeProperty.NUMERIC),
    BOOLEAN("boolean", Boolean.class, TypeProperty.PRIMITIVE),
    STRING("String", String.class),
    /* ... */;

    private String name;
    private Class<?> representationClass;
    private EnumSet<TypeProperty> properties;
    private JavaType(String name, Class<?> representationClass, TypeProperty... properties) {
        this.name = name;
        this.representationClass = representationClass;
        this.properties = EnumSet.copyOf(Arrays.asList(properties));
    }
    /* ... code from before ... */
}

If you have a lot of non-Boolean properties, then it gets a lot harder to organize. At that point, approach #3 may be more reasonable, or alternatively you could combine approach #1 and varargs params.

其他提示

I'm not a huge fan of enums over using the Typesafe Enum pattern for this reason among others. The main advantage with using the built in enum feature is that you can use them in switch statements but I tend to prefer polymorphism anyway.

Anyway, another way to do this which uses the 'switchability' of enums is as follows. It's kind of ugly but so are enums in general:

public class EnumHolder
{
  public enum JavaTypes {
    INTEGER,
    BOOLEAN,
    STRING;

    boolean isPrimitive() {
      return EnumHolder.isPrimitive(this);
    }
  }

  private static final boolean isPrimitive(JavaTypes type) {
    switch (type) {
      case INTEGER:
      case BOOLEAN:
        return true;
      default:
        return false;
    }
  }
}
许可以下: CC-BY-SA归因
scroll top