为什么从创建列表的方法中 return unmodifiableList 更好?
Why is it better to return unmodifiableList from a method where list is created?
public List<Integer> getInts()
{
List<Integer> xs = new ArrayList<Integer>();
xs.add(1);
// return Collections.unmodifiableList(xs);
return xs;
}
我知道返回一个不可修改的列表将阻止消费者向列表引用添加额外的元素,但除此之外,我通过将它包装在一个不可修改的列表中获得了什么?每次调用该方法时,我都会创建一个新列表。
当一个对象 returns 是一个可变的私有字段时,它会暴露给外部代理 unwanted/unknown 改变其内部状态,这违反了封装。
您提到的模式称为 安全发布,可防止出现此问题。它可以通过在不可变包装器中返回深层副本或字段(以及任何可变子字段)来实现。
对于您注释掉的代码:
return Collections.unmodifiableList(xs);
它正在创建一个新对象,但它只是列表顶部的一个非常薄的层,您几乎无法衡量这样做的 cpu 或内存性能成本。
您还可以:
return new ArrayList<>(xs);
进行深拷贝(在本例中,因为 Integer
是不可变的)。
Bohemian 的回答陈述了一些关于 return 不可修改、不可变或防御性复制数据的通用原则,以保持封装。如果数据是对象内部的,例如存储在字段中,这当然是正确的。
但是,OP 声明每次调用该方法时都会新创建 returned 列表。在这种情况下,为什么 return 是不可修改的列表而不是常规的 ArrayList?有一些原因,但它们有些微妙,并且它们与保持实现灵活性的封装关系不大。
作为背景主题,您需要确定此 API 是否有任何长期兼容性限制或政策。如果您 return 一个可变列表,那么调用者可能会依赖于它的可变性(根据 Hyrum's Law)。 (Hyrum 定律本质上指出,系统的任何可观察 属性 最终都会被用户依赖。)我个人认为,改变已经 return 告诉你的集合是草率的编程,但事实是,人们这样做。如果是这种情况,并且将来您要提议将 returned 列表更改为不可修改,是否会因为它不兼容并且会破坏某些呼叫者而被禁止?如果您不关心兼容性(有些项目不关心),那么也许它并不重要。但如果你这样做,那么你现在应该考虑 return 一个不可修改的列表。
一个原因(其他人在评论中提到)是您可能决定不每次都创建一个新列表,但您可能会缓存它并 return 它给多个调用者。如果这样做,绝对应该将其设置为不可修改,以防止一个调用者修改列表并影响所有调用者。
另一个原因是不同的列表实现具有不同的性能和 space 特征。 Collections.singletonList
和 List.of(x)
实现将它们的单个元素存储在 List 对象本身的一个字段中,而 ArrayList
将其元素(即使只有一个)存储在单独的数组对象中。小列表实现可以节省大量 space
与 ArrayList
相比,如果您创建了很多。如果您想在将来切换到单例列表或 Java 9 个不可修改的列表实现,则围绕 ArrayList
返回一个不可修改的包装器将缓解兼容性问题。
您可能还想向该方法添加一些自适应行为,例如,根据 returned 列表中的元素数量。例如,
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 包装在不可修改的包装器中,调用者将面临行为上的奇怪差异,例如列表有时可修改,有时不可修改。如果可能,最好在所有情况下都提供统一的行为,从而为将来的更改保留实施灵活性。
public List<Integer> getInts()
{
List<Integer> xs = new ArrayList<Integer>();
xs.add(1);
// return Collections.unmodifiableList(xs);
return xs;
}
我知道返回一个不可修改的列表将阻止消费者向列表引用添加额外的元素,但除此之外,我通过将它包装在一个不可修改的列表中获得了什么?每次调用该方法时,我都会创建一个新列表。
当一个对象 returns 是一个可变的私有字段时,它会暴露给外部代理 unwanted/unknown 改变其内部状态,这违反了封装。
您提到的模式称为 安全发布,可防止出现此问题。它可以通过在不可变包装器中返回深层副本或字段(以及任何可变子字段)来实现。
对于您注释掉的代码:
return Collections.unmodifiableList(xs);
它正在创建一个新对象,但它只是列表顶部的一个非常薄的层,您几乎无法衡量这样做的 cpu 或内存性能成本。
您还可以:
return new ArrayList<>(xs);
进行深拷贝(在本例中,因为 Integer
是不可变的)。
Bohemian 的回答陈述了一些关于 return 不可修改、不可变或防御性复制数据的通用原则,以保持封装。如果数据是对象内部的,例如存储在字段中,这当然是正确的。
但是,OP 声明每次调用该方法时都会新创建 returned 列表。在这种情况下,为什么 return 是不可修改的列表而不是常规的 ArrayList?有一些原因,但它们有些微妙,并且它们与保持实现灵活性的封装关系不大。
作为背景主题,您需要确定此 API 是否有任何长期兼容性限制或政策。如果您 return 一个可变列表,那么调用者可能会依赖于它的可变性(根据 Hyrum's Law)。 (Hyrum 定律本质上指出,系统的任何可观察 属性 最终都会被用户依赖。)我个人认为,改变已经 return 告诉你的集合是草率的编程,但事实是,人们这样做。如果是这种情况,并且将来您要提议将 returned 列表更改为不可修改,是否会因为它不兼容并且会破坏某些呼叫者而被禁止?如果您不关心兼容性(有些项目不关心),那么也许它并不重要。但如果你这样做,那么你现在应该考虑 return 一个不可修改的列表。
一个原因(其他人在评论中提到)是您可能决定不每次都创建一个新列表,但您可能会缓存它并 return 它给多个调用者。如果这样做,绝对应该将其设置为不可修改,以防止一个调用者修改列表并影响所有调用者。
另一个原因是不同的列表实现具有不同的性能和 space 特征。 Collections.singletonList
和 List.of(x)
实现将它们的单个元素存储在 List 对象本身的一个字段中,而 ArrayList
将其元素(即使只有一个)存储在单独的数组对象中。小列表实现可以节省大量 space
与 ArrayList
相比,如果您创建了很多。如果您想在将来切换到单例列表或 Java 9 个不可修改的列表实现,则围绕 ArrayList
返回一个不可修改的包装器将缓解兼容性问题。
您可能还想向该方法添加一些自适应行为,例如,根据 returned 列表中的元素数量。例如,
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 包装在不可修改的包装器中,调用者将面临行为上的奇怪差异,例如列表有时可修改,有时不可修改。如果可能,最好在所有情况下都提供统一的行为,从而为将来的更改保留实施灵活性。