Disagree.
* Lambdas shipped without playing nice with checked exceptions
* Streams shipped while failing to terminate in cases where they should. (Do they still? I try it out every few years and so far I've been able to produce an infinite-loop or stack/heap space problem when asking for the first number of a well-defined series.)
* CompletableFutures shipped with this gem (still current I believe):
public boolean cancel(boolean mayInterruptIfRunning) {
boolean cancelled = (result == null) &&
internalComplete(new AltResult(new CancellationException()));
postComplete();
return cancelled || isCancelled();
}
Spot the unused bool. They've also pivoted away from CompletableFutures towards green threads. Which also reportedly lock up under some thread-starving scenarios (but shipped anyway.)* Not to mention all the newer features that I haven't kept up with that enter "preview status 1", "preview status 2", etc.
As for that cancel method, why is it a problem if it doesn’t use a variable? Like, there are also no-op methods inside standard libraries, these are very different to how you write ordinary code.
Also, Futures and green threads are not something to pivot between, they are different abstractions. And virtual threads locking up is pretty overblown of an issue, and always happened under some stupid workload, where normal threads would have locked up just as well.
Does java ship bugs? Of course, there is zero chance of shipping such a large project without bugs. But I do think that they have a tendency to re-think and wait for new features, before committing to them, and they actually commit to such decisions, remarkably so.
> so far I've been able to produce an infinite-loop or stack/heap space problem
The linked issue was resolved 5 years ago
Anyway, it's a been a while, so, I downloaded the latest Adoptium - that's the one you don't get sued for using right?
./javac --version
javac 21.0.3
Full source code for pulling the first number out of a big stream: import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
Stream.iterate(0, a -> a + 1)
.flatMap(b -> Stream.iterate(b, c -> c + 1)
.flatMap(d -> Stream.iterate(d, e -> e + 1)))
.limit(1L)
.findFirst()
.get();
}
}
Run it: ./javac Main.java && time ./java Main
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.base/java.lang.Integer.valueOf(Integer.java:1073)
at Main.lambda$main$2(Main.java:7)
{...}
at java.base/java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:647)
at Main.main(Main.java:9)
real 0m9,911s
user 1m31,992s
sys 0m1,423sI could agree or disagree depending on how 'type erasure' is defined.
I'm firmly in the static-typing camp: Do the type-checking. Use the type information to generate good code, then throw away the types.
> And optionals.
What's your beef with Optionals? They didn't exactly go all-in on it. All the standard library stuff still returns nulls. I have one or two small beefs with Optionals, but my current peeve is IntelliJ warning me that I use them. (I KNOW I use them! I'm telling my callers which parameters they can include and which ones they can leave out.)
With optionals, I had hoped they would be a huge benefit to catching potential NPEs at compile time. But it was just lipstick. It was half assed. The reasoning was as you say - telling the caller about nulls - but IMO it adds complexity without benefit. They could have just defined a standard annotation. Or… made the compiler enforce them.
On the one hand, it is super hacky and requires ugly workarounds (passing Class<T> references around). At the same time, it also made creative abuses of the type system possible (e.g., heterogeneous lists). All while remaining type safe (albeit with runtime type checks).
It is also very noticeable how Java can iterate more quickly on its type system (only partially embedded in its VM) whereas C# generics are more or less set in stone (deeply embedded in its runtime).
This JEP seemed promising at some point, but in the end it was decided that it wasn't good enough to become a language feature. And that makes sense. It's likely that a future JEP will replace it. And even if it never happens, it's still not a problem.
Introducing poor language features just "because other languages pick a point" doesn't make sense.
> String.format works well enough for most easy use cases
It's a convenience thing.C# can also use `String.Format("My template: {0}", name)`, but `$"My template: {name}"` is much nicer, IMO.
The longer the string and the higher the number of tokens, the more it benefits. I find this particularly true with working with LLM prompts, for example.
With respect to mitigating injections, when dealing with user entered prompt fragments, I don't think there's any templating engine that would be able to address this because it's not a standard format like SQL or JavaScript. Typically, we run the input prompt through another prompt first to test for validity of the prompt in the context of the flow and reject the prompt fragment if it's not valid for the context.
Not something a template engine is going to solve.
That's not true at all. In the last years there were lots of features that were introduced, later disabled, enabled and then disabled again because they were causing smaller or bigger issues. There was a major work on metaspace tuning (guard allocations) that went in, stayed in for 2 or 3 versions then was removed. AVX features were enabled and disabled multiple time because companies where getting wrong results or crashes.
It's a fair game to remove something you as a developer don't consider ready. Just think how much garbage your team or company have shipped that wasn't ready, you had to patch under pressure or explain to users that it's not really working as described by marketing team and users should stick to old ways for a few more months.
There is also a proof that they can be shipped for a complex runtime without such level of pain: added back in .NET Core 3.0[0] and there were relatively few AVX-related bugs in the compiler since then and all of them were fixed in servicing (patch) releases upon discovery, without deprecation.
[0]: https://learn.microsoft.com/en-us/dotnet/api/system.runtime....
In either case, I don't see how that disproves the argument that support of hardware features like AVX in a runtime comparable to JVM can be done well.
(though I'm biased to say low-level features are a weak point of JVM and a strong point of .NET, you don't have to sacrifice high-level convenience in other areas to get that)
The frustration I was trying to express is more related to "perfect being the enemy of good". JEP-430 was introduced 3 years ago. By that point C# has already had string interpolation in place for 6 years and the C# solution achieves 90% of what JEP-430 set out to do. Java users could have benefited from 90% of that feature for 9 years by now.
It sounds a bit dramatic when we are talking about string interpolation, I realize that.
Did they think that generics were perfect? I doubt it. Its a community that's been burned by its past pragmatism. I think its wise to be more cautious in response.
The JVM is a masterwork of engineering, and I mean that without any sarcasm. Its fast, it has a pretty sophisticated GC, it has a lot of great well-tested libraries built in, I want to use it for pretty much everything, but Java still kind of feels like it's stuck in ~2008. So many languages have added quality-of-life improvements literal decades ago and Java is just barely getting them. I mean, for goodness sakes, Java didn't even get multiline strings until Java 15!
Now, before someone posts this, yes I'm obviously aware of Kotlin and the dozens of other JVM languages out there, I use Clojure all the time, but I would still prefer that the core language for the platform were caught up with the rest of the world.
No scribbling all over the OS, no system global packages, no shell games with bundler / virtualenv, and not much need for Docker.
(I really like rails, but the deploy story is nowhere near as good as java.)