javaenumsconstantscohesion

Using Enum to store constants and use those constants in multiple classes


I'm currently writing a genetic algorithm solving the traveling salesman problem. There are some "constants" I use at multiple places. Those values, however, need to be precalculated, thus, I can't store them into a private static final variable. Hence, I decided to use an Enum.

public enum Constants {
    NODE_COUNT(0),
    SEQUENCE_LENGTH(0),
    POPULATION_SIZE(0),
    MAX_EDGE_WEIGHT(0),
    MUTATION_RATE(0);

    private int value;
    private boolean alreadySet = false;

    Constants(int value) {
        this.value = value;
    }

    public void setValue(int value) {
        if (!alreadySet) {
            this.value = value;
            this.alreadySet = true;
        } else {
            throw new AssertionError("Value is already set.");
        }
    }

    public int get() {
        return value;
    }
}

My question is, do you consider this a good approach? I'm not sure whether this lowers the cohesion of each class using the enum. Thanks in advance.


Solution

  • As commented, a constant that changes value is not a constant.

    While you certainly can store changing state on an enum, you should ask yourself if doing so is a good idea. Generally not, in my opinion. Enum objects are usually used as constants, hence the convention of naming in all uppercase.

    Map

    You can accomplish the goal of having type-safe limited set of objects (enums) along with more obviously changing state by using a Map. A map would pair each enum object with an object of a custom type to contain your changing state.

    EnumMap

    The EnumMap class is an implementation of Map that is highly efficient, taking little memory and little CPU.

    Here is some untested code as an example.

    enum TravSalesAspect NODE_COUNT, SEQUENCE_LENGTH, POPULATION_SIZE, MAX_EDGE_WEIGHT, MUTATION_RATE ;
    record AlgorithmState ( int val , boolean alreadySet ) {} 
    
    final Map< TravSalesAspect , AlgorithmState > map = new EnumMap<>() ;
    

    Our map is empty. So we want to initialize with some default values.

    We access an array of all the enum objects. From that array, we make a stream. For each element in the stream, we put a key-value entry into the map.

    Arrays.stream( TravSalesAspect.values() ).forEach( aspect -> map.put( aspect , new AlgorithmState( 0 , false ) ) ) ;  // Initializing the map.
    

    When you want to later set the state:

    if( ! map.get( someAspect ).alreadySet() ) 
    {
        map.put( someAspect , new AlgorithmState( 42 , true ) ) ; 
    }
    

    By the way, note that in Java 16+ both enum and record can be defined locally if you so desire.

    Of course the code shown here is not thread-safe; beware.