4 ms·
In my opinion Java was basically a mistake. It's not that the design choices were dumb with the information we had at the time by smart people, but with the ben
by thomashabets2 2y ago
In my opinion Java was basically a mistake. It's not that the design choices were dumb with the information we had at the time by smart people, but with the benefit of hindsight they were almost all duds.
https://blog.habets.se/2022/08/Java-a-fractal-of-bad-experiments.html https://blog.habets.se/2022/08/Java-a-fractal-of-bad-experim...
Java has been an extremely successful technical failure. With Oracle, it's also gotten a tonne of bureaucratic/legal/CYA cost.
- kelseyfrog 2y agoWorse is better[1]. 1. https://cs.stanford.edu/people/eroberts/courses/cs181/projects/2010-11/WorseIsBetter/index.php/Worse-is-better.html https://cs.stanford.edu/people/eroberts/courses/cs181/projec...
- _gnad_ 2y ago[dead]
- invalidname 2y agoActually that post is very much a mistake. Everything in it is wrong. OOP is great for big applications which is where Java is still a leader. If you have statics all over then you have a problem. Heap was the right decision when Java was made. Newer JVMs are fantastic at allocating and releasing it in a way that beats C performance for some cases. There are edge cases where it can't do that which is why we're getting Valhalla which will solve that edge case. The file API you're referring to is out of date. NIO access has been around for ages. I agree that generics have a lot of issues, but Java has some of the cleanest error messages around. You dug up a single problem there which plagues pretty much any language that has a complex type system. The alternative is untyped languages that have their own problems. Bytecode is fantastic. You're confusing historic VM implementation (the Pascal VM) which were inspirational but unrelated. The value Java provides there is amazing, first modern JVMs provide a level of performance that rivals native code. But the biggest benefit isn't in performance: it's observability and extensibility. Thanks to the stability of bytecode developers have built debugging and observability tools for Java that are both performant and powerful. Having worked in this industry I can tell you that no other language comes close to that power. This isn't even a contest, it's Java then everything else. It also opened the gate for many other languages that run on the JVM and interoperate with Java fantastically well. Modern Java uses UTF-8 for Strings and for internal representation of Strings when applicable. I suggest reading a bit about modern GCs. Xms/Xmx are important but modern heap allocation and the things done by the newer GCs is at a completely different level. Misuse of checked exceptions in some cases is a pain point for me too. I'd also add that when Lambdas were added the designers didn't do the work required to solve the issue with checked exceptions there. Which is indeed a shame. Having said that, this is a wide problem in pretty much every language. Exceptions make a lot of sense in terms of performance/flow. I disagree about finally though. It's a powerful control flow tool that allows us to remove code duplication. No, it doesn't look great. But the alternatives (e.g. Go) look worse.
- thomashabets2 2y ago> Newer JVMs are fantastic at allocating and releasing it in a way that beats C performance for some cases. And yet the care and feeding (tuning) of the GC consumes countless millennia of human time per year. This, as my post says, is not even an inherent problem with GC, but with Java assuming an eventually sufficiently smart GC. But also yes, successive Java GC implementations have done huge heroics. They've gotten better and better. A million PhDs have been minted inventing amazing technology in this space. But we've been promised the ultimate compacting pauseless GC for decades, and yet I see outage after outage of huge systems root caused to "look, we found a brand new way that the GC can explode (or just thrash) on us". And (for all its many flaws), Go chose a different strategy, and avoided a class of problems. They learned from Java's mistake in this case. There's still GC issues (though Go is also getting better and better), but nowhere close. > Java has some of the cleanest error messages around One can make the case for which is better (well, least bad) of Java or C++ error messages, but neither can come close to Rust error message quality and cleanliness. And Rust does have a developed type system. But fair enough, let's call it opinion. Which is a disclaimer I have at the top of the post. Like I said I'm impressed that Java managed to make error messages worse than C++, but you can disagree. > Modern Java uses UTF-8 for Strings and for internal representation of Strings when applicable. Ok, that sounds like I'm not up to date (probably nowhere near). It's not an issue I've fought in years. But glad to hear they fixed that… "when applicable". I'll read up on that. Thanks. > finally Go is not my favorite language, but here I think it's better than Java. RAII beats defer, but at least defer makes the programmer add the cleanup at resource acquisition, not four pages further down, possibly behind a null pointer check in case the finally clause triggers before the resource acquisition. I respect your opinion about aesthetics, but I don't accept that my opinion on finally/defer/RAII is "wrong". I appreciate the feedback. Thanks.
- invalidname 2y ago> And yet the care and feeding (tuning) of the GC consumes countless millennia of human time per year. This is a feature not a bug. When I write C++ code I need to make a lot of decisions in terms of memory behavior that would deeply impact performance. I can benchmark locally but production is the part that matters and I'm mostly blind there. An observability solution might be able to detect problems if I used the default C allocator under the hood, but in that case my performance would also be terrible to begin with. Java lets me detect production performance issues and even pinpoint them to specific objects in a shipping application. This can translate to feedback to developers that could be very valuable. But it also allows me to tune GC behavior in production to get better performance with zero code changes. Furthermore, the object deletion cost is deferred so if your app works in performance peeks it can be very efficient assuming it has enough memory allocated to it. The downside is that Java will probably always need more RAM than non-GCd languages (the amount varies). That's a reasonable tradeoff for most since RAM is still cheaper than CPU/GPU. > But we've been promised the ultimate compacting pauseless GC for decades, and yet I see outage after outage of huge systems root caused to "look, we found a brand new way that the GC can explode (or just thrash) on us. There is no "one size fits all". Java has such GCs but right now the default JVM ships with several GCs since every workload has different requirements. This means you need to tune it which is a challenge. If you see bad performance and don't know what to do then I would blame your observability provider. Good ones actually provide recommendations on how to improve a thrashing JVM. > One can make the case for which is better (well, least bad) of Java or C++ error messages, but neither can come close to Rust error message quality and cleanliness. Rust is great. But it is a young language with less baggage. It has no binary compatibility to deal with and it doesn't offer many of the language features Java (or for that matter C++) have. Those are all good things for Rust, but I wouldn't build the type of apps I build in Java using Rust. > Like I said I'm impressed that Java managed to make error messages worse than C++, but you can disagree. I just spent 20 minutes the other day looking at 100 errors in std::vector which I didn't touch at all due to closing brackets appearing in the wrong place... C++ error messages are at a different level compared to Java. Maybe I'm too used to Java but I never spend time on these things and the IDE is fantastic at handling 98% of the stuff. > Go is not my favorite language, but here I think it's better than Java. RAII beats defer, but at least defer makes the programmer add the cleanup at resource acquisition, not four pages further down, possibly behind a null pointer check in case the finally clause triggers before the resource acquisition. Go was built for simple quick solutions at the system level. Typically in such cases you would need to handle the error as it happens. Java was built for libraries/frameworks/APIs. E.g. if Spring runs into an error it can't handle it since it doesn't know whether you want to show the error to the user, recover, log etc. Throw is the only reasonable option. This lets me build a nesting doll of libraries and dependencies that can defer decision making and even add a systemic solution that eventually handles that. E.g. in Java I can define a recovery policy (retry/circuit breakers etc.) with an annotation and the system would handle all of that for me. In a typical Java backend application I don't write catch at all. You let the exceptions propagate and then write error handlers at the top level to define behaviors for the system/user. This also makes tracking issues in production much easier as you get a clean stack trace that includes all the details you need to fix most issues.
- jcranmer 2y agoI can pinpoint several mistakes in that blog post: * Java has had escape analysis for decades. What it doesn't have is value objects. But there are plans for that in the works. * The rant against bytecode is... I mean, there's a reason why you've had projects like LLVM whose (original) goal was to bring something like JVM bytecode to native code. And it's not for portability reasons, it's for the ability to do things like install-time optimizations. Also note that, for example, iOS distribution also relies on the ability to deliver bytecode instead of native code for those kinds of late optimization opportunities. * The C++ standard library absolutely uses exceptions for all sorts of silly errors. std::vector::at, for example, throws an exception on a range error. The reason why this isn't obvious is that the more common methods just go for undefined behavior on errors instead of using exceptions, which is hardly a design to be emulated. * I'm confused as to why the author hates try/finally so much, given that it's morally equivalent to RAII and defer. And there's no mention of the try-with-resources, which brings an interface much like RAII to Java.
- thomashabets2 2y agoThanks for feedback. I wonder what you would classify as a "mistake", though. I don't want to say anything that's incorrect, and definitely invite corrections. > Java has had escape analysis for decades. What it doesn't have is value objects. But there are plans for that in the works. I'm not sure what you mean this contradicts. Let's say it lands. Which of my points does it invalidate? (also, you know, Java has had ~30 years to land this) Edit: ah, I see what you mean. Basically you're saying that because of escape analysis, Java can avoid stuffing literally all objects onto the heap for the GC to collect. Sure, that's one of the millions of heroics that Java has added to manage the mistake. The GC has also added more genius algorithms than maybe any other code in history. And yet, a very large percentage of all that work is just to work around a mistake in the language. And it'll never be fully mitigated. > The rant against bytecode is[…] Reasonable people can disagree on the values, but what's the mistake? > iOS distribution also relies on the ability to deliver bytecode instead of native code for those kinds of late optimization opportunities. So did (does?) android. I remember those "optimizing apps" loading screens on every update. This is actually exactly my point: None of this requires that the IR / bytecode is, itself, "executable". > The C++ standard library absolutely uses exceptions for all sorts of silly errors Nowhere near Java. It's a difference is philosophy, don't you agree? Comparing C++ and Java, you'd agree that Java doubled down on exceptions, right? And that's what I'm saying the mistake is. I'm not saying that std never throws errors. But that's also not the case in Rust or Go. But I'm sure you agree that panic in Rust or Go is worlds apart from exception use in Java? C++ (standard library API, at least) is much much closer to Rust and Go, than to Java. Like, no contest. > I'm confused as to why the author hates try/finally so much Not "try" so much as "finally". Compared to RAII or Go's "defer", it's a very convoluted and (in my experience from various codebases) very error prone way to hopefully remember to undo everything (and if a resource acquisition is added, to remember to release it way down there in different code). It's morally equivalent to defer, yes, but unlike RAII it's not automatic. Even compared to Go's defer and (not guaranteed to be called) finalizer, it's easier to get wrong. Defer is better than finally, but RAII is better than both. > try-with-resources, which brings an interface much like RAII to Java. True. I have no criticism of that pattern, which is why I didn't criticize it. :-)
- sureglymop 2y agoDidn't read the post but I highly disagree with your comment. I use mainly rust and I don't think it would be where it is today without being able to stand on the shoulders of giants like java. There are a lot of things that oop languages pioneered that are now in other languages. In rust specifically, there is a mixture of features taken from functional and oop languages that are a pleasure to use.
- thomashabets2 2y ago> Didn't read the post I think the post is pretty important context, in fact it's the main thing, so this "comment without reading" is a bit too reddit, and not enough hackernews. > I don't think it would be where it is today without being able to stand on the shoulders of giants like java. The present is a function of the past. Not sure what that's supposed to prove. I do think that Rust (and to a lesser extent Go) has learned from Java some things not to do. So I do think that the decisions that seemed like a good idea at the time, and proved to be mistakes, are the reasons we do not have those same mistakes in Rust and (to a lesser extent) Go. If they hadn't been mistakes, then Rust and Go would repeat them, wouldn't you say? > There are a lot of things that oop languages pioneered that are now in other languages. Yes, but most of the innovation in Java specifically have since been walked back by newer languages. That's my point. > In rust specifically, there is a mixture of features taken from functional and oop languages that are a pleasure to use. Indeed. But what did Java invent, that Rust took in? If we take C++, and move in the direction of 1) more OOP, 2) More heap usage, 3) More exceptions, 4) Worse error messages, 5) virtual machines, 6) GC, 7) pre-zoned memory, then that's Java. For literally all of those bullet points Rust went the opposite way. 1) less OOP, 2) structs are one whole heap object, not one per field, 3) less exceptions, 4) Way better error messages, 5) native code again, 6) not GC, 7) "regular" allocators. So I agree with you if you mean that by standing of the shoulders of Java you mean "let Java be a warning example of what not to do". If you mean something else, then I don't know what you're talking about. And since you say you replied without reading the post, probably neither do you. Edit: by the way by "more heap usage" I don't mean more bytes. I mean e.g. a struct with members is in Java many objects on the heap, but in C++, Rust, and Go, it's a single one.