How to test if a List<? extends Object> is an UnmodifableList?

感情迁移 提交于 2019-11-29 11:11:09

问题


I'm looking for a way to test if some given List is an unmodifiable one.

I have an object that has a List<NoMatter>, in order to offer methods such as addNoMatter(NoMatter nm) instead of allowing the API client to simply do .getNoMatters().add(nm); I always return an unmodifiable version of this list, so the client is still able to have the list. I do it as follows:

public List<NoMatter> getNoMatters() {
    return Collections.unmodifiableList(this.noMatters);
}

The problem is that when I'm doing my tests I simply cant check if this object is of type UnmodifiableList. My first try was doing:

@Test
public void checkIfListIsImmutable(){
    assertTrue("List is not immutable", this.myObj.getNoMatters() instanceof UnmodifiableList);
}

Happens that I cant seem to be able to import the type UnmodifiableList neither java.util.Collections$UnmodifiableRandomAccessList that is what I get when I try System.out.println(myObj.getNoMatters().getClass().getName()); at the console.

So how can I achieve it???

PS.: I know I can pass this test by doing:

@Test(expected = UnsupportedOperationException.class)
public void checkIfListIsImmutable(){
     this.myObj.getNoMatters().add(null);
}

EDIT: The above test doesn't grants me that the list Im dealing with isn't immutable as I would need to have tests to every method that may modify my original list, including .remove(), .clear(), .shuffle(), etc! thats why I do not think this is a nice way to proceed.

>>> But I still believe that its not even close to an elegant solution! <<<


回答1:


I actually think that is your best bet. Your alternative (less elegant) way is to check the name of the class.

this.myObj.getNoMatters().getClass().getSimpleName().equals("UnmodifiableCollection")

The issue for you is the wrapped UnmodifiableCollection is package-private.

I don't see anything wrong with expecting an exception there, but that's just me.




回答2:


I think your solution is not only reasonable, it is elegant. You want to test that you cannot modify the list, and your test proves it succinctly. Testing the name of the class tests the name, not the behavior. In this case, who cares about the name?

Likewise, if I wanted to test that I cannot pass null to some method, I'd pass null in a test and expect an IllegalArgumentException.




回答3:


You can use Class#isInstance to check.

Collections.unmodifiableList(someList).getClass().isInstance(listToCheck);

/e1
The following returns false.

Collections.unmodifiableList(new ArrayList<Object>()).getClass().isInstance(new ArrayList<Object>())

The following returns true.

Collections.unmodifiableList(new ArrayList<Object>()).getClass().isInstance(Collections.unmodifiableList(new ArrayList<Object>()))

/e2
Your problem may be because Collections#unmodifiableList returns an UnmodifiableList some of the time, and an UnmodifiableRandomAccessList the rest of the time (see below for code). However, since UnmodifiableRandomAccessList extends UnmodifiableList, if you get an instance of an UnmodifiableList to check with you'll be golden.

To obtain an instance of UnmodifiableList you can use Collections.unmodifiableList(new LinkedList<Object>()). LinkedList does not implement RandomAccess, so an instance of UnmodifiableRandomAccessList will not be returned.

Code of Collections#unmodifiableList:

public static <T> List<T> unmodifiableList(List<? extends T> list) {
        return (list instanceof RandomAccess ?
                new UnmodifiableRandomAccessList<>(list) :
                new UnmodifiableList<>(list));
}

Class header of UnmodifiableRandomAccessList:

static class UnmodifiableRandomAccessList<E> extends UnmodifiableList<E> implements RandomAccess



回答4:


Why do you want to test that your list is immutable? Your "alternative" solution is the way to go: you should focus on testing the behaviour of your class, not its state.

What I mean is you don't really care whether your method returns an instance of UnmodifiableList or anything else. Who cares? Your test should certainly not. If later on you change the implementation to achieve the exact same behaviour, your test should not fail.

So what do you want to test? That users cannot add anything to the list? Then write a test (as you suggested) that adds something to the list and expect an exception. That they can't remove anything from the list? Do the same with another test. And so forth.

It is indeed elegant to test for negative cases like these ones, and I found it's very often forgotten in the functional coverage of a class. Proper coverage should specifically state what your class disallow and say what's expected in these cases.




回答5:


sort of a hack, but try:

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);
    }
}



回答6:


I wish something like Collections.examine(T element) does the job without actually modifying the collection.

Besides the most voted answer, be advised that there's Collections.EmptyList (at least) which is another unmodifiable list as well.

So, if I really have to create a utility as a dirty solution though, I will add my 'easy-to-identify' element and see if it throws UnsupportedOperationException, then it means it is unmodifiable. Otherwise, I have to remove the entered right away of course.




回答7:


In addition to user949300 answer, which states that one should indeed test for the behavior, and including the comment by Steve Zobell, who suggests to add an empty list so in case the list is not unmodifiable it doesn't get modified, I add here an example of how a check for unmodifiability of a list could look like.

The code tries to add an empty list and if that is possible throws a IllegalArgumentException, otherwise as a convenience returns the original list of which one can now be sure that it is unmodifiable.

/**
 * 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.");
}

Not sure if testing for remove, clear, ... is also necessary. Not being able to add is usually a good indication of unmodifiability.




回答8:


If API is hard to test that's often a smell that API requires improvement. In this case the problem is that API is supposed to return read-only list but (due to the lack of such type in JDK I guess) it returns more generic type.

I would consider to change function signature so that it returns the type that is effectively a read-only list, for example guava's ImmutableList.

It is by definition can't be modified so you don't need test at all. It's also causes less confusion in clients of the class.



来源:https://stackoverflow.com/questions/8364856/how-to-test-if-a-list-extends-object-is-an-unmodifablelist

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!