我正在寻找一种方法来测试一些给定的List是否是不可修改的。
我有一个List<NoMatter>
的对象,为了提供addNoMatter(NoMatter nm)
之类的方法,而不是让API客户端只执行.getNoMatters().add(nm);
我总是返回一个不可修改的版本列表,所以客户端仍然可以拥有该列表。我这样做:
public List<NoMatter> getNoMatters() {
return Collections.unmodifiableList(this.noMatters);
}
问题在于,当我进行测试时,我无法检查此对象是否为UnmodifiableList类型。我的第一次尝试是:
@Test
public void checkIfListIsImmutable(){
assertTrue("List is not immutable", this.myObj.getNoMatters() instanceof UnmodifiableList);
}
发生我似乎无法导入UnmodifiableList
类型java.util.Collections$UnmodifiableRandomAccessList
,这是我在控制台尝试System.out.println(myObj.getNoMatters().getClass().getName());
时获得的内容。
那我怎么能实现呢???
PS:我知道我可以通过以下方式通过测试:
@Test(expected = UnsupportedOperationException.class)
public void checkIfListIsImmutable(){
this.myObj.getNoMatters().add(null);
}
编辑:以上测试并没有授予我处理的列表不是不可变的,因为我需要对可能修改原始列表的每个方法进行测试,包括.remove (),. clear(),. shuffle()等!这就是为什么我认为这不是一个很好的方法。
&GT;&GT;&GT;但我仍然相信它甚至不是一个优雅的解决方案! &LT;&LT;&LT;
答案 0 :(得分:15)
我认为您的解决方案不仅合理,而且优雅。您想测试您无法修改列表,并且您的测试证明它简洁。测试类的名称测试名称,而不是行为。在这种情况下,谁在乎这个名字?
同样,如果我想测试我不能将null传递给某个方法,我会在测试中传递null并期望IllegalArgumentException。
答案 1 :(得分:7)
我认为这是你最好的选择。您的替代方法(不太优雅)是检查班级的名称。
this.myObj.getNoMatters().getClass().getSimpleName().equals("UnmodifiableCollection")
问题在于包裹的UnmodifiableCollection是包私有。
我没有看到在那里期待一个例外有什么不妥,但那只是我。
答案 2 :(得分:5)
您可以使用Class#isInstance
进行检查。
Collections.unmodifiableList(someList).getClass().isInstance(listToCheck);
/ E1
以下内容返回false
。
Collections.unmodifiableList(new ArrayList<Object>()).getClass().isInstance(new ArrayList<Object>())
以下内容返回true
。
Collections.unmodifiableList(new ArrayList<Object>()).getClass().isInstance(Collections.unmodifiableList(new ArrayList<Object>()))
/ E2
您的问题可能是因为Collections#unmodifiableList
在某些时候会返回UnmodifiableList
,其余时间会返回UnmodifiableRandomAccessList
(请参阅下面的代码)。但是,由于UnmodifiableRandomAccessList
扩展了UnmodifiableList
,如果您获得UnmodifiableList
的实例来检查,那么您将是金色的。
要获取UnmodifiableList
的实例,您可以使用Collections.unmodifiableList(new LinkedList<Object>())
。 LinkedList
未实现RandomAccess
,因此不会返回UnmodifiableRandomAccessList
的实例。
Collections#unmodifiableList
代码:
public static <T> List<T> unmodifiableList(List<? extends T> list) {
return (list instanceof RandomAccess ?
new UnmodifiableRandomAccessList<>(list) :
new UnmodifiableList<>(list));
}
UnmodifiableRandomAccessList
的类标题:
static class UnmodifiableRandomAccessList<E> extends UnmodifiableList<E> implements RandomAccess
答案 3 :(得分:4)
为什么要测试您的列表是不可变的?您的“替代”解决方案是可行的方法:您应该专注于测试类的行为,而不是州。
我的意思是你并不关心你的方法是否返回UnmodifiableList
或其他任何实例。谁在乎?你的测试当然不应该。如果您稍后更改实施以实现完全相同的行为,您的测试不应失败。
那么你想测试什么?用户无法在列表中添加任何内容?然后编写一个测试(如你所建议的那样),它会在列表中添加一些内容并期望出现异常。他们无法从列表中删除任何内容?对另一个测试做同样的事情。等等。
测试像这些负面情况确实很优雅,我发现它经常被遗忘在课程的功能性覆盖范围内。适当的报道应明确说明您的课程不允许,并说明在这些情况下的预期。
答案 4 :(得分:0)
是一种黑客攻击,但请尝试:
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Integer> list=new LinkedList<Integer>();
list.add(1);
List<Integer> unmodifiableList=Collections.unmodifiableList(list);
System.out.println(unmodifiableList.getClass());
if(unmodifiableList.getClass().getName().contains("UnmodifiableList"))
System.out.println(true);
}
}
答案 5 :(得分:0)
我希望像Collections.examine(T element)
之类的东西可以在不实际修改集合的情况下完成工作。
除了投票最多的答案之外,请注意,Collections.EmptyList
(至少)是另一个不可修改的列表。
所以,如果我真的必须创建一个实用程序作为一个脏解决方案,我将添加我的'易于识别'元素,看看它是否抛出UnsupportedOperationException,然后它意味着它是不可修改的。否则,我必须立即删除输入的内容。
答案 6 :(得分:0)
除了user949300回答之外,其中指出一个人应该测试行为,并包括Steve Zobell的评论,他建议添加一个空列表,以防列表不可修改它没有被修改,我在这里添加了一个如何检查列表的不可修改性的示例。
代码尝试添加一个空列表,如果可能则抛出IllegalArgumentException,否则为方便起见,返回原始列表,现在可以确定它是不可修改的。
/**
* Ensures that a list is unmodifiable (i.e. that nothing can be added).
*
* Unmodifiable lists can e.g. be created by Collections.UnmodifiableList().
*
* @param list a list
* @param <T> element type of the list
* @return the list that is verified to be unmodifiable
*/
public static <T> List<T> verifyUnmodifiable(List<T> list) {
try {
list.addAll(Collections.emptyList());
} catch (Exception e) {
return list;
}
throw new IllegalArgumentException("List is modifiable.");
}
不确定是否还需要测试remove,clear,...无法添加通常是不可修改的良好迹象。
答案 7 :(得分:0)
如果API难以测试,那通常是API需要改进的气味。在这种情况下,问题是API应该返回只读列表但是(由于JDK中缺少这种类型,我猜)它返回更通用的类型。
我会考虑更改函数签名,以便返回实际上是只读列表的类型,例如guava的ImmutableList。
根据定义,无法修改,因此您根本不需要测试。这也减少了班上客户的混淆。
答案 8 :(得分:0)
Set、List 和 Map 从 Java 10 开始支持方法 copyOf(...)。
它确保您获得一个不可变的集合/列表/映射。
如果参数已经是
不可变的 set/list/map,它只是返回它。
如果它是可修改/可变的,copyOf(...) 将元素/条目复制到一个不可变的 set/list/map 中并返回它。
然而,如果一个可修改的集合/列表/地图被 Collections.unmodifiableSet(...)/unmodifiableList(...)/unmodifiableMap(...),
copyOf(...) 将所有元素/条目复制到一个不可变的 set/list/map 中,以确保它不能被仍然持有可修改 set/list/map 实例的人修改。