Do Collections.unmodifiableXXX methods violate LSP? [closed] - java

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 4 years ago.
Improve this question
Liskov Substitution principle is one of the principles of SOLID. I have read this principle some number of times now and have tried to understand it.
Here is what I make out of it,
This principle is related to strong behavioral contract among the
hierarchy of classes. The subtypes should be able to be replaced with
supertype without violating the contract.
I have read some other articles too and I am a bit lost thinking about this question. Do Collections.unmodifiableXXX() methods not violate LSP?
An excerpt from the article linked above:
In other words, when using an object through its base class interface,
the user knows only the preconditions and postconditions of the base
class. Thus, derived objects must not expect such users to obey
preconditions that are stronger then those required by the base class
Why I think so?
Before
class SomeClass{
public List<Integer> list(){
return new ArrayList<Integer>(); //this is dumb but works
}
}
After
class SomeClass{
public List<Integer> list(){
return Collections.unmodifiableList(new ArrayList<Integer>()); //change in implementation
}
}
I cannot change the implentation of SomeClass to return unmodifiable list in future. The compilation will work but if the client somehow tried to alter the List returned then it would fail at runtime.
Is this why Guava has created separate ImmutableXXX interfaces for collections?
Isn't this a direct violation of LSP or I have totally got it wrong?

LSP says that every subclass must obey the same contracts as the superclass. Wether or not this is the case for Collections.unmodifiableXXX() thus depends on how this contract reads.
The objects returned by Collections.unmodifiableXXX() throw an exception if one tries to call any modifying method upon them. For instance, if add() is called, an UnsupportedOperationException will be thrown.
What is the general contract of add()? According to the API documentation it is:
Ensures that this collection contains the specified element (optional
operation). Returns true if this collection changed as a result of the
call. (Returns false if this collection does not permit duplicates and
already contains the specified element.)
If this was the full contract, then indeed the unmodifiable variant could not be used in all places where a collection can be used. However, the specification continues and also says that:
If a collection refuses to add a particular element for any reason
other than that it already contains the element, it must throw an
exception (rather than returning false). This preserves the invariant
that a collection always contains the specified element after this
call returns.
This explicitly allows an implementation to have code which does not add the argument of add to the collection but results in an exception. Of course this includes the obligation for the client of the collection that they take that (legal) possibility into account.
Thus behavioural subtyping (or the LSP) is still fulfilled.
But this shows that if one plans to have different behaviours in subclasses that must also be foreseen in the specification of the toplevel class.
Good question, by the way.

