【问题标题】:Why would you use a MyObject[] internally, but expose a List<MyObject>?为什么要在内部使用 MyObject[],但公开 List<MyObject>?
【发布时间】:2011-02-05 16:21:33
【问题描述】:

我遇到了一个具有不可变属性的类:

MyObject[] allObjs

属性初始化如下:

List<MyObject> objs = createAllMyObjects();
allObjs = objs.toArray(new MyObject[objs.size()]);

当它通过访问器暴露时,它是作为一个列表完成的:

public List<MyObject> getAllMyObjects() {
    return Collections.unmodifiableList(Arrays.asList(allObjs));
}

程序员为什么要这样做?有什么我不知道的好处吗?

性能不是问题,因为 obj 数组只会包含几十个元素。

我们好像在兜兜转转。

这个类是一种工厂,所以它有一个私有构造函数并且只公开静态方法(不确定这是否是疯狂的原因)。

编辑

我想我的问题是,“为什么不直接使用内部 List&lt;MyObject&gt; allObjs 而不是 MyObject[] allObjs,并返回 Collections.unmodifiableList(allObjs)?”因为这会完成同样的事情。

【问题讨论】:

  • 也许原作者根本不知道他的替代方案?或者getAllMyObjects() 方法的要求是使用数组实现之后出现的。
  • 这看起来有点像侦探游戏,试图了解初衷。我猜这只是成为程序员的一部分。
  • MyObject[] allObjs 不能是不可变的,只有 final:这是一个巨大的差异。

标签: java arrays collections


【解决方案1】:

让我用一个笑话来回答:

这位新的犹太新娘正在为她的丈夫做她的第一顿大餐,并尝试着她母亲的牛腩食谱,像她母亲一贯的做法一样,将烤肉的末端切掉。哈比觉得肉很好吃,但说:“你为什么要把末端切掉——那是最好的部分!”她回答说:“我妈妈一直都是这样做的。

下周他们去了老布比家,她准备了著名的牛腩食谱,再次切掉了末端。年轻的新娘确信她一定错过了一些重要的信息,所以她问她的奶奶为什么要剪掉末端。奶奶说:“Dahlink,只有这样才能把它放进锅里!”

虽然我们当然可以找到一些论据来证明某个特定决定的合理性,但在实践中这很少会产生任何影响,而且此类决定背后通常没有任何意图。

使用最简单易读的选项。可读性通常比聪明更重要。

