Java has always been something of a half ‘n half: an object language written in a procedural language with very similar syntax to said procedural language.
As a result, there’s a temptation to transfer structures used in procedural programming to Java without accounting for the object nature of (most) of the language’s implementation. This has gotten somewhat better as fewer Java programmers are coming from a C background, although the backgrounds they are coming from are often not significantly better for learning object code. A couple of years ago I had a phone call from a recruiter who specializes in Smalltalk programmers about a job writing Java in a city I left five years ago (and the recruiter was aware I no longer lived in the country). The reason for the call and the subsequent attempt to get me to move back was that the manager of the company he was recruiting for didn’t think that people who “grew up with Java” knew anything about how to write object code, and would only hire current or former Smalltalk programmers, despite the entire project being in Java.
Functional interfaces present another challenge in Java programming, or more specifically, for reading Java programs and understanding what they do and how. One of the biggest contradictions between object and (properly) functional code is that functional code either assumes or prescribes (depending on the given language) immutability of data structures, which in an object language can generally be assumed to be some sort of object (yes, even primitives are really primitive objects in most object languages that have them, Java included). However, in object language program state is controlled precisely by mutating objects, and some of the more powerful object languages provide means of mutating the object structure as well as the data.
Granted, most people who use structures such as lambdas in Java don’t stick to the ideal of immutability particularly well, if at all, but I’ve seen in code reviews and various articles the prescription that they should (although nobody really appears to know how to) use only immutable objects in lambdas. The lack of a true closure in Java is part of the problem (closures are basically anonymous inner classes in Java, not what’s really needed in this instance). That lambdas are themselves generically named objects with generically named methods hardly helps either, especially in debugging complex lambdas. These implementation issues are not the main issue I’m discussing though, the issue is the proliferation of objects to maintain state when it should be maintained in the appropriate object (the one with the code that deals with those aspects of program state). State has to be maintained somewhere, and lambdas are certainly useful for injecting code into a different JVM when the data resides there (such as code colocation in Apache Ignite or Geode), but if the application state or that aspect of the application state is maintained internally (and if not, it’s not an application) objects that maintain said state have to exist, given it’s an object language for the most part.
The result, generally, pushes Java code into more and more procedural looking code, with data objects that may as well just be structs and code objects that are simply arbitrary groupings of functions. Since there’s no obvious means of decomposing that kind of application, methods and classes tend to get longer and more convoluted. Essentially, they’re long scripts dumped into the only language structure (a method) capable of holding them. That they’re a problem can be seen in the fact that the easiest way to find such methods in an existing Java code base is to look for methods that catch and squash exceptions, especially runtime exceptions. These “megamoths” are too complex (given how many external methods they call as well as their own length and complexity) to properly debug and the only way to make them “sorta” work is to hide the bugs by squashing checked and unchecked exceptions. In a number of cases I’ve found such methods that are over 1200 lines long (my Smalltalk dev env gives a warning if a method is longer than 12 lines), and in a couple of cases, after refactoring the hell out of the method to try to understand what it’s doing, I’ve found that since it avoids making any changes to passed in objects, it doesn’t accomplish anything at all other than slowing the program and logging huge numbers of squashed exceptions.
I’ve seen functions of this type numerous times in C and C++ code (and most C++ code is so procedurally oriented that it wouldn’t take much refactoring to compile it with a straight C compiler) but the contagion has definitely increased in Java as developers try more and more to do more and more within lambdas while attempting to maintain the immutability of the objects they’re working with. I think this is a completely back-asswards way of approaching development in a language that remains, by and large, an object language.
If functional programming were so advantageous, honestly, we’d all be writing in Haskell (a beautiful language in itself). We’re not, of course, and a big part of the reason is that while Haskell code tends to be clear, concise and relatively bug-free (it either has no bugs or it doesn’t work at all) it can be extremely difficult to think of how to implement relatively simple algorithms in it. I’m sure competent physicists don’t have this issue with Haskell, but the average physics program looks substantially different from the average business application, no matter what language it’s written in.
This is just a suggestion, but next you’re trying to write something new try keeping your methods under 25 lines (disincluding whitespace) and keep lambdas under 15 lines. If a lambda is being used for something non-trivial such as code colocation there’s no point in doing it if the code is bigger than the data in any case. You might find your code much easier to read and debug, and also find there’s little reason in an object language to separate code and data if the objects and methods are properly decomposed, given part of the reason for their invention was precisely to keep code and related data together.
Of course, there are instances where an atomic, idempotent function is precisely what you need, and functional interfaces work especially well in such instances. However, they’re not as commonly useful as the pushers of functional programming appear to believe. Wherever state is not maintained in the interest of simplicity, the program inevitably has added complexity elsewhere.
As a manager of mine who had been programming himself for 13 years at that company said well “an application is by definition a (expletive deleted) state-machine, if you’re not maintaining state, it’s a web site”.