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

12

29

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! <<<

Tantra answered 3/12, 2011 at 1:47 Comment(4)
I suggest using "addAll(Collections.emptyList());" instead of add(null) to test for immutable. This will raise the UnsupportedOperationException if immutable and does not modify the list if mutable. I agree modifying the list to see if it is immutable feels wrong, and could cause problems if anyone else is using the list.Rockbottom
@SteveZobell's suggestion is like a cherry on the cake. Solves the issue when the List is actually mutable. One thing to think about is that UnmodifiableList is not the only unmodifiable list in Java API. Collections.nCopies(..) returns an instance of Collections$CopiesList which is also unmodifiable. It is so easy to implement an unmodifiable list from AbstractList, so the best bet is to test the behavior.Ea
@SteveZobell Tysvm for your suggestion. It was EXACTLY what I was looking for when I came here.Esurient
I agree with @Ea that the focus should be on the behavior itself. Hence, I have formalized the solution in my personal and corporate libraries, which is captured in this StackOverflow Answer: https://mcmap.net/q/67708/-how-to-test-if-a-list-lt-extends-object-gt-is-an-unmodifablelistEsurient
S
9

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.

Starlike answered 3/12, 2011 at 1:54 Comment(2)
try/catch and try adding to it?Parapsychology
This is both a poor performance and poor implementation choice. Please see the StackOverflow answer for both context and a more performant and safer approach: https://mcmap.net/q/67708/-how-to-test-if-a-list-lt-extends-object-gt-is-an-unmodifablelistEsurient
O
20

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.

Oresund answered 3/12, 2011 at 2:1 Comment(4)
You're right, I agree w/ you (upvoted even), but its not too good if you wanna test many lists at the same test once the test stops when first exception happens!Tantra
I'm used to JUnit 3, so YMMV, but you should be able to write try { list.add(null); fail(); } catch (RuntimeException expected) { ; } I even made an Eclipse template for this called testfailOresund
p.s. - in the above template, change RuntimeException to be as specific as possible to the exception you expect, e.g. UnsupportedOperationException in this example.Oresund
At some point, the effort put into unit tests reaches a near-zero return. I think you are reaching it. You could test for all the myriad possibilities. You could write your own Immutable wrapper that throws a specific exception, MyUnsupportedOperationException, and, assume that that proves that your wrapper is there. Or you could just try add(), perhaps remove(), and be happy. The chance that some weird class will throw the exception for remove, yet will allow you to call clear(), seems remote.Oresund
S
9

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.

Starlike answered 3/12, 2011 at 1:54 Comment(2)
try/catch and try adding to it?Parapsychology
This is both a poor performance and poor implementation choice. Please see the StackOverflow answer for both context and a more performant and safer approach: https://mcmap.net/q/67708/-how-to-test-if-a-list-lt-extends-object-gt-is-an-unmodifablelistEsurient
B
8

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
Bilk answered 3/12, 2011 at 2:30 Comment(4)
it doesnt work well as it return true both when the list to check is ArrayList or Collections.unmodifiedList(list)Tantra
@RenatoGama See my second edit, I think I found the problem you're encountering.Bilk
Hi - you can also check UnmodifiableCollection, which is a base class for the unmodifiable list and set types. This can be retrieved from Collections.unmodifiableCollection(Collections.emptyList()).getClass()Gifferd
This solution is poor in terms of memory performance; i.e. the creation of the ArrayList<> noisily pollutes the GC's work. Here's a solution to avoid the undesirable memory performance overhead: https://mcmap.net/q/67708/-how-to-test-if-a-list-lt-extends-object-gt-is-an-unmodifablelistEsurient
I
5

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.

Investigator answered 4/4, 2018 at 14:41 Comment(1)
There is no need to specify the generic type. Please see this StackOverflow Answer to see how to do so sans the generic type overhead and complexity: https://mcmap.net/q/67708/-how-to-test-if-a-list-lt-extends-object-gt-is-an-unmodifablelistEsurient
B
2

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.

Bertold answered 3/12, 2011 at 23:0 Comment(2)
That is absolutely the right thing to do. Thank you for reminding me about the difference between testing state and behaviour.Woolard
This is a poor answer. Having mutable state in collections in a multi-threaded environment is a source of incredibly difficult to track down bugs. The purpose of ensuring the collections (imperfectly) immutable enables all sorts of simplification in reasoning on concurrency environments. And as Java moves increasingly towards FP, it is essential it also move towards immutability, of which the unmodifiable facade mostly enables.Esurient
T
2

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.

Tamqrah answered 13/12, 2016 at 18:36 Comment(1)
Please see this StackOverflow Answer: https://mcmap.net/q/67708/-how-to-test-if-a-list-lt-extends-object-gt-is-an-unmodifablelistEsurient
D
2

