4 ms·
I agree. Java has improved as a language quite a bit in recent years. And I don't even mean that in a "Java has followed the cargo cult of what's fashionable in
by ragnese 4y ago
I agree. Java has improved as a language quite a bit in recent years. And I don't even mean that in a "Java has followed the cargo cult of what's fashionable in programming languages"-sense. I mean that things like Records to define value types have "always" been desired in Java- at least in the 10 years that I've worked with people on Java code. If Java didn't automatically make everything equatable/hashable, it would be a different story.
Anyway, Java still has baggage that I doubt will go away any time soon. My biggest two complaints with Java are that null exists outside of the type system and that they mix runtime type reflection and non-reified generics. The latter features being actually incompatible with each other makes working with Java libraries/frameworks a minefield.
But, aside from those issues, the OP is totally right that so much of Java's bad reputation comes purely from library design and culture. Even when I preferred OOP architectures, I hated the fact that so many Java libraries and frameworks required a bunch of annotations for everything, even when it really wasn't necessary. I get that Java code can be verbose, but if you don't like it, use something else- don't just throw away compile time safety and information for a janky runtime-checked "DSL" where a bunch of the features don't even actually work together (I'm mostly looking at you, JacksonXML, but also Spring, et al).
Everyone praises the Java ecosystem as some kind of selling point, but I find that while the ecosystem is indeed broad, the actual quality and productivity of many of the popular libraries and frameworks is fairly low. And by "quality", I don't necessarily mean number of open bug reports, I mostly mean that the APIs are really difficult to understand and use correctly. In some sense the devs are "off the hook" when my app doesn't work correctly because "this is documented behavior", but that makes me think I should just start writing documentation for my apps' bugs and seeing what my boss thinks... ;)
- titzer 4y agoNot sure what you mean by null being "outside the type system"--it's just the bottom type, assignable to all references. I loved Java circa 2002-2005 and was deeply interested in JVMs. However I wanted to develop the JVM itself, and it turns out that Java is bad systems language. So I really want a systems language because VMs are the itch I have. What's weird to me are the choices that Java has made in its evolution. In Java 5, the VM and library powers-that-be basically killed reified generics, forcing type erasure. That was a huge language design mistake precipitated by not wanting to upset the apple cart of libraries and the .class format. But in versions 6 and 7 they updated the class format anyway, to add stackmaps and invokedynamic! I never understood why such a key language feature didn't get the requisite VM support it needs to be done right, but completely marginal features did get VM support. Poor choices IMHO. Java is just a big ball of semi-typed mud at this point. There are no layers and hardly any separation between what features are in the VM and what features are in the class libraries that are wormholes into the VM.
- BlackFly 4y agoI would say it explicitly isn't the bottom type since it doesn't satisfy the Liskov substitution principle for any actual type due to null pointer exceptions unless you unreasonably axiomatize null checks into every single property any type may have. Basically, you need to think of Java references as the tagged union of Void + the true type. But since nobody wants to discriminate and pattern match on every reference, most java code tries to either follow a convention of non-nullability + Optional, additional type annotations with static checks beside the compiler, or "trust me, I've done this a million times". Lack of reified generics used to bother me. Than I started passing parameterized interfaces next to instances of that type parameter to access polymorphic "static" methods of the type. This allows for up stack dependency injection into the body of the method where you would want the reified generic type without being forced to extend the parameters of the method for every use case: that is, you get a much more flexible design this way.
- titzer 4y ago> it doesn't satisfy the Liskov substitution principle I haven't seen that principle applied so stringently before. Under a strict interpretation it wouldn't be allowed to override methods with any implementation that didn't have observably equivalent behavior. > I started passing parameterized interfaces next to instances of that type parameter This is just manual reification, and it's only skin deep. You still can't do List<Foo>[] because Java's builtin array types can't be polymorphic or extended with fields to store classes representing the reification.
- ragnese 4y agoIt's outside of the type system for two-ish reasons. First, because of the point the sibling comment made: while you can assign the singleton value `null` to any reference type's binding (but not primitives, so it's not even the bottom value for all types), it's not actually a value of that type, bottom or not. E.g, I can assign `null` to a `String` binding, or pass it as a `String` parameter, but I can't actually get it to behave as a `String`, so any call to a `String` method results in a runtime error. That leads to the second reason I say it's "outside the type system": you can't explicitly write down the `null` type in the language. The reason you get a runtime error when we pretend like the `null` value is a `String` is because it's not a `String`- rather, the binding actually has the type `String | null`, but the compiler allows (forces us) to pretend that the binding is a `String`. Since we can't describe nullability in the written Java language (express it in the type system's language), but it does exist at runtime, I say it's outside the type system.