【发布时间】:2011-09-15 16:14:34
【问题描述】:
当您为代码库设计 API 时,您希望它易于使用且难以使用。理想情况下,您希望它是白痴证明。
您可能还希望使其与无法处理泛型的旧系统兼容,例如 .Net 1.1 和 Java 1.4。但是您不希望从较新的代码中使用它会很痛苦。
我想知道以类型安全的方式使事物易于迭代的最佳方法...记住您不能使用泛型,因此 Java 的 Iterable<T> 和 .Net 的 IEnumerable<T> 已经过时了。
您希望人们能够在 Java (for Item i : items) 中使用增强的 for 循环,以及在 .Net 中使用 foreach / For Each 循环,并且您不希望他们必须进行任何转换。基本上,您希望您的 API 现在友好且向后兼容。
我能想到的最好的类型安全选项是数组。它们完全向后兼容并且它们很容易以类型安全的方式进行迭代。但是数组并不理想,因为你不能让它们不可变。因此,当您有一个包含数组的不可变对象时,您希望人们能够对其进行迭代,为了保持不变性,您必须在他们每次访问它时提供一个防御性副本。
在 Java 中,执行 (MyObject[]) myInternalArray.clone(); 非常快。我确信 .Net 中的等价物也非常快。如果你喜欢:
class Schedule {
private Appointment[] internalArray;
public Appointment[] appointments() {
return (Appointment[]) internalArray.clone();
}
}
人们可以这样做:
for (Appointment a : schedule.appointments()) {
a.doSomething();
}
它将简单、清晰、类型安全且快速。
但是他们可以做这样的事情:
for (int i = 0; i < schedule.appointments().length; i++) {
Appointment a = schedule.appointments()[i];
}
然后它会非常低效,因为每次迭代都会克隆整个约会数组(一次用于长度测试,一次用于获取索引处的对象)。如果数组很小,则不是这样的问题,但如果数组中有数千个项目,则非常可怕。呵呵。
真的有人会这样做吗?我不确定...我想这主要是我的问题。
您可以调用方法toAppointmentArray() 而不是appointments(),这可能会降低任何人以错误方式使用它的可能性。但这也会让人们更难找到他们只想迭代约会的时间。
你当然会清楚地记录appointments(),说它返回一个防御性副本。但是很多人不会阅读那些特定的文档。
虽然我欢迎提出建议,但在我看来,没有完美的方法可以让它简单、清晰、类型安全、和白痴证明。如果 少数 人在不知情的情况下克隆数组数千次,我是否失败了,或者对于 大多数 的简单、类型安全的迭代来说,这是可以接受的代价?
NB 我碰巧正在为 Java 和 .Net 设计这个库,这就是为什么我试图让这个问题适用于两者。我将其标记为与语言无关,因为这也是其他语言也可能出现的问题。代码示例使用 Java,但 C# 类似(尽管可以选择将 Appointments 访问器设为属性)。
更新:我做了一些快速的性能测试,看看这对 Java 有多大的影响。我测试过:
- 克隆一次数组,然后使用增强的 for 循环对其进行迭代
- 使用 ArrayList 迭代 增强的 for 循环
- 迭代不可修改的 数组列表(来自 Collections.unmodifyableList) 使用 增强的 for 循环
- 以错误的方式迭代数组(在长度检查中重复克隆它 以及获取每个索引项时)。
对于 10 个对象,相对速度(多次重复并取中位数)如下:
- 1,000
- 1,300
- 1,300
- 5,000
对于 100 个对象:
- 1,300
- 4,900
- 6,300
- 85,500
对于 1000 个对象:
- 6,400
- 51,700
- 56,200
- 7,000,300
对于 10000 个对象:
- 68,000
- 445,000
- 651,000
- 655,180,000
肯定是粗略的数字,但足以让我相信两件事:
- 克隆,然后迭代肯定是 不是性能问题。实际上 它始终比使用 列表。 (这是why Java's enum.values() method returns a defensive copy of an array instead of an immutable list。)
- 如果反复调用该方法, 不必要地反复克隆阵列, 所讨论的阵列越大,性能就变得越来越重要。这太可怕了。那里没有惊喜。
【问题讨论】:
-
永远不要低估白痴的力量......
-
@Marc 这就是我的态度。在迎合它时很难弄清楚在哪里划清界限,如果迎合它会让其他人的事情变得更难。
-
实际上大多数白痴都有足够的意识将
appointments()的结果缓存在局部变量中。其中一些甚至会缓存[].length。即使是白痴也很有防御性。
标签: java .net api language-agnostic api-design