Set, List and Map support the method copyOf(...) since Java 10.
It ensures you get an immutable set/list/map. If the argument is already an immutable set/list/map, it just returns it. If it is modifiable/mutable, copyOf(...) copies the elements/entries into an immutable set/list/map and returns it.
However, if one modifiable set/list/map was made unmodifiable by Collections.unmodifiableSet(...)/unmodifiableList(...)/unmodifiableMap(...),
copyOf(...) copies all elements/entries to an immutable set/list/map to ensure that it cannot be modified by someone still holding an instance of the modifiable set/list/map.

Deloisedelong answered 4/8, 2021 at 2:19 Comment(0)
G
2

it looks like Collections.unmodifiable* are smart enough not to wrap an already unmodifiable collection:

jshell> import java.util.*;

jshell> var unmodifiable = Collections.unmodifiableList(new ArrayList<String>())
unmodifiable ==> []

jshell> unmodifiable == Collections.unmodifiableList(unmodifiable)
$3 ==> true

jshell> var unmodifiableMap = Collections.unmodifiableMap(new HashMap<String,String>())
unmodifiableMap ==> {}

jshell> unmodifiableMap == Collections.unmodifiableMap(unmodifiableMap)
$5 ==> true
Goddard answered 12/8, 2022 at 21:33 Comment(1)
You can actually see it explicitly tested for in the source code for the methods in the OpenJDK.Esurient
B
0

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);
    }
}
Betts answered 3/12, 2011 at 2:1 Comment(1)
This is definitely a dangerous hack in that it modifies the target collection. I'd recommend a safe way to perform the same test, captured in this StackOverflow Answer: https://mcmap.net/q/67708/-how-to-test-if-a-list-lt-extends-object-gt-is-an-unmodifablelistEsurient
F
0

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.

Finnegan answered 5/4, 2018 at 22:10 Comment(0)
E
0

I have the same issue in that I need a cheap and easy way to discover if a List, Set, or Map has been wrapped in an unmodifiable facade, regardless of how.

I use DbC (Design By Contract) to ensure my business object and DAO layers have strict state integrity. IOW, I work very hard to ensure that no instances and entities can enter an invalid state.

And I want an easy way to assert a precondition that when a List, Set, or Map is passed into one of my methods, it must be wrapped in an unmodifiable facade.

Here are the two methods I have added to my CollectionsUtils class where I put all of these kinds of solutions. I hope it helps anyone who is seeking this obvious gap in the Java standard collections API.

/**
 * Returns {@code true} if the {@link Collection} throws an {@link UnsupportedOperationException}
 * when calling {@link Collection#addAll} with {@link Collections#emptyList}, false otherwise.
 *
 * @param collection instance being tested for being unmodifiable
 * @return {@code true} if the {@link Collection} throws an {@link UnsupportedOperationException}
 * when calling {@link Collection#addAll} with {@link Collections#emptyList}, false otherwise
 */
public static boolean isUnmodifiable(Collection<?> collection) {
  try {
    collection.addAll(Collections.emptyList());

    return false;
  } catch (UnsupportedOperationException UnsupportedOperationException) {
    return true;
  }
}

/**
 * Returns {@code true} if the {@link Map} throws an {@link UnsupportedOperationException} when
 * calling {@link Map#putAll} with an empty {@link Map#of}, false otherwise.
 *
 * @param map instance being tested for being unmodifiable
 * @return {@code true} if the {@link Map} throws an {@link UnsupportedOperationException} when
 * calling {@link Map#putAll} with an empty {@link Map#of}, false otherwise
 */
public static boolean isUnmodifiable(Map<?, ?> map) {
  try {
    map.putAll(Map.of());

    return false;
  } catch (UnsupportedOperationException UnsupportedOperationException) {
    return true;
  }
}
Esurient answered 9/2 at 22:9 Comment(2)
Exceptions should not be used for regular flow, only for exceptional flow. This is misuse of exceptionsSinkhole
Agreed! I hate that this is the only reliable option that the Java Collection Library authors both didn't follow OOP principles nor provide a this funtionality.Esurient
R
-1

Try this:

public <T> boolean isModifiableCollection(Collection<T> collection) {
    T object = (T) new Object();
    try {
        collection.add(object);
    } catch (Exception e) {
        return false;
    }
    collection.remove(object);
    return true;
}
Regretful answered 4/8, 2023 at 23:5 Comment(2)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Uta
This is a dangerous solution, especially in a multi-threaded environment, in that it actively modifies the contents of the collection which is a huge NO-NO! There is a way to perform the same test WITHOUT actually modifying the collection. I have encapsulated in this StackOverflow Answer: https://mcmap.net/q/67708/-how-to-test-if-a-list-lt-extends-object-gt-is-an-unmodifablelistEsurient

© 2022 - 2024 — McMap. All rights reserved.