实现如何知道输入参数是否可变?

时间:2015-10-26 13:06:29

标签: java

例如,某些方法具有下一个实现:

void setExcludedCategories(List<Long> excludedCategories) {
    if (excludedCategories.contains(1L)) {
        excludedCategories.remove(1L);
    }
}

以下一种方式调用它:

setExcludedCategories(Array.asList(1L, 2L, 3L));

当然,它会在尝试删除项目时引发异常java.lang.UnsupportedOperationException。

问题:如何修改此代码以确保输入参数excludedCategories支持删除?

UPD: 谢谢你的回答。让我们总结一下结果:

  1. 始终从输入列表创建新的ArrayList以确保它是可变的 - 将使用大量无用的内存 - &gt; NO。
  2. 捕获UnsupportedOperationException。
  3. 在JavaDoc中指定调用者不能传递不可变列表 - 任何人都读取JavaDoc?当某些东西不起作用时:)
  4. 不要在调用者的代码中使用Arrays.asList() - 如果您是此代码的所有者,那么这是一个选项,但无论如何您应该知道这个具体方法是否允许不可变(参见3)。
  5. 似乎第二种变体是解决此问题的唯一方法。

6 个答案:

答案 0 :(得分:2)

简而言之,你无法知道。如果一个对象实现了一个接口(例如List),你就无法知道它是否会实际执行所有方法的预期。例如,Collections.unmodifiableList()会返回引发List的{​​{1}}。如果您希望能够获得其他UnsupportedOperationException实现,则无法通过方法签名过滤掉它。

您可以做的最好的事情是抛出List已知的不支持您想要的子类型。并抓住IllegalArgumentException了解其他类型的案例。但实际上你应该用你需要的东西javadoc你的方法,并在其他情况下抛出UnsupportedOperationException

答案 1 :(得分:2)

  

如何修改此代码以确保输入参数excludedCategories支持remove?

在一般情况下,你不能。给定一个实现List API的任意类,您无法(静态地或动态地)告知是否支持可选方法。

您可以使用instanceof测试来检查列表的类是否已知来实现该方法或不实现它。例如ArrayListLinkedList,但Collections.UnmodifiableList没有。问题是您的代码可能遇到您的测试无法涵盖的列表类。 (特别是如果它是一个可以在其他人的应用程序中重用的库。)

您还可以尝试测试以前未知类的行为;例如创建一个测试实例,尝试remove查看发生的情况,并在Map<Class, Boolean>中记录行为。这有两个问题:

  • 您可能无法(正确)实例化列表类以进行测试。

  • 行为可能取决于您如何实例化类(例如构造函数参数),甚至取决于您要删除的元素的性质......尽管后者正在推动合理性的界限。

事实上,唯一完全可靠的方法是调用方法并每次都捕获异常(如果它被抛出)。

答案 2 :(得分:1)

这在某种程度上取决于你正在尝试做什么。例如,在您发布的示例中,您可以抓住UnsupportedOperationException并执行其他操作。

这假设您可以假设非可变容器会在每次尝试修改容器时抛出它,并且没有副作用(即它们确实是不可变的)。

在您的代码具有其他副作用而不是尝试修改容器的其他情况下,您必须确保在知道您可以修改容器之前不会发生这些。

答案 3 :(得分:1)

您可以在实用程序类中捕获异常,如下例所示(正如其他人提到的那样)。不好的是你必须插入/删除测试是否会有异常。您无法使用instanceof,因为所有Collections.Unmodifiablexxx类都具有默认访问权限。

CollectionUtils:

import java.util.List;

public class CollectionUtils {

    public <T> boolean isUnmodifiableList(List<T> listToCheck) {

        T object = listToCheck.get(0);

        try {
            listToCheck.remove(object);
        } catch (UnsupportedOperationException unsupportedOperationException) {
            return true;
        }
        listToCheck.add(0, object);
        return false;
    }
}

主:

import java.util.Arrays;
import java.util.List;

public class Main {

    private static final CollectionUtils COLLECTION_UTILS = new CollectionUtils();

    public static void main(String[] args) {
        setExcludedCategories(Arrays.asList(1L, 2L, 3L));


    }

    private static void setExcludedCategories(List<Long> excludedCategories) {
        if (excludedCategories.contains(1L)) {
            if(!COLLECTION_UTILS.<Long>isUnmodifiableList(excludedCategories)){
                excludedCategories.remove(1L);
            }

        }
    }


}

答案 4 :(得分:0)

List<java.util.Arrays.ArrayList<E>>返回java.util.ArrayList<T>,这是一个不可变列表。要使代码正常工作,只需使用setExcludedCategories(new ArrayList<Long>(Arrays.asList(1L, 2L, 3L))); 包装结果,如下所示

{{1}}

答案 5 :(得分:0)

  
      
  1. 始终从输入列表创建新的ArrayList以确保它是可变的 - 将使用大量无用的内存 - &gt; NO。
  2.   

这实际上是做事的首选方式。 &#34;很多无用的记忆&#34;在大多数实际情况中并不是很多,当然不是在你引用的例子中。

忽略这一点,它唯一的健壮的 inotitively 理解成语。

唯一可行的替代方法是明确更改方法的名称(从而更好地传达其行为),形成您显示的示例,将其命名为&#34; removeExcludedCategories&#34;如果它意味着修改参数列表(但不是对象状态)。

否则,如果它被认为是批量制定者,那么你运气不好,就没有公认的命名习惯用法可以清楚地表明论证集合直接融入了一个对象的状态(它也是危险的)因为对象状态可以在没有对象知道的情况下被改变。)

此外,只是略有相关,我会设计不是排除列表,而是排除。集合在概念上更适合(没有重复),并且对于最常见的问题,集合实现具有更好的运行时复杂性:contains()。