I want to propose that java.util.List and java.util.Set should have been implemented with inheritance rather than interfaces.
Whenever I would give people an example of Java Interfaces done correctly, I used to point to java.util.List as an example.
List is a true data type, not just a collection of methods. It’s a well defined concept that doesn’t change often. Plus all the implementations of List are in the same jar as the interface, so there are no messy factories or Reflection to deal with. List is the ideal candidate for Interface use.
But then it occurred to me that 99.9% of the time, when we use List, we are using ArrayList as the implementation. If you need to use Vector, that’s a sign you have some other kind of design problem because your code should be naturally thread-safe without locking. Likewise 99.9% of the time that we use “Set” we use “HashSet”.
I always follow the K.I.S.S. principle in my designs. I favor simplicity over features even though that’s ironically more difficult to accomplish. So if I was in charge of Java, I would eliminate the List and Set interfaces and make people use concrete types. LinkedHashSet would simply inherit from HashSet, and Vector would inherit from ArrayList, so you could still have your polymorphic behavior.
I don’t propose this just because I find Java Interfaces to be annoying, but rather I believe that inheritance is more natural for this situation because a LinkedHashSet “is a” HashSet. A Vector “is a” ArrayList (although poorly named). HashSet could extend an abstract class called “Set”, and ArrayList could extend an abstract List.
There are a couple of things you can do with Interfaces that you can't do with Inheritance, and maybe that could burn you on a few occasions. But it's the nature of software development that you remember (often with scars) the two or three times you get burned bad by a missing feature, but you never remember the ten thousand times you were saved a step or a click or a mental cycle of debugging by not having that feature.
In the long run, I'd rather suffer the occasional deep wound than be slowed down by tons of paper cuts. Also, if you follow other good development practices (like having good Unit Test coverage and being nimble at refactoring) you can make it easy to recover from those deep wounds quickly.
"Any intelligent fool can make things bigger, more complex... It takes a touch of genius -- and a lot of courage -- to move in the opposite direction." -- Albert Einstein