Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Visibility of enum values in Java

Is it possible to somehow mark certain enum values in Java as package-private, i.e. give them the default modifier?

Background (only to preempt the otherwise immediate first comment "What for?" ;) )

I have a Task-object with different execution-methods and an execution-state that decides which method to call next. Each one of the execution-methods returns the execution-state of the next method to be called (basically a framework for executing a state-machine).

I have an enum that contains all possible execution-states, but also contains a few "package-internal" states like "pending" or "failed" that should not be returnable by the execution-methods.

I know I could manage these states in a separate variable with its own enum, but that would make the code a lot less clean as it turns a single switch-statement into (at least) two (and possibly a surrounding if). Also, I could, of course, just check the return value, but I'd rather not even make wrong ones available in the first place.

like image 568
Markus A. Avatar asked Jul 23 '13 16:07

Markus A.


People also ask

Can enums be private in Java?

Enum constructors are always private. We can't create instance of enum using new operator. We can declare abstract methods in java enum, then all the enum fields must implement the abstract method.

Are enums always public?

Enum constructors must be private (to prevent people creating extra constants) and are private implicitly (JLS §8.9. 2).

Is enum is public or private?

A Public procedure is visible to all modules in a project, while a Private Enum type is not visible outside its own module.

Can Java enums have values?

Overview. The Java enum type provides a language-supported way to create and use constant values. By defining a finite set of values, the enum is more type safe than constant literal variables like String or int.


2 Answers

Sounds like the simple answer is "No."

But, thinking about the different comments and answers (particularly by Marcelo, BlackVegetable and OldCurmudgeon), I have come up with the following workaround:

A package-private enum contains all values:

enum PackagePrivateEnum {
    PUBLIC_VALUE_1,
    PUBLIC_VALUE_2,
    PUBLIC_VALUE_3,
    PACKAGE_PRIVATE_VALUE_1,
    PACKAGE_PRIVATE_VALUE_2;
}

A second public enum contains only the public values, and directly maps these to the package-private ones:

public enum PublicEnum {
    PUBLIC_VALUE_1 (PackagePrivateEnum.PUBLIC_VALUE_1),
    PUBLIC_VALUE_2 (PackagePrivateEnum.PUBLIC_VALUE_2),
    PUBLIC_VALUE_3 (PackagePrivateEnum.PUBLIC_VALUE_3);

    final PackagePrivateEnum value;

    private PublicEnum(PackagePrivateEnum value) {
        this.value = value;
    }
}

Now, if I have a function that is only allowed to return one of the public values, I define it as:

public abstract PublicEnum returnSomething();

and can then use it in the package via:

PackagePrivateEnum value = returnSomething().value;

This hides the unwanted values from the public and, I believe, simultaneously minimizes coding- and performance-overhead inside the package (e.g. no switch- or if-statements, no Map-lookups, etc., just a .value required). In fact, with a smart compiler like GWT, the return-value should probably get "inlined" to the point that even the .value-lookup is removed completely, i.e. no performance-overhead at all.

Also, with this, it is possible to define an arbitrary number of different allowed subsets of a big collective enum for different contexts: I could easily define another PublicEnum2 that exposes an entirely different set of values from the PackagePrivateEnum.

like image 189
Markus A. Avatar answered Oct 15 '22 11:10

Markus A.


You are having difficulty because you are using the wrong pattern.

Your Tasks should not return the next state. You should use a matrix of States to control the flow. This way your flow is not tangled up inside the tasks and the States remain private to the flow system.

If you want your Task to control the flow they should return something (perhaps success/failure) to influence the flow controller. They should not define the next state, they should influence the next state.

Added

Here's a slightly contrived example of what I mean. Notice how the Tasks are attached to each State and the flow is controlled by a Map that merely holds each state transition.

I have made a token effort to match your returning results but I suspect that just overcomplicates matters and once you embrace the separation of flow from state you will realise what I am trying to explain.

