开发者

Create an extended "enum" with runtime attributes

I want to simplify the selection of a particular predefined object.

I currently have an enum defined as

ArtworkType { Poster, Banner, Other }

And I want to add attributes to those ArtworkTypes so that I can use them in code elsewhere. The attributes of the ArtworkTypes are either pre-defined static labels or populated from an external configuration file that is populated into a Properties() class.

Ideally I want to do something as simple as

ArtworkType.Poster.getWidth();

If I have to use a final class, I think it's going to be more complicated having to use something like

ArtworkType.getWidth(TypeEnum.Poster);


EDIT: Thanks for the answers below, I conclude that whilst I can do开发者_StackOverflow中文版 it with an Enum, it's probably better to use an external class (e.g. ArtworkUtil) to retrieve the attributes I'm after.

This is the sample enum code I've created so far (error checking omitted):

public enum ArtworkType {
    Poster("poster"), Banner("banner"), Other("other");

    private String type;
    private Dimension dimension;

    private ArtworkType(String type) {
        this.type = type;
        this.dimension = new Dimension(Properties.getProperty("width."+type), Properties.getProperty("height."+type);
    }

    public Dimension getDimension() {
        return dimension;
    }
}

While I appreciate this is against the principles of strict Enums, as the values that are associated with the Enum are static (for the duration of the application) it might be the lesser of two evils.

The only other approach that I can think of is to create an "ArtworkUtil" class that creates a collection and populates all the required attributes into an object and stores that in the collection.

Accessing that class in the code would make it a lot more unreadable (unless I'm missing something?)


Enums are compile-time constants. You can not initialize them from property files.

They can however have constructors, methods and fields. They can also have basic method implementations in the main body that are overwritten in the individual enum entries.

public enum Shape{
    SQUARE(10),
    RECTANGLE(10, 15),
    CIRCLE(10){
        @Override
        public double getArea(){
            return Math.PI * Math.pow(((double) getWidth()) / 2, 2);
        }
    },
    OVAL(10, 15){
        @Override
        public double getArea(){
            return Math.PI * (getWidth()) / 2 * (getHeight()) / 2;
        }
    };

    private Shape(final int dim){ this(dim, dim); }
    private Shape(final int width, final int height){
        this.width = width; this.height = height;
    }

    private final int width;
    private final int height;

    public double getArea(){ return width * height; }

    public final int getWidth(){ return width; }

    public final int getHeight(){ return height; }

}

Test code:

public static void main(final String[] args){
    for(final Shape shape : Shape.values()){
        System.out.printf("Shape: %s, area: %1.2f\n", shape,
            shape.getArea());
    }
}

Output:

Shape: SQUARE, area: 100.00
Shape: RECTANGLE, area: 150.00
Shape: CIRCLE, area: 78.54
Shape: OVAL, area: 117.81


Dealing with Compile-Time restrictions

Enums are compile-time constants, so you can not initialize them using non-constant values, which means that you can't do this:

SQUARE(SomeClass.getSquareValue())

So you basically have three options:

  1. Generate the enum automatically from your properties file

    Use a build tool like Maven and have some code generate an enum file for you, converting this format:

    ENUMNAME=property.value
    

    into this enum entry:

    ENUMNAME("property.value")
    

    Add the generated enum .java to your compile sources. From the Java point of view, this is the cleanest approach, as you have absolute compile-time safety. The problem is: you need to re-compile every time the property file changes.

  2. Lazy-initialize the enum (argh)

    Initialize the enum items with property keys, and when the enum's method is first called, look up the property from the classpath, caching it for further use. This is an awful hack, but it is useful sometimes.

  3. Pass all values into the enums from the outside, using them as strategy:

    Example: this enum looks up System properties with a different prefix per enum entry.

    public enum Lookup{
         ADDRESS("$1".address),
         NAME("$1".name);
         private final String pattern;
         private Lookup lookup(String pattern){
             this.pattern=pattern;
         }
         public final String lookupProperty(String input){
             return System.getProperties().get(
                 this.pattern.replace("$1",input)
             );
         }
     }
    


public enum ArtworkType {
    Poster, Banner, Other;

    private static ResourceBundle properties;

    private static ResourceBundle getProperties() {
        if( properties == null ) {
            properties = ResourceBundle.getBundle( "artworks");
        }
        return properties;
    }

    public int getWidth() {
        return Integer.parseInt( getProperties().getString( "width."+this.name() ) );
    }
  }

This is the rough idea of what you need to do. Of course this isn't really production quality code, some safety measures were omitted for the sake of readability.

And your property file will be something like this:

artworks.properties:

width.Poster=500
width.Banner=1900
width.Other=-1


If you want a getWidth() method you can add it to an enum just like any other class type. Perhaps you could clarify your concern.

From the documentation on enum

public enum Planet {
    MERCURY (3.303e+23, 2.4397e6),
    VENUS   (4.869e+24, 6.0518e6),
    EARTH   (5.976e+24, 6.37814e6),
    MARS    (6.421e+23, 3.3972e6),
    JUPITER (1.9e+27,   7.1492e7),
    SATURN  (5.688e+26, 6.0268e7),
    URANUS  (8.686e+25, 2.5559e7),
    NEPTUNE (1.024e+26, 2.4746e7),
    PLUTO   (1.27e+22,  1.137e6);

    private final double mass;   // in kilograms
    private final double radius; // in meters
    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
    }
    public double mass()   { return mass; }
    public double radius() { return radius; }

    // universal gravitational constant  (m3 kg-1 s-2)
    public static final double G = 6.67300E-11;

    public double surfaceGravity() {
        return G * mass / (radius * radius);
    }
    public double surfaceWeight(double otherMass) {
        return otherMass * surfaceGravity();
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