So, you’ve been working with Java since the very beginning? Remember the days when it was called “Oak”, when OO was still a hot topic, when C++ folks thought that Java had no chance, when Applets were still a thing?
I bet that you didn’t know at least half of the following things. Let’s start this week with some great surprises about the inner workings of Java.
1. There is no such thing as a checked exception
That’s right! The JVM doesn’t know any such thing, only the Java language does.
Do you want proof that the JVM doesn’t know such a thing? Try the following code:
Not only does this compile, this also actually throws the
, you don’t even need Lombok’s
2. You can have method overloads differing only in return types
That doesn’t compile, right?
Right. The Java language doesn’t allow for two methods to be “override-equivalent” within the same class, regardless of their potentially differing
throws clauses or
Note that there may be more than one matching method in a class because while the Java language forbids a class to declare multiple methods with the same signature but different return types, the Java virtual machine does not. This increased flexibility in the virtual machine can be used to implement various language features. For example, covariant returns can be implemented with bridge methods; the bridge method and the method being overridden would have the same signature but different return types.
Wow, yes that makes sense. In fact, that’s pretty much what happens when you write the following:
Check out the generated byte code in
T is really just
Object in byte code. That’s well understood.
The synthetic bridge method is actually generated by the compiler because the return type of the
Parent.x() signature may be expected to
Object at certain call sites. Adding generics without such bridge methods would not have been possible in a binary compatible way. So, changing the JVM to allow for this feature was the lesser pain (which also allows covariant overriding as a side-effect…) Clever, huh?
3. All of these are two-dimensional arrays!
Yes, it’s true. Even if your mental parser might not immediately understand the return type of the above methods, they are all the same! Similar to the following piece of code:
Type annotations. A device whose mystery is only exceeded by its power
Or in other words:
When I do that one last commit just before my 4 week vacation
I let the actual exercise of finding a use-case for any of the above to you.
4. You don’t get the conditional expression
So, you thought you knew it all when it comes to using the conditional expression? Let me tell you, you didn’t. Most of you will think that the below two snippets are equivalent:
… the same as this?
Nope. Let’s run a quick test
This programme will print:
Yep! The conditional operator will implement numeric type promotion, if“needed”, with a very very very strong set of quotation marks on that“needed”. Because, would you expect this programme to throw a
5. You also don’t get the compound assignment operator
Quirky enough? Let’s consider the following two pieces of code:
Intuitively, they should be equivalent, right? But guess what. They aren’t! The JLS specifies:
A compound assignment expression of the form E1 op= E2 is equivalent to E1 = (T)((E1) op (E2)), where T is the type of E1, except that E1 is evaluated only once.
A good example of this casting is using *= or /=
byte b = 10;<br />b *= 5.7;<br />System.out.println(b); // prints 57<br />
byte b = 100;<br />b /= 2.5;<br />System.out.println(b); // prints 40<br />
char ch = '0';<br />ch *= 1.1;<br />System.out.println(ch); // prints '4'<br />
char ch = 'A';<br />ch *= 1.5;<br />System.out.println(ch); // prints 'a'<br />
Now, how incredibly useful is that? I’m going to cast/multiply chars right there in my application. Because, you know…
6. Random integers
Now, this is more of a puzzler. Don’t read the solution yet. See if you can find this one out yourself. When I run the following programme:
… then “sometimes”, I get the following output:
92<br />221<br />45<br />48<br />236<br />183<br />39<br />193<br />33<br />84<br />
How is that even possible??
. spoiler… solution ahead…
When I do that one last commit just before my 4 week vacation
This is one of my favourite. Java has GOTO! Type it…
This will result in:
Test.java:44: error: <identifier> expected<br /> int goto = 1;<br /> ^<br />
But that’s not the exciting part. The exciting part is that you can actually implement goto with
continue and labelled blocks:
2 iload_1 [check]<br />3 ifeq 6 // Jumping forward<br />6 ..<br />
2 iload_1 [check]<br /> 3 ifeq 9<br /> 6 goto 2 // Jumping backward<br /> 9 ..<br />
8. Java has type aliases
In other languages (e.g. Ceylon
), we can define type aliases very easily:
People type constructed in such a way can then be used interchangably with
In Java, we can’t define type aliases at a top level. But we can do so for the scope of a class, or a method. Let’s consider that we’re unhappy with the namings of
Long etc, we want shorter names:
In the above programme,
Integer is “aliased” to
I for the scope of the
Long is “aliased” to
L for the scope of the
x() method. We can then call the above method like this:
This technique is of course not to be taken seriously. In this case,
Long are both final types, which means that the types
L areeffectively aliases (almost. assignment-compatibility only goes one way). If we had used non-final types (e.g.
Object), then we’d be really using ordinary generics.
Enough of these silly tricks. Now for something truly remarkable!
9. Some type relationships are undecidable!
OK, this will now get really funky, so take a cup of coffee and concentrate. Consider the following two types:
Now, what do the types
D even mean?
They are somewhat recursive, in a similar (yet subtly different) way that
is recursive. Consider:
With the above specification, an actual
enum implementation is just mere syntactic sugar:
With this in mind, let’s get back to our two types. Does the following compile?
Hard question, and Ross Tate
has an answer to it. The question is in fact undecidable:
Is C a subtype of Type<? super C>?
Step 0) C <?: Type<? super C><br />Step 1) Type<Type<? super C>> <?: Type (inheritance)<br />Step 2) C (checking wildcard ? super C)<br />Step . . . (cycle forever)<br />
Is D a subtype of Type<? super D<Byte>>?
Step 0) D<Byte> <?: Type<? super C<Byte>><br />Step 1) Type<Type<? super D<D<Byte>>>> <?: Type<? super D<Byte>><br />Step 2) D<Byte> <?: Type<? super D<D<Byte>>><br />Step 3) List<List<? super C<C>>> <?: List<? super C<C>><br />Step 4) D<D<Byte>> <?: Type<? super D<D<Byte>>><br />Step . . . (expand forever)<br />
Let this sink in…
Some type relationships in Java are undecidable!
10. Type intersections
Java has a very peculiar feature called type intersections. You can declare a (generic) type that is in fact the intersection of two types. For instance:
The generic type parameter
T that you’re binding to instances of the class
Test must implement both
Cloneable. For instance,
String is not a possible bound, but
This feature has seen reuse in Java 8, where you can now cast types to ad-hoc type intersections. How is this useful? Almost not at all, but if you want to coerce a lambda expression into such a type, there’s no other way. Let’s assume you have this crazy type constraint on your method:
You want a
Runnable that is also
Serializable just in case you’d like to execute it somewhere else and send it over the wire. Lambdas and serialisation are a bit of a quirk.
You can serialize a lambda expression if its target type and its captured arguments are serializable
But even if that’s true, they do not automatically implement the
Serializable marker interface. To coerce them to that type, you must cast. But when you cast only to
… then the lambda will no longer be Runnable.
Cast it to both types:
I usually say this only about SQL, but it’s about time to conclude an article with the following:
Java is a device whose mystery is only exceeded by its power