10

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

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为 a HashMap,然后直接初始化put键值对,而无需创建虚拟的占位符映射或使该字段成为不可变映射。我在这里错过了什么吗?

4

6 回答 6

9

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

于 2012-12-10T02:55:59.493 回答
9

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

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

于 2012-12-10T03:02:26.130 回答
3

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

于 2012-12-10T01:27:08.443 回答
3

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

于 2012-12-10T01:36:50.987 回答
1

Map 接口不会传达您希望某些东西是不可变的或不可修改的。

以下方法适用于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 的提交者。

于 2012-12-10T17:57:03.823 回答
0

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

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

于 2017-07-28T05:14:11.910 回答