Yes, I believe you have it correct. Essentially, to fulfill the LSP you have to be able to do anything with a subtype that you could do with the supertype. This is also why the Ellipse/Circle problem comes up with the LSP. If an Ellipse has a setEccentricity method, and a Circle is a subclass of Ellipse, and the objects are supposed to be mutable, there is no way that Circle can implement the setEccentricity method. Thus, there is something you can do with an Ellipse that you can't do with a Circle, so LSP is violated.† Similarly, there is something you can do with a regular List that you can't do with one wrapped by Collections.unmodifiableList, so that's an LSP violation.
The problem is that there is something here that we want (an immutable, unmodifiable, read-only list) that is not captured by the type system. In C# you could use IEnumerable which captures the idea of a sequence you can iterate over and read from, but not write to. But in Java there is only List, which is often used for a mutable list, but which we would sometimes like to use for an immutable list.
Now, some might say that Circle can implement setEccentricity and simply throw an exception, and similarly an unmodifiable list (or an immutable one from Guava) throws an exception when you try to modify it. But that doesn't really mean that it is-a List from the point of view of the LSP. First of all, it at least violates the principle of least surprise. If the caller gets an unexpected exception when trying to add an item to a list, that is quite surprising. And if the calling code needs to take steps to distinguish between a list it can modify and one it can't (or a shape whose eccentricity it can set, and one it can't), then one is not really substitutable for the other.
It would be better if the Java type system had a type for a sequence or collection that only allowed iterating over, and another one that allowed modification. Perhaps Iterable can be used for this, but I suspect it lacks some features (like size()) that one would really want. Unfortunately, I think this is a limitation of the current Java collections API.
Several people have noted that the documentation for Collection allows an implementation to throw an exception from the add method. I suppose that this does mean that a List that cannot be modified is obeying the letter of the law when it comes to the contract for add but I think that one should examine one's code and see how many places there are that protect calls to mutating methods of List (add, addAll, remove, clear) with try/catch blocks before arguing that the LSP is not violated. Perhaps it isn't, but that means that all code that calls List.add on a List it received as a parameter is broken.
That would certainly be saying a lot.
(Similar arguments can show that the idea that null is a member of every type is also a violation of the Liskov Substitution Principle.)
† I know that there are other ways of addressing the Ellipse/Circle problem, such as making them immutable, or removing the setEccentricity method. I'm talking here only about the most common case, as an analogy.

I don't believe it's a violation because the contract (i.e. the List interface) says that the mutation operations are optional.

I think you are not mixing things here.
From LSP:
Liskov's notion of a behavioral subtype defines a notion of
substitutability for mutable objects; that is, if S is a subtype of T,
then objects of type T in a program may be replaced with objects of
type S without altering any of the desirable properties of that
program (e.g., correctness).
LSP refers to subclasses.
List is an interface not a superclass. It specifies a list of methods that a class provides. But the relationship is not coupled as with a parent class. The fact that class A and class B implement the same interface, does not guarantee anything about the behavior of these classes. One implementation could always return true and the other throw an exception or always return false or whatever but both adhere to the interface as they implement the methods of the interface so the caller can call the method on the object.

Related

Is Arrays.asList a violation of Liskov Substitution Principle? [duplicate]

This question already has answers here:
Do Collections.unmodifiableXXX methods violate LSP? [closed]
(4 answers)
Closed 6 years ago.
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?
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.
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.
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

Why should not be two collection interfaces be implemented together?

I know what an interface is and what is a collection. But to be honest, I could not find any solid reason as why not to implement two collection interfaces in one class.
Got this question asked a few days back in an interview.
In some cases they are or can be implemented by the same object.
A Queue and a List are both implemented by LinkedList, TreeMap is both NavigableMap and SortedMap. There are a few other examples like this.
Each describes a trait or feature of the collection (exposed as a way to use it).
It just doesn't make sense all that often. For a Java collection to implement two interfaces it must be a near perfect implementation of both (and perhaps that's your answer).
A linkedlist COULD technically implement the methods of an ArrayList, however it would be a really bad idea.
Another point that hasn't been made already is that you can't implement the same interface twice with different type parameters.
As a result, if you tried to make a class implement both List<String> and Collection<Integer>, you would get a compiler error. This is because List<String> extends Collection<String> so we're trying to implement Collection twice with different type parameters.
If you did manage to implement two collection interfaces at once, it would have to be like the LinkedList example (where the generic type is the same when you think of it as a List and when you think of it as a Deque).
For starters, incompatible method contracts: e.g. List.add must always return true, and Set.add must always return false if the element is already present.

