Being Java nerds, we are always interested in obscure details that may not be of direct use but teach us more about Java and the JVM. That's why I decided to publish this great article that Lukas Eder originally wrote on jooq.org.
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.
Today, everyone agrees that checked exceptions were a mistake. As Bruce Eckel said on his closing keynote at GeeCON, Prague, no other language after Java has engaged in using checked exceptions, and even Java 8 does no longer embrace them in the new Streams API (which can actually be a bit of a pain, when your lambdas use IO or JDBC).
Do you want proof that the JVM doesn’t know such a thing? Try the following code:
public class Test {
// No throws clause here
public static void main(String[] args) {
doThrow(new SQLException());
}
static void doThrow(Exception e) {
Test.<runtimeexception> doThrow0(e);
}
@SuppressWarnings("unchecked")
static <e extends Exception> void doThrow0(Exception e) throws E {
throw (E) e;
}
}
Not only does this compile, this also actually throws the SQLException
, you don’t even need Lombok’s @SneakyThrows
for that.
More details about the above can be found in this article here, or here, on Stack Overflow.
2. You can have method overloads differing only in return types
That doesn’t compile, right?
class Test {
Object x() { return "abc"; }
String x() { return "123"; }
}
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 return
types.
But wait a second. Check out the Javadoc of Class.getMethod(String, Class...)
. It reads:
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:
abstract class Parent<t> {
abstract T x();
}
class Child extends Parent<string> {
@Override
String x() { return "abc"; }
}
Check out the generated byte code in Child
:
// Method descriptor #15 ()Ljava/lang/String;
// Stack: 1, Locals: 1
java.lang.String x();
0 ldc </string><string "abc"> [16]
2 areturn
Line numbers:
[pc: 0, line: 7]
Local variable table:
[pc: 0, pc: 3] local: this index: 0 type: Child
// Method descriptor #18 ()Ljava/lang/Object;
// Stack: 1, Locals: 1
bridge synthetic java.lang.Object x();
0 aload_0 [this]
1 invokevirtual Child.x() : java.lang.String [19]
4 areturn
Line numbers:
[pc: 0, line: 1]
So, 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?
Are you into language specifics and internals? Then find some more very interesting details here.
3. All of these are two-dimensional arrays!
class Test {
int[][] a() { return new int[0][]; }
int[] b() [] { return new int[0][]; }
int c() [][] { return new int[0][]; }
}
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:
class Test {
int[][] a = ;
int[] b[] = ;
int c[][] = ;
}
You think that’s crazy? Imagine using JSR-308 / Java 8 type annotations on the above. The number of syntactic possibilities explodes!
@Target(ElementType.TYPE_USE)
@interface Crazy {}
class Test {
@Crazy int[][] a1 = ;
int @Crazy [][] a2 = ;
int[] @Crazy [] a3 = ;
@Crazy int[] b1[] = ;
int @Crazy [] b2[] = ;
int[] b3 @Crazy [] = ;
@Crazy int c1[][] = ;
int c2 @Crazy [][] = ;
int c3[] @Crazy [] = ;
}
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:
Object o1 = true ? new Integer(1) : new Double(2.0);
… the same as this?
Object o2;
if (true)
o2 = new Integer(1);
else
o2 = new Double(2.0);
Nope. Let’s run a quick test
System.out.println(o1);
System.out.println(o2);
This programme will print:
1.0
1
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 NullPointerException
?
Integer i = new Integer(1);
if (i.equals(1))
i = null;
Double d = new Double(2.0);
Object o = true ? i : d; // NullPointerException!
System.out.println(o);
More information about the above can be found here.
5. You also don’t get the compound assignment operator
Continue reading %10 Things You Didn’t Know About Java%
by Lukas Eder via SitePoint
No comments:
Post a Comment