Java is a very primitive language. For the vast majority of its life, it's basically been C + basic classes + garbage collection.
As a result, it's very verbose, which is totally fine for a low-level language. But, when building large, high-level, business apps, it's just a weird fit. I think that's why we see all of these annotation-heavy band-aids on top of Java (Lombok, Spring, JPA, etc)- it's because Java is actually not the right tool for the job, but instead of migrating (or inventing) a better tool, we just sunken-cost-fallacy ourselves to death.
It's not very terse (like Ruby maybe), but modern Java is terse enough.
To keep a language small is a good thing: less to remember, easier to join the team. Go, Elm, Reason/ReScript, LISPs all go that route.
Java misses some things badly. Like being able to have a reference to a method (Jodd has a library fix for this). Or like sum types and pattern matching.
But I'm more bitten by features that Java has than what it has not. Overuse of Exceptions (instead of sum types) and Annotations are my biggest pains.
You see a lot of Java's shortcomings properly being addressed in Kotlin. Like the getter/setter story. And "standards" like Bean and XML config have given Java a bad rep.
I used to think that, too. I probably wont't convince you otherwise, and it really doesn't matter how you or I categorize the language, but I think a solid argument can be made that Java's abstraction power is almost zero, especially if you consider versions older than two or three years (before records, switch expressions, sealed classes, etc). I also think that the ability to differentiate between boxed and unboxed primitives, no concept of immutability/const, primitive synchronization tools like mutexes and raw threads, etc, make a compelling case that Java is not well-suited for thinking at a high level of abstraction.
Think about how much code it required in Java to create a value type with four fields before records. You needed to list all four fields and their types in the class body, then you need to list all four fields and their types in the constructor signature, then you need to write `this.foo = foo` four times in the constructor body. Then, depending on your conventions and preferences on mutability, etc, you'll need to write getters and/or setters for the four fields. Then you need to write a custom `equals()` implementation. Then you need to write a custom `hashCode()` implementation. Then you need to write a custom `toString()` implementation.
I hope you don't have to update that class, either, because forgetting to change your `equals`, `hashCode`, and `toString` will cause bugs.
There's basically no universe where you can convince me that this shouldn't be considered low-level programming.
> To keep a language small is a good thing: less to remember, easier to join the team. Go, Elm, Reason/ReScript, LISPs all go that route.
I agree! Small/simple is great. But look at how expressive Reason/ReScript/OCaml is/are compared to Java. Same with LISP. They aren't huge languages with endless features being added on all the time, but they allow for much more high-level programming than Java, IMO.
> Java misses some things badly. Like being able to have a reference to a method (Jodd has a library fix for this). Or like sum types and pattern matching.
To be fair, though, this is not what Java was designed for. Java was initially an object-oriented language. Sum types and pattern matching are not OO. Object-orientation was supposed to be about black-boxes sending "messages" to each other while maintaining their own internal state and invariants. In "true" OOP, you wouldn't have a sum type, because you'd have an object that would exhibit different behavior depending on the implementation.
Granted, we're moving away from hardcore OOP as an industry (thank goodness). But, I'd argue that the "problem" isn't Java's lack of sum types and pattern matching, but rather that we're trying to make Java into something it isn't. We should just use a different tool. I'm not in my garage trying to attach a weight to the end of my screwdriver to make it better at driving nails- I'm going to my toolbox to grab a hammer, instead.
OTOH lets consider Rust. It is in my book a low-level lang, close to the metal (hence Rust?). It has a muuuuuuch better feature set compared to Java (IMHO). But it is geared at low-level, so no VM and certainly no GC out-of-the-box... In your def Rust'd be a high level lang: which is cool. I like your def :) But I still def'd high level slightly different: more in terms of the ability program close to the machine, or more in abstractions.
> Sum types and pattern matching are not OO.
Traditionally not often found in OO, but otherwise verrry much compatible with OO.
> In "true" OOP, you wouldn't have a sum type, because you'd have an object that would exhibit different behavior depending on the implementation.
I think this is more about tradition than "trueness". I cannot return an Either<Error, Result> from Java. That sucks. Many have used Exceptions to fix it, but that suck even more. I'd say OO is compatible with sum types.
> But, I'd argue that the "problem" isn't Java's lack of sum types and pattern matching, but rather that we're trying to make Java into something it isn't.
This always happens. And some langs are better suited for that than others. I work on a Java codebase currently and welcome those features, and actively consider moving the whole show over to Kotlin. Kotlin to me is like a typed Ruby. And in Ruby many libs are in C (in Kotlin then many libs'd be in Java).
I think OO and FP bite eachother. You cannot have both. See Scala. It becomes way too big as a language, and lack idiomatic ways of doing things. But one can have a lot of FP in an otherwise OO lang (see Kotlin for instance).
So how can I pass a method to another method? (without using lambdas)
> Pattern matching also was recently released.
I know, great improvement (if it comes together with proper sum types). One less reaosn to switch to Kotlin.