Why many methods in JCF interfaces not made default in Java 8? [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 9 years ago.
Improve this question
Minimum complete definition of Collection interface consists only of two methods: iterator() and size(), which are abstract in AbstractCollection.
Why all other methods not made default in Java 8? Compatibility shouldn't be an issue, for example, Iterator.remove() was abstract until Java 7, but made default since Java 8.
Subclassing from AbstractCollection is inconvenient sometimes, when I want a Collection implementation to be a member of another class hierarchy. Wasn't that one of the reasons why default methods in interfaces actually needed in Java?
The same question about Map, List and other basic interfaces, comprising Java Collection Framework.
UPDATE
Paul Sandoz:
Generally we have only converted existing abstract methods on an interface to non-abstract if there was a compelling reason to aid implementations, such as Iterator.remove.
These are not new methods on Collection, and there are already implementations in AbstractCollection. The advantage of converting these abstract into non-abstract methods its not particularly compelling given one is most likely to inherit from AbstractCollection or provide more efficient implementations.
It would be possible to move all non-abstract methods on AbstractCollection to Collection. If we were starting from a blank sheet of paper that is what we might have done. (Note one cannot do this with all non-abstract methods on AbstractList.)
http://mail.openjdk.java.net/pipermail/core-libs-dev/2014-February/025118.html
The primary goal of default methods is to enable compatible evolution of interfaces. See section 10 of the State of the Lambda document. One of the main directions of this evolution is to facilitate internal iteration. See the Internal vs External Iteration section of State of the Lambda: Libraries Edition. To this end, there are new methods such as Iterable.forEach, Collection.removeIf, and List.replaceAll.
Other methods like List.sort have been added because it allows individual concrete list implementations to provide more efficient sorting algorithms, which cannot be done with Collections.sort.
Finally, default methods have been added for sheer convenience, such as Iterator.remove. Over the years, we and many others have gotten quite annoyed at adding a method remove that simply throws UnsupportedOperationException every time we implemented a new Iterator. The default method remove does this for us. Note, crucially, that this method doesn't actually remove any elements. (How would it?)
It might seem convenient to provide default implementations for a bunch of Collection methods, written in terms of other methods such as iterator. However, I don't think it's very useful, and in fact I'm not sure it's even possible for some methods.
Consider the Collection.contains(Object) method. It's conceivable that one could write a default implementation of this in terms of iterator by stepping through each element and comparing for equality. This would be a very bad implementation for something like a TreeSet or a HashSet. Even the concrete List implementations such as LinkedList and ArrayList provide fast-path implementations that are much more efficient than stepping through an iterator. Having a default implementation of Collection.contains might be a little bit convenient, but really, it doesn't add much value. In practice every collection will want to override it.
Now consider equals. The specification of Collection.equals raises a bunch of subtle issues. Briefly, a Set can only be equal to another Set, and a List can only be equal to another List, and the equals operation must be symmetric. It follows that a Collection that's neither a List nor a Set can never be equal to a List or a Set.
OK, so our Collection.equals default method will have to do a bunch of instanceof checks up front. If both are Lists we can delegate to AbstractList.equals, and if both are Sets we can delegate to AbstractSet.equals. Now let's suppose that this object and the other object neither Lists nor Sets. What if they are different concrete implementations that cannot be equal to each other? We can't tell.
Setting that aside, let's assume that we equality is defined as having the same membership. The only thing we can do is to iterate through each collection. But we can't (in general) make any assumptions about iteration order, so we can't iterate through them simultaneously and compare elements pairwise like we would for lists. Instead, we'd have to load all the elements from one collection into a temporary collection of some kind. It can't be a Set since we might have duplicates. We'd then check each element of the other Collection to make sure that every element in it is in the first one, and that there are no extras in the first one. This isn't terribly difficult, but it's expensive, and some semantics such as order sensitivity are not supported.
I can't imagine any concrete collection subclass actually wanting to use this algorithm.
In summary, using default methods to make collection implementations easier is not one of the design goals of default methods. In addition, while it might seem that providing default methods on Collection would be convenient, they don't actually seem useful. Any reasonable Collection implementation will need to override all the methods in order to provide the semantics it wants without being horribly inefficient.

Why doesn't Java have an interface similar to Comparator, but for hashing? [duplicate]

This question already has answers here:
Why there is no Hashable interface in Java
(2 answers)
Closed 9 years ago.
In Java, the Comparator interface allows a client to specify an equals() and compare() method for any type. A Comparator can be passed to most (or maybe all) collections that require sorting, and the methods in the Comparator will be used instead of the methods of the specified class. This allows clients to sort objects in a way that is different from their natural ordering, or even to sort objects that don't have a natural ordering (i.e. don't implement Comparable).
Why isn't there a similar interface for hashing? It could specify two methods, hashCode() and equals(), and be useful for HashSets or HashMaps in the same way Comparators are useful for sorting.
Edit: For those who marked this question a duplicate of this other question, I would mention that the other question asks why hashCode is included in every class instead of an interface, while this question is about abstracting the hashing function to allow multiple implementations of it.
Answer Edit: The best methods of getting this functionality seem to be:
-If you're ok using an external library and/or are already using Guava (which is a fantastic library for a lot of reasons), Guava has an Equivalence class that allows this.
-If you don't want to use an external library, you can use a custom built adapter, similar to what is done in the top answer on this SO question.
Questions of the form
"Why doesn't Java have XXX"
are difficult to answer objectively except with a generic
"We don't know because nobody here was in the room when the decision was made."
In this case:
On the face of it, this requirement can be implemented ... from a technical perspective.
This requirement has been proposed numerous times via RFEs. The most direct one is http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6435963. The RFE is marked as WONT FIX, though no particular reason is given.
This requirement could be, and has been adequately met by 3rd party libraries.
My reading of this is that it is not supported because it has not been deemed important enough for enough people that it needs to be supported. I'd say they made a reasonable call on that.
I also think that such an interface would be handy, specifically as a way to consider different kinds of equalities in collections. It should be similar to the way that you can make an object Comparable, but still override that behavior in a specific collection by providing some other Comparator.
As was pointed out in this question, Guava has an Equivalence class that provides a way to do this, by wrapping your class and letting you define at the wrapper level what 'equality' means in this context.
If your question is really why this didn't happen at language design time... well, hey, James Gosling and company are only human, right?
Hashing has really just one requirement: Being like a hash method. So you can implement it for a type without knowing who will use it in what way and for what purpose. So the hash method on the object itself is sufficient.
Equals on the other hand has different meanings in different contexts. For example you can sort people by first name, last name, age, size, weight, time of membership in a club ... So it makes sense to have different implementations of equal (and 'less than') for a single class.
Of course nothing keeps you from creating such an interface and using it ...
You can always change the hashcode() to say how the HashMap arranges your object in Map and improve its performence by implementing an effective hashcode method in your class.
How HashMap performs addition and removal from a hashed data is internal to HashMap and changing it would basically mean changing add,remove methods etc.
Also sorting is a common feature that is used more often and for a rare case when you really want to change how hashing happens in a map then there is always the option to extend a Map.
Let me try to say how I see it, why.
Briefly - you often need to sort a list objects, but you rare (if ever) need to cross compare identities of a list of object.
In Object, method hashCode is a secondary, helpful method, which main purpose is to serve the equals method. The agreement is that if for two objects hashCode returns different values, equals mustn't return true.
So, methods hashCode and equals serve to establish the identity of an object.
Method compareTo (in both Comparable and Comparator) serves another general purpose. It defines objects order, not their identity.
Resume - compareTo defines how the objects are ordered, hashCode (together with equals) defines objects identity.
Again - practice contribution is that you often have to sort a group of objects, but you rare (if ever) have to take a group of objects and cross compare their identities.

