I have started properly programming about 4 years ago. I started with python for the first few months of learning, but after a small period of indecisiveness I settled on learning Java. It has since then stayed as the language I know best and use most often.
I do generally like it as a language, but it definitely has its flaws. Sometimes I just stumble upon some unexpected behavior that I don't think makes sense. Enough times to make this post.
Sorry for the lack of formatting in code blocks. I use a hacked-together page to write these and have not decided to properly implement them yet.
Generics
Generics are probably the worst thing to exist in Java.
To write this part, I had to read Oracle's Java™ Tutorials to make sure my (old) assumptions were correct. They were not.
I'll take you on a small journey of me expecting OOP to work like OOP.
5 Months ago I modified an MPRIS library to allow to arbitrarily modify metadata without being locked to some specific values. If you don't know what that means, what's important is that I tried to accept this type as an argument:
Map<String, List<?>>
The intentions are clear here: I needed a map with strings as keys and lists of whatever as values. With Java being an object oriented language where you are expected to use hierarchy, you'd expect this to work right?
When I tried using the library, I only needed to use lists of strings as values as that is how FLAC files store metadata. So my type became:
Map<String, List<String>>
Lists of Strings were Lists of whatever, so I expected to not have problems here.
But this did not compile, since java.util.Map<java.lang.String,java.util.List<java.lang.String>> cannot be converted to java.util.Map<java.lang.String,java.util.List<?>>
.
Turns out that generics like to completely throw away all concepts of OOP unless you explicitly tell them not to.
In this instance, the problem was that List<String>
and List<?>
are not exactly the same type. Because I didn't specify ? extends List<?>
it assumed I only wanted exactly a List<?>
.
But that's not my only issue with generics. Generics in Java are a lie. They don't exist at runtime. All generic types, when compiled, become Object
. They're a fancy addition at compile time to specify your intentions but there is nothing stopping you from just ((List) list).add("hello")
with a List<Integer>
. That sounds like a fantasy scenario I made up to prove my point but all you need to replicate it is some wrong assumptions when decoding JSON and a couple of innocent-looking casts and you'll get a ClassCastException.
My last issue with generics is that they should be more accessible at runtime. I've read it's possible to access a generic class using reflection but that's hacky and I always try to avoid it when possible. We have Class
objects for each class already! We treat T
as a class by putting it in front of variable names, just let me write T.class
and make it behave like one! I don't want to have to specify my class twice when it'll always be the same (new Thing<String>(String.class)
).
Everything's a class
Edit: after publishing this post I was informed there actually is a good reason for the structure which sparked my complaint. I still find it to be a weird design decision but am not knowledgeable enough to complain in this scenario, so I take it back.
Not long ago I had to look into the code of a function in a Minecraft-related library. The class was a Vector3d, which is just 3 variables x, y, z. I expected it to be simple. I needed to check whether calling the .add(x, y, z)
method would modify the original vector or create a new one.
I use IDEA (as neovim sucks for java specifically) and ctrl+clicking on the function always worked in the past, so that's what I did. I ended up in an abstract class Vector3d.
Okay. Sure. Let's see what implements it. I ctrl click and all I find is a public interface VectorProvider with a method createVector3d(x, y, z)
.
Cool so I'll just go one more layer deeper and see what implements it. I ctrl click and all I find a public static VectorProvider provider()
method in a Vectors class. In the method I find this:
Iterator iterator = MathImplementationLoader.serviceLoader(VectorProvider.class).iterator();
if (!iterator.hasNext()) {
throw new RuntimeException("Could not initialize vector provider as no implementation was provided!");
}
Excuse me?? A dynamic implementation loader for three variables? Are you scared you'll get addition wrong and will have to swap it out??
This is silly. I'm sure these many layers of abstraction might make sense in a complex code base but we're talking about x, y and z. Three variables.
I think this structure is actually encouraged by the language. Everything you make is a class anyway so might as well dynamically load the three numbers.
If there's something I love about C is the ability to make a struct with n bytes, and use them all. If you need x, y, and z, you'll intentionally allocate the memory needed for x, y, and z.
I think java should support that. There's no reason to store an address to the heap where you allocate space for the three doubles. Store the doubles as if they were a primitive. It'll be fine.
Edit: yay!
No tuples
Working with other languages I've had the pleasure to work with tuples. They might be a bit of a slippery slope if you abuse them, but storing 2 values in 1 variable is really useful on a small scale. I've actually just ended up implementing Pair<T, U>
in multiple of my projects.
This isn't a proper talking point like the others, i just find these really handy.
Let me tell you what version I was made for
Java verisons are a mess. Sometimes you just need any JRE and everything is great, until you need to use the 10 year old program that only works with java 8 or suddenly need a JDK for one program. Then you end up installing an old version of Java you only intended to use once and it takes over your PATH and now the rest of your programs won't run because they needed a recent JDK.
If a java program wants to reliably work on all - or most - machines, it needs a bootstrap to download its own version of java. I don't think it should work like that. After all, a a bootstrap with that purpose will always do the same thing.
Similarly to how jar files specify their main class, they should also be able to specify which version of java they were designed for and which other versions they also work with. Then each JDK should be able to read that and judge if another JDK installed in the system may be more fit or - if none is - install a new one.
Published: , Last edit: