Sure, but instead of talking only to that Java specificity (inheritance), Let me rather rather talk ito the underlying needs driving all of this; I.e. Our need for parametricity and inclusion in code, (also called as parametric and subtype polymorphism).
To summarise:
- Parametricity; is used to avoid code duplication for equivalent processes, for example: the ability to write a single (generic) function that works the same across different types (Sort, Filter, ...)u
- Inclusion where we need polymorphism for interchangeability, for example: the ability to write a function can take an object that is ostensively interchangeable for another of "equal" type (Vehicles: Car, Van, ...)
Ok with that said, let me talk around the problems with it.
Aside from the obvious dependency issues, type inference with sub typing is rather awful when you stress it, and has led to many of the stranger Java design defects. Ultimately the purpose of a static typing system is rule out the bad stuff (crashes) without ruling out too much of the good stuff.
Once you go down the road of sub typing everything, you end up with a number of situations where you'll be forced to pick between sacrificing static type safety versus wrapping a lot of this with ad hoc coercions that serve no purpose but to help inheritance play nicely with the Java static type engine.
You simply need to define an interface like Functor i.e. the ability for a container object to mapped over with a parameter provided transformative function: e.g. In Java 8 this ability has been incorporated in Streams.
Basically a functor is just an Interface stipulating that each object that implements it, have a higher order function called Map; and yes that sounds simple enough. But practically it's a pain in the arse to implement across multiple Java object types; because the notion of parametricity is completely lost when inclusion(subtype) polymorphism is involved. You only need to look at the messiness of Java 8's stream implementation to pick up on this, or even simply try to make Java's ArrayList comply with a new interface (e.g. Applicative Functor)
Shall we count the number of *new* Java List types of github, or more specifically those that were forced to replicate most of the List code to add some *missing* feature?
In Java, many of the scenarios where inclusion has been the first grab bag approach can be replaced with interface methods, which has the added advantage of being also parametric in approach, plus adding new methods to the interface also means that all implementing types receive this behavior similar to inclusion(sub typing). It's unfortunately limited when compared to Haskell, Rust, Swift, ... and being Java with its historic roots firmly planted in OOP land it's certainly not perfect by any measure, but situations are still fairly rare when I'm forced to look at inclusion for my own types. Granted I had to rewrap many of the default Java types to work around many of the parametricity constraints.
FYI newer languages like Haskell, Rust, ... don't even support inclusion polymorphism; simply the same flexibility is achieved using a different approach, with some similarity to interface methods. Swift on the other hand was forced to support subtyping because of its inherent compatibility with Apple's Objective-C frameworks, but at the same time they wanted to tackle it in a more flexible way like Haskell & Rust -- which they've called Protocols, and their new catch phrase Protocol Oriented Programming (POP). Mixing these two approaches has been very problematic; simply said its not easy to do both of these and do them well. Ultimately Java needs to decide just how far it wants to go down this road, because inevitably they'll hit a point where backward compatibility has to be sacrificed to stay relevant.
Anyway pm if you need any code examples of this.