The ultimate problem with Java (and OOP too, even in the Kay's sense) is that its philosophy represents a very paternalistic approach to software development. The idea is here are these things called objects, you can't look into them, and you only have to use them the way they are designed; under no circumstances you can break them down into internal components and remix those freely.
In Java, this overall approach manifests in myriad of different ways:
- Functions and data are tied together. It's difficult to create new functions (methods) that operate on existing data, you have to inherit, and even that avenue can be closed, even indirectly because you find that you cannot actually get to some internals.
- Interfaces are tied to objects. You cannot explain to the compiler that a given class actually supports a certain other interface in the way YOU define it.
- Everything is a reference, no value types. No need to worry about the intricacies of cache management, we will handle that for you.
- No need to worry about CPU optimizations or memory management either - we will do that for you, without an escape hatch.
- You cannot change the existing operators to introduce more convenient syntax, because I said so. Any kind of "macro" - code generators, compile-time checks, conditional compilation, DSLs - forbidden.
- The class and package hierarchy determines the access to its components. No way to override this from the outside, no way to expose things that are hidden. Worse, this is implicit, for example, class method accesses an object attribute, where taking a parameter would be more convenient; this hurts reusability pretty badly.
Some of these shortcoming are only now being addressed, because people actually need to do these things anyway, in certain situations, which leads to proliferation of patterns and magical code generators.