Correct behavior for interface methods that can't be implemented

If I have a class that needs to implement an interface but one or more of the methods on that interface don't make sense in the context of this particular class, what should I do?
For example, lets say I'm implementing an adapter pattern where I want to create a wrapper class that implements java.util.Map by wrapping some immutable object and exposing it's data as key/value pairs. In this case the methods put and putAll don't make sense as I have no way to modify the underlying object. So the question is what should those methods do?
Any method that cannot be implemented according to the semantics of the interface should throw an UnsupportedOperationException.
That depends on your business case. 2 options:
Do nothing.
Throw an UnsupportedOperationException.
Use whichever makes more sense. If you do nothing, you are not obeying the contract of the interface. However, throwing a runtime exception can wreak havoc on the calling code. Thus, the decision will have to be made based on how you will be using the class. Another option would be to use a simpler or different interface, if possible.
Do note that the Java library goes the exception route in the specific case of read-only collections.
It was noted below that UnsupportedOperationException is a part of the java collections framework. If your situation is outside of collections, and the semantics bother you, you can roll your own NotImplementedException, or if you are already using commons-lang, you could use theirs.
That read-only collection already provided by Java throw the UnsupportedOperationException during write operations is already an unfortunate design hack. The collection classes should have been written with separate read-only and write-only interfaces that are both inherited by the full read-write interface. Then you know what you're getting.
Your two choices are really only:
Do nothing.
Throw an exception.
Both have disadvantages. In the first case, by having an empty method, you could mislead the programmer into thinking something happened to your data. The second case breaks the whole idea of polymorphism inherent in interfaces.
Note that UnsupportedOperationException is only OK because of the particular property of the Java Collections Framework, that implementations are permitted to "goof off" implementing part of the interface because they're immutable.
So it's fine for put() (assuming all the mutator methods do the same thing), but a Map which throws UnsupportedOperationException from the size() method would just be broken. If you're trying to implement a kind of map which doesn't know how big it is, you may be in trouble (although sometimes you can return Integer.MAX_VALUE).
Also note that the class documentation for UnsupportedOperationException says that it is part of the Java Collections Framework. Outside the collections framework, throwing UnsupportedOperationException is not be expected and could lead to client code that plain doesn't work. Sure, it's a RuntimeException, but just because you can throw it doesn't mean that your method will work if it always does.
Instead you could either refactor the interface (perhaps splitting it in two), or else rethink why this class is claiming to be a Foo when it plainly isn't, since it can't do the things that Foos are defined to be able to do.

Categories

Resources