【问题标题】:Which is good practice - Modifying a List in the method, or returning a new List in the method?哪个是好的做法 - 在方法中修改列表,或在方法中返回新列表?
【发布时间】:2014-02-11 11:22:27
【问题描述】:

示例代码:

modifyMyList(myList);

public void modifyMyList(List someList){
    someList.add(someObject);
}

或:

List myList = modifyMyList(myList);

public List modifyMyList(List someList){
    someList.add(someObject)
    return someList;
}

我相信还有第三种选择:您可以在 modifyMyList 方法中创建一个新列表并返回这个新列表...

(第三个选项在这里,我太懒了,但有人已经在答案中添加了它:)

List myList = modifyMyList(myList);

public List modifyMyList(List someList){
    List returnList = new ArrayList();
    returnList.addAll(someList);
    returnList.add(someObject);
    return Collections.unmodifiableList(returnList);
}

有什么理由我应该选择一个而不是另一个?在这种情况下应该考虑什么?

【问题讨论】:

  • 两者相同。在这两种情况下,原始列表都已修改,因此无需返回相同的列表。查看有关 Java 引用传递的更多信息。
  • 我讨厌选项 2!我会从 API 中假设你没有改变方法参数,但你是。
  • 如果您有很多与列表关联的方法,我会从 List 继承并将这些方法放在新类中。所以你会有 myList.modifyMyList();

标签: java list


【解决方案1】:

我有一个(自我强加的)规则,即“永远不要在公共方法中改变方法参数”。因此,在私有方法中,可以改变参数(我什至也尝试避免这种情况)。但是在调用公共方法时,参数永远不应该被改变,应该被认为是不可变的。

我认为变异方法参数有点笨拙,可能会导致更难发现的错误。

众所周知,我对这条规则有例外,但我需要一个非常好的理由。

【讨论】:

  • 所以你说第三个选项是最佳实践?
  • 是的,因为它是一个公共方法。 guava 中还有一些时髦的集合,您可以考虑(即指向原始集合但有额外元素的集合)。
  • 从不改变公共方法中的方法参数 => 这是否意味着“从不修改方法参数”?
  • 不,有时您希望将公共方法拆分为私有方法以提高可读性,并且您希望更改列表,而无需每次都将其复制到新列表中。正如我所说,我尽量避免它,但有时我认为在私有方法中是可以的。而且这个列表不能是公共方法的参数(即它必须是在方法的某个地方创建的)。
【解决方案2】:

其实没有功能上的区别。

当你想要返回列表时,你就会知道区别

List someNewList = someInstnace.modifyMyList(list);

