3

Arrays.asList(..) returns a List wrapper around an array. This wrapper has a fixed size and is directly backed by the array, and as such calls to add() or other functions that attempt to modify the list will throw an UnsupportedOperationException.

Developers are often surprised by this, as is evident from questions in stackoverflow.

However the List interface has an add() method which should work unsurprisingly for all derivers of List, according to the Liskov Substitution Principle (LSP)

Is the type returned by Arrays.asList() an example of a violation of the Liskov Substitution Principle?

Gonen I
  • 5,576
  • 1
  • 29
  • 60

3 Answers3

6

Strictly speaking, it is, because LSP has no notion of optional interface members: a method is either part of an interface, or it is not part of an interface.

However, Java Class Library explicitly allows for violations of LSP when it designates certain interface methods as optional. List<T>.add() is one such method. Other mutating methods (addAll, remove, etc.) are also marked optional.

Essentially, designers of Java library took a shortcut: rather than making a separate interface for mutable list (extending a read-only list) they went for designating an operation "optional". Moreover, they have not provided a way for you to test the instance of list for being read-only, so your only option is to catch a runtime exception, which is a very bad idea. This amounts to you having to keep track of where your lists came from, and performing optional operations only when you are 100% certain of your list origin.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • This explanation is also the direction in which I was leaning. – Gonen I Sep 03 '16 at 23:01
  • 1
    @user889742 Right - LSP has no concept of "optional operation", everything is perfectly binary. Java library designers took a shortcut by not defining a separate interface for read-only lists, creating this mess with optional operations. – Sergey Kalinichenko Sep 03 '16 at 23:04
  • Hi @Sergey. I have an abstract class that has the database modification and fetching operations. But some subclasses should not modify the data. Should i take a shortcut or separate operations? – Arash Oct 26 '21 at 09:44
5

I think it is not a violation of LSP.

LSP says that all instances of classes implementing a given interface can be used interchangably.

The documentation of List.add (and other mutating methods) clearly states that an UnsupportedOperationException might be thrown by implementations of that method.

Throws

UnsupportedOperationException - if the add operation is not supported by this list

As such, if you're going to invoke this method on an instance of List from an unknown source, you need to handle the case that add throws an UnsupportedOperationException.

If you don't, you're not using the API correctly.


This is not to say that I like the design. I think the fact that trying to invoke the method is the only way to detect that any given method is not supported on an instance is rubbish. I mean, heck, give us an isAddSupported() method (or similar).

I just don't see that following documented behaviour can be violating LSP.

Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • Thanks. But I'm concerned that if you could defend LSP violations with documentation, you could write on Rectangle.setWidth // Note, may not work on all derivers of rectangle, and then derive a square from it, saying that this is in line with LSP – Gonen I Sep 03 '16 at 22:57
  • 1
    @user889742 yes, you could do that, and users of your class would need to handle it. Ideally, you should convey it through recognized documentation conventions, like the `@throws` stanza in Javadoc; but really, anything you write in the public documentation of your class is the contract of the behaviour against which code should be written. – Andy Turner Sep 03 '16 at 22:58
  • @dasblinkenlight I'm not actually recommending catching the exception. I'm saying you need to be aware that it might happen. – Andy Turner Sep 03 '16 at 23:03
  • @dasblinkenlight but I don't say to catch the exception, I just say "handle the case". Would you be happier if I said something along the lines of "if you don't, you're ignoring the contract of the class"? – Andy Turner Sep 03 '16 at 23:15
0

If you think about it in very technical terms, then of course it is a violation. LSP states that a bad design is one in which the inherited class cannot use superclass methods. Java though, doesn't always care for violations. This is often, as you suggested, a way for confusion among developers. The add() method is one example, so is remove(). Both of these are available for an immutable list, and that cannot be changed. The good thing is that at least an exception is thrown.

Further reading: http://c2.com/cgi/wiki?UnmodifiableListIsStupidAndItBreaksLsp

Arnav Borborah
  • 11,357
  • 8
  • 43
  • 88