问题描述
||
在为代码库设计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];
}
然后这将是非常低效的,因为整个约会数组将在每次迭代中克隆两次(一次用于长度测试,一次用于将对象指向索引)。如果数组很小,就不是这样的问题,但是如果数组中有成千上万个项目,那就太可怕了。 Yu
有人会这样做吗?我不确定...我想这主要是我的问题。
您可以将方法toAppointmentArray()
而不是appointments()
调用,这可能会减少任何人以错误的方式使用它的可能性。但是,这也将使人们在仅想遍历约会时就更难找到。
当然,您会清楚地记录“ 10”,说它返回了防御性副本。但是很多人不会读那些特别的文档。
尽管我很欢迎提出建议,但在我看来,还没有完美的方法可以使它变得简单,清晰,类型安全和白痴证明。如果少数人不知道数千次克隆数组,或者对于大多数人来说,为简单的类型安全的迭代支付的价格是否合理,我是否会失败?
注意:我碰巧正在为Java和.Net设计该库,这就是为什么我试图使这个问题适用于两者。我将其标记为与语言无关,因为其他语言也可能会出现此问题。代码示例使用Java,但是C#会类似(尽管可以选择将“ 12”访问器设置为属性)。
更新:我进行了一些快速的性能测试,以了解这在Java中有多大的区别。我测试了:
克隆一次数组,然后使用增强的for循环对其进行迭代
使用以下方法遍历ArrayList
增强的for循环
遍历一个不可修改的对象
ArrayList(来自
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
粗略的数字可以肯定,但足以使我相信两件事:
克隆,然后迭代绝对是
不是性能问题。事实上
始终比使用
清单。 (这就是Java的原因
enum.values()方法返回一个
数组的防御性副本,而不是
不可变的清单。)
如果您反复调用该方法,
不必要地重复克隆阵列,
阵列越大,性能就越成问题。这太可怕了。没有惊喜。
解决方法
clone()很快,但是我描述的不是超级快。
如果您不信任人们高效地编写循环,那么我不会让他们编写循环(这也避免了需要clone())
interface AppointmentHandler {
public void onAppointment(Appointment appointment);
}
class Schedule {
public void forEachAppointment(AppointmentHandler ah) {
for(Appointment a: internalArray)
ah.onAppointment(a);
}
}
,由于您实际上并不能同时使用这两种方式,因此建议您创建API的预泛型和泛型版本。理想情况下,底层实现可以基本相同,但是事实是,如果您希望对使用Java 1.5或更高版本的任何人都易于使用它,则他们会期望使用Generics和Iterable以及所有较新的languange功能。
我认为数组的使用应该是不存在的。在任何一种情况下,都无法使用易于使用的API。
注意:我从未使用过C#,但是我希望同样适用。
, 至于使少数用户失败的问题,无论API设计如何,那些在循环的每次迭代中调用相同方法以获取相同对象的用户都将要求低效率。我认为,只要有据可查,就可以要求用户遵守一些常识。