【问题标题】:Proper way to declare and set a private final member variable from the constructor in Java?从Java中的构造函数声明和设置私有最终成员变量的正确方法?
【发布时间】:2010-12-17 12:01:47
【问题描述】:

有不同的方法可以从构造函数中设置成员变量。我实际上是在讨论如何正确设置最终成员变量,特别是由辅助类加载条目的映射。

public class Base {
    private final Map<String, Command> availableCommands;
    public Base() {
        availableCommands = Helper.loadCommands();  
    }
}

在上面的例子中,帮助类看起来像这样:

public class Helper {
    public static Map<String, Command> loadCommands() {
        Map<String, Command> commands = new HashMap<String, Command>();
        commands.put("A", new CommandA());
        commands.put("B", new CommandB());
        commands.put("C", new CommandC());

        return commands;
    }
}

我的想法是,使用方法在构造函数中设置这样的变量是更好的做法。所以 Base 类看起来像这样:

public class Base {
    private final Map<String, Command> availableCommands;
    public Base() {
        this.setCommands();  
    }
    private void setCommands() {
        this.availableCommands = Helper.loadCommands();
    }
}

但现在我无法维护 final 修饰符并得到编译器错误(无法设置最终变量)

另一种方法是:

public class Base {
    private final Map<String, Command> availableCommands = new HashMap<String, Command>();
    public Base() {
        this.setCommands();
    }
    private void setCommands() {
        Helper.loadCommands(availableCommands);
    }
}

但在这种情况下,Helper 类中的方法将更改为:

public static void loadCommands(Map<String, Command> commands) {
    commands.put("A", new CommandA());
    commands.put("B", new CommandB());
    commands.put("C", new CommandC());
}

所以区别在于我在哪里使用new HashMap&lt;String, Command&gt;(); 创建一个新地图?我的主要问题是是否有推荐的方法来执行此操作,因为部分功能来自此 Helper's静态方法,作为使用条目加载实际地图的一种方式?

我是在我的 Base 类还是 Helper 类中创建新地图? 在这两种情况下,Helper 将执行实际加载,并且 Base 对包含具体命令的地图的引用将是私有的和最终的。

除了我正在考虑的选项之外,还有其他更优雅的方法吗?

【问题讨论】:

  • 您以这种方式创建的地图是不可变的吗?然后有一个相对不错的选择。
  • 好吧,一旦设置好它们就不应该改变。至少不经常
  • 我猜地图应该是可变的,否则你会无缘无故地创建不可变地图的多个副本......
  • 你不需要把“this”放在你的方法前面。

标签: java constructor final private-members


【解决方案1】:

根据您的第一个代码 sn-p,帮助程序类创建地图对我来说似乎是完全合理的。您正在在构造函数中设置变量 - 我看不到问题。

正如 yawn 所说,在这里使地图不可变是一个不错的选择,但除此之外,我只使用第一个 sn-p 中的代码。

(顺便说一下,我假设在现实生活中这确实需要是一个实例变量,而不是静态变量?)

【讨论】:

  • 地图是Base类中的一个实例变量,但它是通过Helper的静态方法加载的。
  • @joel_nc:我可以看到它在这种情况下 - 但它必须是吗?内容是否因实例而异?
  • 我明白你的意思,通过使此地图不可变类似于使其成为静态,因此不同的基地看到相同的命令?
  • 我同意,主要是。如果是我,并假设 Base 是唯一使用此特定命令映射的类,我将移动 loadCommands 实用程序 Helper 成为 Base 类本身的私有静态成员。跨度>
  • 只是想问一下我是否理解正确:一个不可变的集合,与静态最终成员或只是静态成员相同?
【解决方案2】:
  1. 如果您希望它不可变,则不需要使用 3rd 方 API,您可以使用:java.util.Collections.unmodifiableMap(Map m)

  2. 最常见的方法是:

公共类基{ 私有最终地图可用命令; 公共基础(){ 可用命令=新哈希映射(); // 或您希望加载的任何其他类型的地图 availableCommands = Helper.loadCommands(availableCommands); } }

【讨论】:

  • 我想知道这样的初始化是否实际上是足够不可变的:内存模型。具体来说,我不确定availableCommands 的分配顺序和其内容的写入是否相关。
  • @Tuure:这不是一个开创性的概念,但我可以看到的一个示例用途是保证使用的 Map 实现。例如,我可以将其实例化为 TreeHashMap 以保证元素将根据其自然顺序进行排序。当然,在这种特殊情况下,由于变量是私有的,因此您在分配它时可以控制它,因此将其标记为 final 有点多余。
【解决方案3】:

如果您希望此类映射不可变,请查看Google Collection API。引用链接的文档:

static final ImmutableMap<String, Integer> WORD_TO_INT =
       new ImmutableMap.Builder<String, Integer>()
           .put("one", 1)
           .put("two", 2)
           .put("three", 3)
           .build();

【讨论】:

    【解决方案4】:

    您是否考虑过使用类似Effective Java 2nd ed. 中的构建器模式?

    您可以在一个地方捕获所有地图构造逻辑(因此您不需要维护 2 个单独的类)。基地看起来像这样:

    public class Base {
    
        private final Map<String, Command> commands;
    
        private Base(Builder b) {
            commands = b.commands;
        }
    
        public static class Builder() {
    
            private final Map<String, Command> commands;
    
            public Builder() {
                commands = new HashMap<String, Command>();
            }
    
            public Builder addCommand(String name, Command c) {
                commands.put(name, c);
                return this;
            }
    
            public Base build() {
                return new Base(this);
            }
        }
    }
    

    Base 的客户端现在可以这样工作:

    Base b = new Base.Builder().addCommand("c1", c1).addCommand("c2", c2).build();
    

    结果是客户端类不需要知道他们需要构建一个地图,您基本上可以用 1 行来构建它。缺点是 Base 无法扩展,因为构造函数现在是私有的(也许你想要,也许你不想要)。

    编辑:在 build() 中有一个错误,我传递了命令而不是像我最初打算的那样 EDIT2:错误地称为 add 而不是放入 Base.Builder.addCommand

    【讨论】:

      【解决方案5】:

      你为什么不这样做

      private final Map<String, Command> availableCommands = Helper.loadCommands();  
      

      ?

      【讨论】:

        【解决方案6】:

        我个人会将 Helper 类重命名为 CommandHolder:

        public class CommandHolder {
            private static Map<String, Command> availableCommands;
            private static CommandHolder instance;
        
            private CommandHolder{}
            public static synchronized Map<String, Command> getCommandMap() {
                if (instance == null) {
                    instance = new CommandHolder();
                    instance.load();
                }
                return availableCommands
            }
            private void load() {
                ...
            }
        }
        

        同步以确保加载只发生一次。没有 getCommand 因为 然后还必须同步那个,并且每次查找都会更昂贵。 我假设地图是只读的,否则在多线程环境中您无论如何都需要同步地图。

        【讨论】:

          【解决方案7】:

          你也可以使用双大括号初始化,虽然你是否认为它更干净可能是个人喜好问题,但至少有一个好处是所有初始化代码都放在一个地方:

          public class Base {
              public final Map< String, Command > availableCommands;
          
              public Base() {
                  availableCommands = Collections.unmodifiableMap( new HashMap() {
                      {
                          put( "A", new CommandA() );
                          put( "B", new CommandB() );
                      }
                  } );
              }
          }
          

          【讨论】:

          • 这样的结果是一个比谷歌 ImmutableMap 更内存膨胀和查询成本更高的地图。哦,你会收到来自 javac 的串行警告。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-08-04
          • 2011-04-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-10-26
          相关资源
          最近更新 更多