【问题标题】:Why make private static final Lists/Sets/Maps unmodifiable?为什么使私有静态最终列表/集/地图不可修改?
【发布时间】:2012-12-10 01:23:41
【问题描述】:

我刚刚阅读了一些更有经验的程序员编写的代码,我遇到了以下内容:

public class ConsoleFormatter extends Formatter {
    private static final Map<Level, String> PREFIXES;

    static {
        Map<Level, String> prefixes = new HashMap<Level, String>();
        prefixes.put(Level.CONFIG,  "[config]");
        prefixes.put(Level.FINE,    "[debug]");
        prefixes.put(Level.FINER,   "[debug]");
        prefixes.put(Level.FINEST,  "[trace]");
        prefixes.put(Level.INFO,    "[info]");
        prefixes.put(Level.SEVERE,  "[error]");
        prefixes.put(Level.WARNING, "[warning]");

        PREFIXES = Collections.unmodifiableMap(prefixes);
    }

    // ...

}

如您所见,这是一个用于格式化日志输出的类。然而,引起我注意的是静态初始化块中的代码:PREFIXES = Collections.unmodifiableMap(prefixes);

为什么PREFIXES 制作了一张不可修改的地图? 这是一个私有常量,因此不存在修改该类之外的数据的风​​险。这样做是为了让常量的不变性具有完整性感吗?

就个人而言,我会直接将PREFIXES 初始化为HashMap,然后直接将put 键值对初始化,而无需创建虚拟的占位符映射或使该字段成为不可变映射。我在这里遗漏了什么吗?

【问题讨论】:

  • 还可能考虑在实例化类时防止并发初始化问题,因为单步分配给整个数据的 PREFIXES。

标签: java collections map constants immutability


【解决方案1】:

通过使列表不可修改,作者记录了他的假设,即这些值永远不会改变。以后可能编辑该类的人不仅可以看到该假设,而且还会在它被破坏时得到提醒。

只有从长远的角度来看,这才有意义。它降低了因维护而出现新问题的风险。我喜欢做这种编程风格,因为即使在我自己的课程中,我也倾向于破坏东西。有一天,您可能会进行快速修复,却忘记了最初做出的与正确性相关的假设。越能锁定密码越好。

【讨论】:

  • “有一天,您可能会进行快速修复,但您忘记了最初做出的与正确性相关的假设。” 天哪,我完全知道是什么你的意思是。
【解决方案2】:

如果你不小心从一个方法中return PREFIXES,突然之间任何其他代码都可以修改它。当您在未来凌晨 3 点修改该代码时,使常量真正不可变可以防止您自己的愚蠢。

【讨论】:

  • 完全正确...否则final 对于Map&lt;Level, String&gt; PREFIXES 映射将不完整。
【解决方案3】:

拥有一个可从类外部修改的private 映射、集合或数组非常容易。您将其标记为final,为什么不同时说明它也应该是不可变的?

【讨论】:

  • 我明白你的意思,但上述领域没有吸气剂。你是说让它们不可变是个好习惯,以防万一?
  • @KonstantinĐ。不需要成为吸气剂。我最喜欢的例子是 Michael Feather 的 Working With Legacy Code。在第 13 章中,他详细介绍了对象如何泄漏的最令人难以置信的乏味细节。在第 14 章中,他公开了一个 private static 数组。如果他这样做了,那么我不指望普通程序员能做对——除非他们非常明确地将可变对象隐藏在不可修改的包装器后面。
【解决方案4】:

假设你的朋友离开了他的工作,一个经验不足的程序员接手了。经验不足的程序员会尝试在同一类的不同方法中的某处修改 PREFIXES 的内容。它是不可修改的,它不会起作用。这是说“这是一个常数,永远不要改变它”的正确方式。

【讨论】:

    【解决方案5】:

    地图界面不会传达您希望某些东西是不可变的或不可修改的。

    以下方法适用于Eclipse Collections

    private static final ImmutableMap<Level, String> PREFIXES = UnifiedMap.<Level, String>newMap()
        .withKeyValue(Level.CONFIG, "[config]")
        .withKeyValue(Level.FINE, "[debug]")
        .withKeyValue(Level.FINER, "[debug]")
        .withKeyValue(Level.FINEST, "[trace]")
        .withKeyValue(Level.INFO, "[info]")
        .withKeyValue(Level.SEVERE, "[error]")
        .withKeyValue(Level.WARNING, "[warning]")
        .toImmutable();
    

    这将创建一个合同上不可变的 Map,因为 ImmutableMap 在其 API 中没有变异方法。

    如果您希望保留 Map 界面,这种方法也可以。

    private static final Map<Level, String> PREFIXES = UnifiedMap.<Level, String>newMap()
        .withKeyValue(Level.CONFIG, "[config]")
        .withKeyValue(Level.FINE, "[debug]")
        .withKeyValue(Level.FINER, "[debug]")
        .withKeyValue(Level.FINEST, "[trace]")
        .withKeyValue(Level.INFO, "[info]")
        .withKeyValue(Level.SEVERE, "[error]")
        .withKeyValue(Level.WARNING, "[warning]")
        .asUnmodifiable();
    

    您应该注意到在这两种情况下都不需要静态块。

    注意:我是 Eclipse Collections 的提交者。

    【讨论】:

      【解决方案6】:

      如果一个集合是最终的,你不能在其中设置一个新对象。 但是,仍然可以向同一个对象添加或删除项目。

      当您将其设为不可修改时,您甚至无法在集合中添加或删除项目。 因此,始终建议将集合设为不可修改,而不是将其保持为最终状态。

      【讨论】:

        猜你喜欢
        • 2011-03-04
        • 2010-11-27
        • 2012-09-29
        • 2012-04-20
        • 2011-01-26
        • 2019-11-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多