Having said that, there are some things OO is bad at, eg concurrency.
Having said that, there are some things OO is bad at, eg concurrency.
OO prioritizes encapsulation ahead of immutability as a way of making the problem of a mutable struct tractable. Most OO programs are graphs (picture as a complex web) of mutable objects holding references to one another, synchronously passing control from one object's method to the next (picture as a spider travelling around the web, making modifications). Concurrency means there is more than one logical point of control flowing around the graph, making modifications. To ensure modifications are consistent, one must now fight against the interconnected nature of this graph, and make certain areas mutually exclusive; that means you need to set up gates on all the edges leading into those areas (often called semaphores or mutexes).
But OO gives you precious little help in setting aside these areas, and guaranteeing internal consistency in the face of multiple mutating points of control.
Programs in a functional style are different, with their heavy emphasis on immutability. In this case, the web, as it were, is fixed and cannot be changed; instead, if there is to be a modification, a new copy is created (possibly only of the local area of change, and the unchanged portion included via references). Since there is no way to change data, there can be as many points of control performing operations as desired. (Actually, one of the models of computation used for functional programs is that of graph reduction, which pictures the program as a web of expressions rather than structures; and that big expression is iteratively made smaller by calculating different parts of it. In principle, the more points of control you have doing these calculations, or reductions (in the same way as '1 + 2' can be reduced to '3'), the better.)
Programs written in an agent / actor style are also different. Here, there is no single point of control flowing from one node in the graph (web) to the next; instead, each node has its own little point of control, and it reacts to messages coming in from each edge, and sends out messages along edges in response. This makes the program locally single-threaded, but globally concurrent.
But there are moderating factors. Procedural programs that don't enforce strict modularity conventions tend to be bound in size by their complexity, a complexity that works against the level of understanding necessary to make things both concurrent and correct. Meanwhile, procedural programs that do enforce strict modularity will probably be using conventions that emulate a different paradigm, and will inherit that paradigms' costs and benefits WRT concurrency.
But Java naturally tends towards mutability. And you will have to work harder to make it immutable. Java also does not give you as many tools for this kind of style as functional languages usually give you.
First class functions, a rich type system and a library full of immutable data types come to my mind.
Worse, OOP tends to hide the mutable state away internally in objects (or in objects stored inside objects etc) - OOP's data hiding and abstraction support can go against you here. Sure, good programming practices and discipline (eg, const correctness in C++) helps, but the languages definitely encourage mutable state.
I wonder how using methods-as-messages and turning OOP into a message passing system not dissimilar to the actor model would work in practice (both from a usability/syntax and concurrency view).