Unmodifiable List Is Stupid And It Breaks Lsp

UnmodifiableListIsStupidAndItBreaksLsp refers to the following problem. Here is the JDK section for Collections.unmodifiableList:

public static List unmodifiableList(List list)

Returns an unmodifiable view of the specified list. This method allows modules to provide users with "read-only" access to internal lists. Query operations on the returned list "read through" to the specified list, and attempts to modify the returned list, whether direct or via its iterator, result in an Unsupported-Operation-Exception. The returned list will be serializable if the specified list is serializable.

Parameters:list - the list for which an unmodifiable view is to be returned.

Returns: an unmodifiable view of the specified list.

Now, let's think this through. When you look at a List reference you've received as a method argument in your program, you have no idea whether it is a regular list or one which will go bang when you add. This is a violation of the LiskovSubstitutionPrinciple (LSP). There is one way to make sure that you know whether your list will go bang when you add and that is to keep the scope of any unmodifiable lists you create pretty tight. Don't pass them around. But, if you aren't going to pass them around, can't you just remember not to modify them? :-)


I think a more interesting point is why does unmodifiable list implement List? Why isn't an unmodifiable list a super interface for the modifiable list? That way, if your function doesn't modify its data, it uses the unmodifiable list, and if you get a modifiable list, its a legal upcast, but the compiler won't LET you modify the list from an unmodifiable list reference. -- JeffBay

So basically you're arguing in favor of ConstQualifier?

No, I'm arguing for using the existing facilities of the language to push problems to CompileTime, which is inherently better than finding out at RunTime, and is the entire driving force behind having a statically typed language. Breaking this ideal by having unmodifiable collections implement what is essentially a narrower interface and then throw runtime exceptions on the wider interface methods is just silly. All they have to do is think through these issues a little more, with their own language drivers in mind, and they could have dramatically improved a) the collections interface and b) one of the most widely used examples by novice programmers of how to structure statically typed packages. *bleah*

What I'm saying is a more direct way of saying "it breaks the LiskovSubstitutionPrinciple".

-- JeffBay


An interesting data point - the unmodifiable wrappers are used exactly twice in the standard API (java.awt.RenderingHints, java.security.Provider), and twice more in the J2EE extensions (in the RMI-IOOP stuff). That is, not much. I'm wondering what would have been better - and more generally useful - separate interfaces for accessing Collections and altering them? Internal iterators?


On the contrary, unmodifiable Lists are extremely useful. This is when you have to stop spouting principles and use common programmer sense. Programmers use them only where the make sense. I wouldn't pass an unmodifiable list to Collections.sort() because that would be stupid. Similarly, I wouldn't try to modify a static final List (of Stategy objects or something) unless the documentation specifically said it was okay. There's really no reason that this should ever be a problem.

I can see where an unmodifiable list might be of use when you pass a List as an argument or return it from a method, but I don't think that either of these is a good idea (at lease, pre Java 5 generics). Doing so distances the code that's inserting into the list and the code that's retrieving. As these have to agree on the datatype contained in the list, and the retriever has to do a cast, this is an error-prone practice. I recommend using list.toArray() to create an array for such uses and Arrays.asList() if you need a list on the other side of the fence.


Actually, I think JavaEmptyListIsBroken because it does not have this behavior. For example, you can add items to an empty list but it will always return zero for its size, get will always yield an index out of bounds exception, and contains will always return false. This can cause "hard to find" programmer errors. "Man, I keep adding items to this collection object but when I try to iterate through them I get nothing." Would have been much better to have #add throw an exception. -- RobertDiFalco


No, the unmodifiable list does not break annything. The resulting list still perfectly follows the contract. Let's take a look at what the cotract for java.util.List says:

        public void add(int index,
                Object element)

Inserts the specified element at the specified position in this list (optional operation). Shifts the element currently at that position (if any) and any subsequent elements to the right (adds one to their indices).

Parameters: index - index at which the specified element is to be inserted.element - element to be inserted.

Throws: UnsupportedOperationException - if the add method is not supported by this list.
(some more exceptions removed for clarity)

Note the exception that is declared. This is also what the unmodifiable list will return for the add() method. This means that it will still follow the contract and thus there is no problem.

Let's see. So, if it also declared that it threw UnsupportedTimeOfDay, and pretty much always threw that exception no matter what, then it would be following the contract, so there would be no problem.

Cool, finally a way to create software with no bugs.

More seriously, the usual and recommended way of dealing with a List is to simply work with it as if it will not ever throw that exception. The 'grain' of the language would imply that UnsupportedOperationException should actually be a checked exception, because it is something that needs to be checked in order to comply with the contract. Either you deal with it, or you notify all of your callers that you may throw this exception as well.


It gets worse than that. I worked on a project once where one of the programmers thought putting objects into an UnmodifiableList? would make the objects themselves unmodifiable, even if they were mutable types. Yipes!


While I agree that a collection with a read-only interface would be better, the unchecked exception is the proper way to go - this exception will be thrown only if the developer uses the list without reading documentation (that should exist) that this list is read-only. A method should return either a read-only or a modifiable list, not both.


Actually, it's not as simple as creating a new interface for an unmodifiable List. All Lists may be iterated, and the Iterator has a remove() method to remove the last-read item. This, of course, would presumably throw an UnsupportedOperationException if you tried it on an unmodifiable List. But if we had a separate interface for read-only lists, then we would need two Iterator interfaces as well. Then the iterator() method would have to be different for the two kinds of lists. And to top it off, this would complicate the Iterable interface, which gives us the for-each statement. All of this falls apart if you create a separate interface for read-only lists. I don't know how any of this could be made to work as cleanly as it does now without getting rid of the remove() method from the Iterator, but it's a very useful method. In retrospect, the current design gives us a great deal of flexibility. If we have to put up with an occasional UnsupportedOperationException (which I don't think I've ever actually seen), that's a small price to pay for a cleaner design overall and a very useful remove() method.


Dude you don't get it. UnmodifiableList? isn't for the the contract or the types. It is so that untrusted code (which is not allowed to use non-public reflection) cannot break out of the sandbox. See that downcasting UnmodifiableList? doesn't work but a "proper" inheritance hierarchy would.


Comments on Page:


EditText of this page (last edited October 5, 2014) or FindPage with title or text search