哪个是好习惯 - 在方法中修改List,或者在方法中返回一个新List?

时间:2014-02-11 11:22:28

标签: java list

示例代码:

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并返回这个新的List ...

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

List myList = modifyMyList(myList);

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

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

7 个答案:

答案 0 :(得分:15)

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

我认为变异方法的论点有点过时,可能导致更难以看到的错误。

我知道这个规则有例外,但我需要一个非常好的理由。

答案 1 :(得分:3)

实际上没有功能差异。

当您想要返回的列表

时,您会知道不同之处
List someNewList = someInstnace.modifyMyList(list);

答案 2 :(得分:1)

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

List myList = modifyMyList(myList);

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

答案 3 :(得分:1)

第二种可能令人困惑,因为它意味着正在创建并返回一个新值 - 而事实并非如此。

如果该方法是'fluent'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。 (第一个是通过使用iterable的迭代器调用第二个来实现的。)另外,我已经将它设置为静态,因为没有理由将您的方法作为实例方法(它不依赖于实例的状态)。

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

/** 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。 (不是NullPointerException - 虽然它经常用于此目的.NullPointerException也会因其他原因而发生,例如错误代码。对于无效参数,IllegalArgumentException更好。)

(还有比实际代码更多的Javadoc!)

答案 4 :(得分:1)

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

所以真正的问题是在第一个带有可变列表的解决方案和第三个带有不可更改列表的解决方案之间,并且再次没有唯一的响应,返回的String之间存在相同的争论,它们是不可变的并且使用Stringbuffer,是可变的,但允许更好的表现。

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

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

答案 5 :(得分:0)

这两种方式都有效,因为在这种情况下,java使用List的引用,但我更喜欢secound方式,因为这个解决方案也适用于按值传递,而不仅仅是通过引用传递。

答案 6 :(得分:0)

功能上两者都相同。

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

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

此外,第二种方法返回一个列表,因此理想情况下,即使传递的列表为非null,调用者也应该检查空返回值(该方法可能会返回null而不是修改后的列表)。

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