【讨论】:

    【解决方案3】:

    第二个可能令人困惑,因为它暗示正在创建并返回一个新值 - 但事实并非如此。

    如果方法是“流式”API 的一部分,则例外情况是,该方法是实例方法并正在修改其实例,然后返回实例以允许方法链接:Java StringBuilder 类是这个例子。

    不过,我一般都不会使用。

    我会选择您的第三个选项:我编写一个方法来创建并返回一个带有适当更改的新列表。就您的示例而言,这有点人为,因为该示例实际上只是在复制 List.add(),但是...

    /** Creates a copy of the list, with val appended. */
    public static <T> List<T> modifyMyList(List<T> list, T val) {
        List<T> xs = new ArrayList<T>(list);
        xs.add(val);
        return xs;
    }
    

    旁白:我不会,正如 Saket 所建议的那样,返回一个不可变的列表。他关于不变性和并行性的论点是有效的。但大多数情况下,Java 程序员希望能够修改集合,除非在特殊情况下。通过让你的方法返回一个不可变的集合,你将它的可重用性限制在这种情况下。 (如果他们愿意,调用者总是可以使列表不可变:他们知道返回的值是一个副本,不会被其他任何东西触及。)换句话说:Java 不是 Clojure。此外,如果需要考虑并行性,请查看 Java 8 和流(新类型 - 不是 I/O 流)。

    这是一个不同的例子:

    /** Returns a copy of a list sans-nulls. */
    public static <T> List<T> compact(Iterable<T> it) {
        List<T> xs = new ArrayList<T>();
        for(T x : it)
            if(x!=null) xs.add(x);
        return xs;
    }
    

    请注意,我已经对方法进行了通用化,并使其更广泛地适用于获取Iterable 而不是列表。在实际代码中,我有两个重载版本,一个采用Iterable,一个采用Iterator。 (第一个将通过使用可迭代的迭代器调用第二个来实现。)另外,我将其设为静态,因为您的方法没有理由成为实例方法(它不依赖于实例的状态)。

    但有时,如果我正在编写库代码,并且不清楚变异实现还是非变异实现更普遍有用,我会同时创建两者。这是一个更完整的例子:

    /** Returns a copy of the elements from an Iterable, as a List, sans-nulls. */ 
    public static <T> List<T> compact(Iterable<T> it) {
        return compact(it.iterator());
    }
    public static <T> List<T> compact(Iterator<T> iter) {
        List<T> xs = new ArrayList<T>();
        while(iter.hasNext()) {
            T x = iter.next();
            if(x!=null) xs.add(x);
        }
        return xs;
    }
    
    /** In-place, mutating version of compact(). */
    public static <T> void compactIn(Iterable<T> it) {
        // Note: for a 'fluent' version of this API, have this return 'it'.
        compactIn(it.iterator());
    }
    public static <T> void compactIn(Iterator<T> iter) {
        while(iter.hasNext()) {
            T x = iter.next();
            if(x==null) iter.remove();
        }
    }
    

    如果这是在真正的 API 中,我会检查参数是否为 null 并抛出 IllegalArgumentException。 (NOT NullPointerException - 虽然它经常用于此目的。NullPointerException 也因其他原因而发生,例如错误代码。IllegalArgumentException 更适合无效参数。)

    (Javadoc 也会比实际代码多!)

    【讨论】:

      【解决方案4】:

      第一个和第二个解决方案非常相似,第二个的优点是允许链接。正如我们在这里看到的那样,“这是一个好的做法”的问题受到了争论: Method Chaining in Java

      所以真正的问题是在第一个具有可变列表的解决方案和第三个具有不可变列表的解决方案之间,同样,没有唯一的响应,返回不可变的 String 和使用 Stringbuffer 之间的争论是一样的,后者是可变的,但允许更好的性能。

      如果您需要 API 的可靠性,并且没有性能问题,请使用 immutable(第三种解决方案)。如果您的列表总是很小,请使用它。

      如果您只需要性能,请使用可变列表(第一个解决方案)

      【讨论】:

        【解决方案5】:

        我建议在方法中创建一个新列表并返回一个不可变列表。这样,即使您在不可变列表中传递,您的代码也能正常工作。随着我们通常转向函数式编程并尝试跨多个处理器架构进行扩展,创建不可变对象通常是一种很好的做法。

        List myList = modifyMyList(myList);
        
        public List modifyMyList(List someList){
            List returnList = new ArrayList();
            returnList.addAll(someList);
            returnList.add(someObject);
            return Collections.unmodifiableList(returnList);
        }
        

        【讨论】:

          【解决方案6】:

          两种方式都可以,因为在这种情况下,java 使用 List 的引用,但我更喜欢第二种方式,因为这种解决方案也适用于按值传递,而不仅仅是按引用传递。

          【讨论】:

          • “按值传递”是什么意思?
          • 如果他使用原始类型,值将被传递给函数而不是引用。
          • 好的,我明白你的意思了。我只是想知道您想如何按值传递列表。 ;-)
          【解决方案7】:

          两者功能相同。

          但是,当您将方法公开为 API 时,第二种方法可能会给人一种印象,即它返回一个新的修改列表,而不是原始传递的列表。

          虽然第一种方法会明确(当然基于方法命名约定)它将修改原始列表(相同对象)。

          另外,第二个方法返回一个列表,所以理想情况下,调用者应该检查一个 null 返回值,即使传递的列表不是 null (该方法可能返回一个 null 而不是修改后的列表)。

          考虑到这一点,我通常更喜欢使用方法一。

          【讨论】:

            猜你喜欢
            • 2011-08-06
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-07-22
            • 2019-06-30
            • 2010-11-02
            • 2015-11-27
            • 1970-01-01
            相关资源
            最近更新 更多