public class Test {
  public void test() {
    new Thread(new Engine()).start();
  }

  static final Map<State, State> flow = new HashMap<>();

  static {
    flow.put(State.Start, State.A);
    flow.put(State.A, State.B);
    flow.put(State.B, State.Finished);
  }

  public static class Engine implements Runnable {
    State state = State.Start;

    @Override
    public void run() {
      while (state != State.Finished) {
        System.out.println("State: "+state);
        // Perform all tasks of this state.
        for ( Task task : state.tasks ) {
          System.out.println("Task: "+task);
          Result result = Result.Start;
          // Keep performing until completed.
          while ( result != Result.Completed ) {
            System.out.println("Result: "+result);
            result = result.perform(task);
          }
          System.out.println("Result: "+result);
        }
        // All tasks performed! Next state.
        state = flow.get(state);
      }
      System.out.println("State: "+state);
    }
  }

  enum State {
    Start, 
    A(Task.One, Task.Two), 
    B(Task.Two), 
    Finished;
    Iterable<Task> tasks;

    State(Task... tasks) {
      this.tasks = Arrays.asList(tasks);
    }
  }

  enum Result {
    Start {
      @Override
      Result perform(Task t) {
        return t.initialise();
      }
    },
    Executing {
      @Override
      Result perform(Task t) {
        return t.execute();
      }
    },
    Finalising {
      @Override
      Result perform(Task t) {
        return t.finalise();
      }
    },
    Completed {
      @Override
      Result perform(Task t) {
        // Stop there.
        return Completed;
      }
    };

    abstract Result perform(Task t);
  }

  enum Task {
    One {
      @Override
      Result initialise() {
        return Result.Executing;
      }

      @Override
      Result execute() {
        return Result.Finalising;
      }

      @Override
      Result finalise() {
        return Result.Completed;
      }
    },
    Two {
      @Override
      Result initialise() {
        return Result.Executing;
      }

      @Override
      Result execute() {
        return Result.Finalising;
      }

      @Override
      Result finalise() {
        return Result.Completed;
      }
    };

    abstract Result initialise();

    abstract Result execute();

    abstract Result finalise();
  }

  public static void main(String args[]) {
    try {
      new Test().test();
    } catch (Throwable t) {
      t.printStackTrace(System.err);
    }
  }
}

Added

Simplifying this by removing your requirement to control the flow through the results of the task methods we get:

public class Test {
  public void test() {
    new Thread(new Engine()).start();
  }

  static final Map<State, State> flow = new HashMap<>();

  static {
    flow.put(State.Start, State.A);
    flow.put(State.A, State.B);
    flow.put(State.B, State.Finished);
  }

  public static class Engine implements Runnable {
    State state = State.Start;

    @Override
    public void run() {
      while (state != State.Finished) {
        System.out.println("State: "+state);
        // Perform all tasks of this state.
        for ( Task task : state.tasks ) {
          System.out.println("Task: "+task);
          task.initialise();
          task.execute();
          task.finalise();
        }
        // All tasks performed! Next state.
        state = flow.get(state);
      }
      System.out.println("State: "+state);
    }
  }

  enum State {
    Start, 
    A(Task.One, Task.Two), 
    B(Task.Two), 
    Finished;
    Iterable<Task> tasks;

    State(Task... tasks) {
      this.tasks = Arrays.asList(tasks);
    }
  }

  enum Task {
    One {
      @Override
      void execute() {
      }
    },
    Two {
      @Override
      void execute() {
      }
    };

    // Nothing by default.
    void initialise() {
    }

    abstract void execute();

    // Nothing by default.
    void finalise() {
    }

  }

  public static void main(String args[]) {
    try {
      new Test().test();
    } catch (Throwable t) {
      t.printStackTrace(System.err);
    }
  }
}

which, I think, demonstrates the separation of flow control from task execution I was trying to get across.

like image 43
OldCurmudgeon Avatar answered Oct 15 '22 11:10

OldCurmudgeon