【讨论】:

    【解决方案2】:

    这可能有几个原因:

    • 他们不知道自己在做什么。我现在正在编写的代码似乎是由 Java 新手和编程新手编写的。不幸的是,遇到设计不佳的代码比找到由对平台有深入了解的人编写的好、干净、维护良好的代码太常见了。这部分是因为下一点。
    • List 更改已修补,但不想进行大规模更改。如果其余代码使用该数组,则可能有人不想费心进行所有其他更改。
    • 虽然集合是更多的 OO/Java 做事方式,但如果列表是静态的,则数组实际上“更容易”使用,因为它们仍然可以像集合一样使用 for(:) 进行迭代,但使用 @987654323 访问@ 而不是 get()。就个人而言,使用数组的理由不够充分,但也许这就是我。
    • 也许他们希望明确数据的长度是final。如果他们刚刚使用了一个不可修改的列表,List 仍然是引用数据的方法,任何不注意其余(希望已注释)代码的人可能会尝试添加到 List,这将导致在运行时 UnsupportedOperationException。将其设置为数组使其用法更加清晰。

    【讨论】:

      【解决方案3】:

      Java Generics这本书里,作者其实说既然我们有泛型,就应该独占使用集合,不应该再使用数组。就我个人而言,我现在倾向于远离数组,因为集合可以帮助我记住我使用集合的意图,例如。将 vs list 设置为与数组相反,它并没有真正告诉我有关数据的任何信息。

      除了可能是快速复制 (System.arraycopy()) 之外,没有什么是我无法对数组做的集合。

      【讨论】:

        【解决方案4】:

        我看到的唯一原因是“在误用的情况下及早失败”(如果这种设计选择不是随机的并且有意义,那么意思是他很少信任他的特定客户)。

        MyObject[]reified,并且在运行时检查,而List&lt;MyObject&gt; 不是。可以在后者中潜入非 MyObject 引用(他当然会在编译时收到未经检查的警告),这将在某个未定义的未来点使用 ClassCastException 失败,但他不能对 MyObject[] 做同样的事情 - 那将在误用时立即失败。

        请注意,如果客户端是在泛型之前使用此 API 的预构建二进制文件,那么没有人会收到未经检查的警告,因此如果此代码试图迁移预泛型客户端使用的代码,这是有道理的。

        (实现相同结果的另一种方法是使用List&lt;T&gt; 并且还需要Class&lt;T&gt;,以便后者可以提供运行时类型检查。但这需要例如另一个构造函数参数 - 现有客户端不会自动利用这一点。**更新:**这基本上描述了 Sauer 在第一条评论中提到的 API 调用,因此不必重新发明它,只需使用那个 :))。

        【讨论】:

        • 说得好!但是,您可以提供 checkedList 以获得泛化列表的快速失败行为:java.sun.com/javase/6/docs/api/java/util/…
        • 同意。如果原始发布者想要在不影响其(甚至是偶然的)语义的情况下清理代码,那么切换到 checkedList 应该是一条安全的路线。
        • 感谢 cmets 和答案。
        【解决方案5】:

        有一个很好的理由不返回对内部MyObject[] 属性的引用。调用者可以通过引用对数组进行更改。

        如果结果没有理由必须是List,您还可以返回数组的副本:

        public MyObject[] getAllMyObjects() {
            MyObject[] result = new MyObject[allObjs.length];
            system.ArrayCopy(allObjs, 0, result, 0, result.length);
            return result;
        }
        

        更新,添加问题后: 您是正确的,如果将内部属性更改为 List,则将其包装为不可修改更容易。

        类使用数组而不是列表的原因可能是性能。如果数组在对象的生命周期内没有发生(太多)变化并且被多次迭代,那么数组的开销就比 List 少。

        如果性能是使用数组的原因,您可以通过存储 List 来防止在 getter 中创建额外的对象,仅用于返回,即:

        allObjsList = Collections.unmodifiableList(createAllMyObjects());
        allObjs = allObjsList.toArray(new MyObject[allObjsList.size()]);
        

        像以前一样使用 allObjs 数组并在 getter 中返回存储的列表:

        public List<MyObject> getAllMyObjects() {
            return allObjsList ;
        }
        

        【讨论】:

        • 其实Collections.unmodifiableList(Arrays.asList()) 避免复制数据。它只创建 2 个小的包装对象。
        • @Joachim,根据问题,该类在内部使用MyObject[],而不是列表,因此可以复制一个新数组以返回。
        • 是的,我知道,我只是想指出“返回数组的副本”并不是“返回数组”的唯一选择,“返回不可修改的列表包装器”实际上是比复制数组更便宜并为调用者提供更丰富的接口。
        【解决方案6】:

        这样集合是不可修改的,即调用者不能修改成员数组。 数组无法做到这一点,因此编码器将其复制到列表中。

        【讨论】:

        • 同样可以通过返回Collections.unmodifiableList(myList)来实现。出于这个原因,不需要使用数组。
        • 但这会返回一个列表而不是一个数组。我认为原始编码器想使用数组并阻止调用者修改它,但是没有'unmodifiableArray'。
        • 看我的编辑:如果接口说它必须是一个列表,那么有什么理由在内部使用数组。
        【解决方案7】:

        作者似乎打算返回一个不可变的副本,并且这样做是多余的。我假设选择 List 作为返回类型,因此可以使用 MyObject 类型轻松创建克隆。

        【讨论】:

        • “克隆”(或字面意思是 clone())数组和列表一样容易。
        【解决方案8】:

        我认为这背后的原因可能是数组不可修改。但是,您可以将内部列表保留为 List 并在 getter 中返回它的副本,这样内部列表仍然无法从外部修改。

        或者使用 Collections.unmodifiableList() 返回的“不可修改”列表...

        在这种情况下,我也看不到任何性能优势,因为无论如何来回转换都需要一些周期。

        【讨论】:

        • “数组不可修改”。这是什么意思?唯一不可修改的数组是长度为零的数组!
        • 您当然是对的,Dimitris,可以从数组中删除和替换项目,但您不能添加任何内容或更改大小。这就是为什么我认为原始编码器决定使用数组而不是列表的原因。
        【解决方案9】:

        我认为使用数组而不是列表没有任何优势。列表要舒服得多。也许这家伙只是喜欢使用数组:)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-09-19
          • 1970-01-01
          • 1970-01-01
          • 2018-11-28
          • 2017-10-17
          • 2013-04-23
          • 2011-11-25
          • 1970-01-01
          相关资源
          最近更新 更多