Java中枚举值的可见性

Posted

技术标签:

【中文标题】Java中枚举值的可见性【英文标题】:Visibility of enum values in Java 【发布时间】:2013-07-22 20:12:07 【问题描述】:

是否可以以某种方式将 Java 中的某些 enum 值标记为包私有,即给它们默认修饰符?

背景(仅用于抢占其他直接的第一条评论“为什么?”;))

我有一个Task-object,它有不同的执行方法和一个决定接下来调用哪个方法的执行状态。每个执行方法都返回下一个要调用的方法的执行状态(基本上是一个用于执行状态机的框架)。

我有一个enum,其中包含所有可能的执行状态,但也包含一些“包内部”状态,例如“待处理”或“失败”,这些状态不应由执行方法返回。

我知道我可以用自己的枚举在一个单独的变量中管理这些状态,但这会使代码变得不那么干净,因为它将单个 switch-statement 变成(至少)两个(并且可能是一个周围的if)。另外,我当然可以只检查返回值,但我宁愿一开始就不要提供错误的值。

【问题讨论】:

不,你不能将一些枚举常量标记为公开的和一些包私有的。 在这种情况下,也许你可以使用旧的 java 枚举,只是一个带有一些最终静态常量的类。 事实上,你不能给它们做任何标记。他们是public,就是这样。 您是否尝试过在枚举本身中使用一种方法来指示是否可以或将要返回某些值?它会将额外的切换逻辑推送到枚举“类”本身。 拥有修饰符会在values() 调用时中断 【参考方案1】:

您遇到了困难,因为您使用了错误的模式。

您的Tasks 不应返回下一个状态。您应该使用States 的矩阵来控制流量。这样,您的流程就不会在任务中纠结,States 对流程系统保持私有。

如果您希望您的 Task 控制流程,他们应该返回一些内容(可能是成功/失败)以影响流程控制器。他们不应该定义下一个状态,他们应该影响下一个状态。

已添加

这里有一个稍微做作的例子来说明我的意思。请注意Tasks 是如何附加到每个State 的,并且流由仅保存每个状态转换的Map 控制。

我已经做出了象征性的努力来匹配您返回的结果,但我怀疑这会使事情变得过于复杂,一旦您接受了流程与状态的分离,您就会意识到我要解释的内容。

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);
    
  

已添加

通过消除您通过我们获得的任务方法的结果来控制流程的要求来简化这一点:

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);
    
  

我认为,这展示了我试图跨越的流程控制与任务执行的分离。

【讨论】:

基本上,我的Task 有一个initialize-、一个execute- 和一个finalize-method。初始化和执行方法返回 EXECUTING、WAITING、FINALIZING 或 COMPLETED 状态,这将导致下一个调用 execute(),暂停任务,下一个调用 finalize(),或分别标记为完成的任务。用这 4 个可能的返回值定义第二个枚举并使用 switch 语句处理它真的是最好的解决方案吗?浪费... @MarkusA。 - 我已经发布了一些代码 - 也许这将有助于证明我的意思。 这段代码中有一些非常酷的想法!谢谢!直接在状态枚举上定义“执行”方法并使任务成为枚举值而不是类的有趣想法。另外,我不知道在枚举中,您可以定义一个抽象类并一次性实现它。有趣的。但看起来你的代码中有很多间接和查找,而且很长。但这绝对是有用的。 +1 我添加了一个简化。【参考方案2】:

听起来简单的答案是“不”。

但是,考虑到不同的 cmets 和答案(尤其是 Marcelo、BlackVegetable 和 OldCurmudgeon),我想出了以下解决方法:

package-private 枚举包含所有值:

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

第二个 public 枚举只包含公共值,并直接将它们映射到包私有的值:

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;
    

现在,如果我有一个只允许返回一个公共值的函数,我将其定义为:

public abstract PublicEnum returnSomething();

然后可以通过以下方式在包中使用它:

PackagePrivateEnum value = returnSomething().value;

这向公众隐藏了不需要的值,并且我相信,同时最大限度地减少了包内的编码和性能开销(例如,没有 switch 或 if 语句,没有 Map-lookups 等,只是一个 .value必需的)。事实上,使用像 GWT 这样的智能编译器,返回值可能应该被“内联”到甚至完全删除 .value-lookup 的程度,即根本没有性能开销。

此外,有了这个,可以为不同的上下文定义一个大集合枚举的任意数量的不同允许子集:我可以轻松定义另一个 PublicEnum2,它公开一组与 PackagePrivateEnum 完全不同的值.

【讨论】:

这个答案是我在评论中的目标。 +1 @BlackVegetable :) 现在说得通了!我已将您添加到顶部列表中... :)

以上是关于Java中枚举值的可见性的主要内容,如果未能解决你的问题,请参考以下文章

如何控制 Java 中变量的可见性?

Java中同步的可见性效果

枚举实现接口、接口和方法可见性

WPF 触发器绑定:将枚举值绑定到可见性的最佳方法是啥?

基于布尔值的 ListView 项目可见性

XAML 在多个选项之间切换可见性