代码之家  ›  专栏  ›  技术社区  ›  linuxNoob

为什么从创建列表的方法返回不可修改的列表更好?

  •  2
  • linuxNoob  · 技术社区  · 6 年前
     public List<Integer> getInts()
    {
        List<Integer> xs = new ArrayList<Integer>();
        xs.add(1);
        // return Collections.unmodifiableList(xs);
        return xs;
    }   
    

    2 回复  |  直到 6 年前
        1
  •  6
  •   Bohemian    6 年前

    当一个对象返回一个可变的私有字段时,它将暴露于外部代理对其内部状态的不需要的/未知的更改,这违反了封装。

    你所指的模式,叫做 安全发布

    如果是注释掉的代码:

    return Collections.unmodifiableList(xs);
    

    它正在创建一个新的对象,但它只是列表顶部的一个很薄的层,您几乎无法测量这样做的cpu或内存性能成本。

    您还可以:

    return new ArrayList<>(xs);
    

    Integer 是不变的)。

        2
  •  7
  •   Stuart Marks    6 年前

    来自波希米亚的答案陈述了一些关于返回不可修改、不可变或防御复制的数据以保留封装的好的一般原则。如果数据是对象的内部数据,例如存储在字段中,那么这肯定是正确的。

    但是,OP声明每次调用方法时都会新建返回的列表。在这种情况下,为什么返回一个不可修改的列表而不是一个常规的ArrayList?这是有原因的,但它们有点微妙,而且它们与封装的关系不大,因为封装可以保持实现的灵活性。

    作为一个背景主题,您需要决定这个API是否有任何长期的兼容性约束或策略。如果你返回一个可变列表,那么它是可能的(根据 Hyrum's Law )调用者将依赖于它的易变性。(Hyrum定律本质上说,系统的任何可观察属性最终都将取决于用户。)我个人认为,修改返回给您的集合是草率的编程,但事实是,人们会这样做。如果是这样的话,将来你会建议将返回的列表更改为不可修改的,这会被禁止吗,因为它是不兼容的,并且会打断一些调用方?如果你不在乎兼容性(而有些项目不在乎),那么也许这无关紧要。但是如果你这样做了,那么你应该考虑现在返回一个不可修改的列表。

    一个原因(其他人在评论中提到)是,您可能决定不每次都创建一个新列表,但您可能会将其缓存并返回给多个调用方。如果您这样做,它绝对应该是不可修改的,以防止一个调用者修改列表并影响所有调用者。

    另一个原因是不同的列表实现具有不同的性能和空间特性。这个 Collections.singletonList List.of(x) 实现将其单个元素存储在列表对象本身的一个字段中,而 ArrayList ,如果你创造了很多。在 阵列列表

    您可能还希望向该方法添加一些自适应行为,例如,取决于返回列表中的元素数。例如,

    if (count == 0) {
        return Collections.emptyList(); // eventually, List.of()
    } else if (count == 1) {
        return Collections.singletonList(i); // eventually, List.of(i)
    } else {
        List<Integer> list = new ArrayList<>();
        // populate list
        return Collections.unmodifiableList(list);
    }
    

    如果不将ArrayList包装在一个不可修改的包装器中,调用方将暴露于行为上的奇怪差异,例如列表有时是可修改的,有时不是。如果可能,最好在所有情况下提供统一的行为,从而为将来的更改保留实现灵活性。