Java 无泛型的防白痴迭代API设计

Java 无泛型的防白痴迭代API设计,java,.net,api,language-agnostic,api-design,Java,.net,Api,Language Agnostic,Api Design,当您为代码库设计API时,您希望它易于良好使用,而难以糟糕使用。理想情况下,你希望它是防白痴的 您可能还希望使其与不能处理泛型的旧系统兼容,如.NET1.1和Java1.4。但是,您不希望从较新的代码中使用它会带来痛苦 我想知道如何以一种类型安全的方式使事情变得简单易懂。。。请记住,您不能使用泛型,因此Java的Iterable和.Net的IEnumerable都已过时 您希望人们能够在Java中使用增强的for循环(对于项目i:items),并在.Net中为每个循环使用foreach/,并且您

当您为代码库设计API时,您希望它易于良好使用,而难以糟糕使用。理想情况下,你希望它是防白痴的

您可能还希望使其与不能处理泛型的旧系统兼容,如.NET1.1和Java1.4。但是,您不希望从较新的代码中使用它会带来痛苦

我想知道如何以一种类型安全的方式使事情变得简单易懂。。。请记住,您不能使用泛型,因此Java的
Iterable
和.Net的
IEnumerable
都已过时

您希望人们能够在Java
中使用增强的for循环(对于项目i:items)
,并在.Net中为每个
循环使用
foreach
/
,并且您不希望他们必须进行任何强制转换。基本上,您希望您的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];
}
for(int i=0;i
然后它的效率会非常低,因为每次迭代都会克隆整个约会数组两次(一次用于长度测试,一次用于在索引处获取对象)。如果数组很小,这不是一个问题,但是如果数组中有数千个项目,这将是一个非常可怕的问题。旭

有人会这么做吗?我不确定。。。我想这主要是我的问题

您可以调用方法
toappointarray()
而不是
appointments()
,这可能会减少任何人以错误的方式使用它的可能性。但这也会让人们更难找到他们只想重复约会的时间

当然,您应该清楚地记录
appoints()
,说它返回一个防御性副本。但是很多人不会阅读那些特定的文档

虽然我欢迎大家的建议,但在我看来,没有完美的方法可以让它变得简单、清晰、类型安全和防白痴。如果少数人不知情地克隆了数千次数组,我是否失败了?或者,对于大多数人来说,为简单、类型安全的迭代付出的代价是否可以接受


注意:我碰巧为Java和.Net设计了这个库,这就是为什么我试图使这个问题同时适用于这两种语言。我给它贴上了语言不可知的标签,因为这是一个其他语言也可能出现的问题。代码示例是用Java编写的,但是C#将是类似的(尽管可以选择将
appointment
accessor设置为属性)


更新:我做了一些快速性能测试,看看这对Java有多大的影响。我测试了:

  • 克隆数组一次,并使用增强的for循环对其进行迭代
  • 使用 增强for循环
  • 在不可修改的对象上迭代 ArrayList(来自 Collections.unmodifyableList)使用 增强for循环
  • 以错误的方式迭代数组(在长度检查中重复克隆它 以及获取每个索引项时)
  • 对于10个对象,相对速度(多次重复并取中值)如下所示:

  • 一千
  • 1300
  • 1300
  • 五千
  • 对于100个对象:

  • 1300
  • 4900
  • 6300
  • 85500
  • 对于1000个对象:

  • 6400
  • 51700
  • 56200
  • 7000300
  • 对于10000个对象:

  • 68000
  • 445000
  • 651000
  • 655180000
  • 当然是粗略的数字,但足以让我相信两件事:

    • 克隆,然后迭代是绝对必要的 这不是性能问题。事实上 它始终比使用 列表(这是。)
    • 如果反复调用该方法, 不必要地重复克隆阵列, 所讨论的阵列越大,性能就变得越来越重要。太可怕了。这并不奇怪
    克隆()速度很快,但不是我所说的超级快

    如果您不信任人们高效地编写循环,我不会让他们编写循环(这也避免了克隆()的需要)


    因为你不可能两种方式都有,我建议你创建一个预泛型和一个泛型版本的API。理想情况下,底层实现可以大致相同,但事实是,如果您想让任何使用Java 1.5或更高版本的人都能轻松地使用它,他们会希望使用泛型、Iterable和所有更新的languange特性

    我认为数组的使用应该是不存在的。在这两种情况下,它都不能提供易于使用的API


    注意:我从未使用过C#,但我希望同样的情况也适用。

    对于少数用户来说,那些在循环的每次迭代中调用相同方法来获取相同对象的用户,无论API设计如何,都会要求低效率。我认为,只要有充分的记录,要求用户遵守一些看似常识的东西并不过分。

    永远不要低估白痴的力量…@Marc这通常是我的态度。如果迎合它能让事情变得有点开心的话,那么在迎合它的时候很难找出界限
    interface AppointmentHandler {
        public void onAppointment(Appointment appointment);
    }
    
    class Schedule {
        public void forEachAppointment(AppointmentHandler ah) {
            for(Appointment a: internalArray)
                ah.onAppointment(a);
        }
    }