I dislike that people conflate simple languages with easy to read code.
Low level verbose abstractions make code harder to read - going through the layers of IFactoryRepositoryLocatorBullshit because the language abstractions suck doesn't make it easier to read code.
It makes the code more accessible, in the sense that you can probably find some 20$/hour devs to read through that and spend a day solving something that should take a couple of hours most.
Good high level code express the problem domain without boilerplate and let's you focus at problem at hand - Java is terrible at that.
What specifically is Java terrible at? I find Java to be as expressive as most mainstream languages. Certainly no less expressive than, say, Python or JavaScript.
Java can't even have a free standing function without 6 lines of boilerplate minimum and 2 levels of indentation to start.
Nominal static typing with weak generics and no almost no compile time programming support - you end up mucking with reflection or code generation if you want to do anything metaprogramming related or non trivial higher order functions - JavaScript and Python just deal with it at runtime, and if you need to type it out - you can still generate types that convey to the user what the result will be (the underlying code doesn't have to be typed)
Function<String, String> fn = parameter -> parameter + " from lambda";
Using annotations and cglib, you can do all the meta-programming you want.
Annotations and CGLib -> reflection nonsense and boilerplate - if you tried that and compare it to something like a dynamic language with open types (eg. JS/TS) you are just being dishonest if you're saying the two are anywhere close to comparable.
I remember doing a node ORM extension where I just injected standard revision management primitives into the entity as a part of repository. That would take an insane ammount of work in Java in comparison to get the same API functionality (namely it was mostly transaparent to the consumer)
Just like anything else. Put it in a package and just static import it from where-ever you need it.
Umm, cglib https://github.com/cglib/cglib is not reflection - its bytecode enhancement. You can inject, create types dynamically, modify types dynamically - all of it is possible. What you just stated is quite straightforward in Java actually and can be fully transparent to the consumer. It's by no means an "insane amount of work".
Now, if you bring up hot language features - sum types, etc - I agree that Java falls behind. But the stuff you offered as criticisms, Java actually excels.
It's exceedingly rare for this to be untrue of Python — it's technically possible to hack builtins but there's intense community pressure against doing that.
> - the syntax, although not the most succinct is fairly easily readable and maintainable by other developers.
This is sort of true but it misses the aspect which makes Java hard to maintain: the language being less capable means that you end up with more boilerplate syntax which has to be understood when reading it and the patterns that produces tend to be the long-term maintenance issue instead.
The boilerplate syntax you talk about comes from like java 1.4.
Modern Java is quite expressive with streams and lambdas. If one does employ the Java Beans convention than yeah, he/she has to autogenerate a few getters setters, but those don’t occlude the actual logic.
And frankly, empirically, Java is anything but hard to maintain. You would be hard pressed to find many languages that continue to manage at the scale Java apps routinely handle.
Yeah, it’s (typical business monstrosity) hard to understand, probably has many thousands hours of technical debt, but it just works, and more things can be added to it (and occasionally with much more work, removed as well).
I was thinking about things I've recently seen in new Java 15 code, so no. There are lots of old APIs in the Java language and popular libraries, and while things like streams are quite powerful, they're far from universal. I still see developers cranking out code which has to do what is built-in in other languages.
As a simple example, not having multiple return means that you end up with worse APIs in some cases or small classes which exist to wrap a couple of things — not necessarily terrible but it means that you've just taken on maintenance work for something which would be trivial in other languages. That extra code isn't exactly a huge lift but when you add things like that up regularly it's not hard to understand why all of the good Java developers I know are keenly following things like Kotlin which give more expressiveness without giving up all of their familiar tools.
But I do agree that there is space for improvements here.
For example: imports inside functions instead of at the top of the file (because the import does work, but the configuration hasn't been set up yet).
An example of this is an internal package at my workplace which makes a external service request at import time.
But even for Python (which syntactically speaking is probably the easiest to read of all the list), if your team scale, you just scale the tooling with it.
The Python ecosystem is very rich in tooling for enforcing best practices. Poetry will manage dependencies cleanly. pylint will let you check that builtins are not overriden. Mypy will check for types.
So of course, it's not as good as Java, but again, the post is about compromises the language make, which are not the proper ones for me, personally.
- you can integrate code from hundreds of developers fairly safely (knowing that no one has changed the default behaviour fo builtins and different dependencies won't clash).
- the syntax, although not the most succinct is fairly easily readable and maintainable by other developers.