10 Things You Didn’t Know About Java

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 "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 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

Quirky enough? Let’s consider the following two pieces of code:

i += j;
i = i + j;

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.
This is so beautiful, I would like to cite Peter Lawrey‘s answer to this Stack Overflow question:
A good example of this casting is using *= or /=
byte b = 10;
b *= 5.7;
System.out.println(b); // prints 57
or
byte b = 100;
b /= 2.5;
System.out.println(b); // prints 40
or
char ch = '0';
ch *= 1.1;
System.out.println(ch); // prints '4'
or
char ch = 'A';
ch *= 1.5;
System.out.println(ch); // prints 'a'
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:

for (int i = 0; i < 10; i++) {
  System.out.println((Integer) i);
}

… then “sometimes”, I get the following output:
92
221
45
48
236
183
39
193
33
84
How is that even possible?? . . . . . . spoiler… solution ahead… . . . . . OK, the solution is here (https://blog.jooq.org/add-some-entropy-to-your-jvm/) and has to do with overriding the JDK’s Integer cache via reflection, and then using auto-boxing and auto-unboxing. Don’t do this at home! Or in other words, let’s think about it this way, once more
When I do that one last commit just before my 4 week vacation When I do that one last commit just before my 4 week vacation

7. GOTO

This is one of my favourite. Java has GOTO! Type it…

int goto = 1;

This will result in:
Test.java:44: error: <identifier> expected
    int goto = 1;
       ^
This is because goto is an unused keyword, just in case… But that’s not the exciting part. The exciting part is that you can actually implement goto with break, continue and labelled blocks: Jumping forward

label: {
  // do stuff
  if (check) break label;
  // do more stuff
}

In bytecode:
2  iload_1 [check]
3  ifeq 6          // Jumping forward
6  ..
Jumping backward

label: do {
  // do stuff
  if (check) continue label;
  // do more stuff
  break label;
} while(true);

In bytecode:
 2  iload_1 [check]
 3  ifeq 9
 6  goto 2          // Jumping backward
 9  ..

8. Java has type aliases

In other languages (e.g. Ceylon), we can define type aliases very easily:

interface People => Set<Person>;

A People type constructed in such a way can then be used interchangably with Set<Person>:

People?      p1 = null;
Set<Person>? p2 = p1;
People?      p3 = p2;

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 Integer, Long etc, we want shorter names: I and L. Easy:

class Test<I extends Integer> {
    <L extends Long> void x(I i, L l) {
        System.out.println(
            i.intValue() + ", " + 
            l.longValue()
        );
    }
}

In the above programme, Integer is “aliased” to I for the scope of the Test class, whereas Long is “aliased” to L for the scope of the x() method. We can then call the above method like this:

new Test().x(1, 2L);

This technique is of course not to be taken seriously. In this case, Integer and Long are both final types, which means that the types I and L are effectively 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:

// A helper type. You could also just use List
interface Type<T> {}

class C implements Type<Type<? super C>> {}
class D<P> implements Type<Type<? super D<D<P>>>> {}

Now, what do the types C and D even mean? They are somewhat recursive, in a similar (yet subtly different) way that java.lang.Enum is recursive. Consider:

public abstract class Enum<E extends Enum<E>> { ... }

With the above specification, an actual enum implementation is just mere syntactic sugar:

// This
enum MyEnum {}

// Is really just sugar for this
class MyEnum extends Enum<MyEnum> { ... }

With this in mind, let’s get back to our two types. Does the following compile?

class Test {
    Type<? super C> c = new C();
    Type<? super D<Byte>> d = new D<Byte>();
}

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>
Step 1) Type<Type<? super C>> <?: Type (inheritance)
Step 2) C  (checking wildcard ? super C)
Step . . . (cycle forever)
And then: Is D a subtype of Type<? super D<Byte>>?
Step 0) D<Byte> <?: Type<? super C<Byte>>
Step 1) Type<Type<? super D<D<Byte>>>> <?: Type<? super D<Byte>>
Step 2) D<Byte> <?: Type<? super D<D<Byte>>>
Step 3) Type<type<? super C<C>>> <?: Type<? super C<C>>
Step 4) D<D<Byte>> <?: Type<? super D<D<Byte>>>
Step . . . (expand forever)
Try compiling the above in your Eclipse, it’ll crash! (don’t worry. I’ve filed a bug) Let this sink in…
Some type relationships in Java are undecidable!
If you’re interested in more details about this peculiar Java quirk, read Ross Tate’s paper “Taming Wildcards in Java’s Type System” (co-authored with Alan Leung and Sorin Lerner), or also our own musings on correlating subtype polymorphism with generic polymorphism

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:

class Test<T extends Serializable & Cloneable> {
}

The generic type parameter T that you’re binding to instances of the class Test must implement both Serializable and Cloneable. For instance, String is not a possible bound, but Date is:

// Doesn't compile
Test<String> s = null;

// Compiles
Test<Date> d = null;

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:

<T extends Runnable & Serializable> void execute(T t) {}

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. Lambdas can be serialised:
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 Serializable

execute((Serializable) (() -> {}));

… then the lambda will no longer be Runnable. Egh… So… Cast it to both types:

execute((Runnable & Serializable) (() -> {}));

Conclusion

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
Found this article interesting? How about this one: 10 Subtle Best Practices when Coding Java

40 thoughts on “10 Things You Didn’t Know About Java

  1. 8 out of 10 ? Am I above average?

    May I add one? What is a volatile method? You may happen to see that

    Modifier.isVolatile(method.getModifiers())

    returns true for some Method method;

    1. You’re certainly above average, although the sample I have right now doesn’t allow me to jump to conclusions :-)

      Interesting – so what *is* a volatile method?

      1. A “volatile” method is a bridge method (as in item 2 of the above article). That’s because the bridge modifier flag and volatile modifier flag are identical.

    1. There probably are, but most developers deal with all those fancy use-cases like this:

      try {
        ...
      }
      catch (Exception ignore) {}
      

      alternatively…

      try {
        ...
      }
      catch (Exception e) {
        log.error(e);
        throw e; // TODO find better treatment here
      }
      

      At least, throwing RuntimeExceptions is honest :-)

      1. Users throwing RuntimeException is one thing. Library authors throwing RuntimeException when a checked exception is appropriate is the real sin.

        Let users be as lazy as they want, but library authors are supposed to pay special attention to design. Libraries who do this are forcing catch (Exception) on all their users.

        1. Let’s be more specific… What library do you have in mind, with what use-case?

          Let users be as lazy as they want

          That is – unfortunately – not possible with checked exceptions…

  2. I run the code from chapter “6. Random integers” , and that is what i got:

    t:\>java Test
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9

    t:\>java -version
    java version “1.8.0_20”
    Java(TM) SE Runtime Environment (build 1.8.0_20-b26)
    Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode)

    So there is no “problem” with cache on Java 1.8.x.

    1. I know – me too. Well, it took a while for an academic to write a whole paper about the quirks of use-site variance, so it’s definitely not an obvious problem… But definitely a real one. The listed example will crash both Eclipse and javac compilers.

  3. Use case for Point 3 is quite obvious: Job security.

    Will start changes from Monday, as most my colleagues will be having a long weekend.
    Thanks Lukas! ;)

  4. 1. Test. doThrow0(new SQLException()) should fail with a ClassCast Exception because an SQLException can’t be cast to a RuntimeException; if it really doesn’t fail, that’s a glaring security bug in Java caused by Generics type erasure!

    6. Is dangerous cheating via reflection, and a security policy should stop that dead.

    1. 1. There is no cast to RuntimeException, only a cast to Exception

      6. You’re right, but those security policy usually have to be enabled explicitly. The standard JDK configuration will allow you to run that code.

  5. Probably 9) was too tricky for the previous readers so they even did not try to fully understand it. There’s a strange sequence in the article:

    Step 0) D <?: Type<? super C>
    Step 1) Type<Type<? super D<D>>> <?: Type<? super D>
    Step 2) D <?: Type<? super D<D>>
    Step 3) List<List<? super C>> <?: List<? super C>
    Step 4) D<D> <?: Type<? super D<D>>
    Step . . . (expand forever)

    Why List and C are here? There was no List type in the example and D type is independent on C type. As far as I understand, the correct sequence should be like this:

    Step 0) D <?: Type<? super D>
    Step 1) Type<Type<? super D<D>>> <?: Type<? super D>
    Step 2) D <?: Type<? super D<D>>
    Step 3) Type<Type<? super D<D>>> <?: Type<? super D<D>>
    Step 4) D<D> <?: Type<? super D<D>>
    Step . . . (expand forever)

    1. Thanks for your feedback. You’re right. I first wanted to introduce a “real” type to make the example from the article by Ross Tate a bit more concrete. I then reverted to Type and forgot a line…

          1. The point is: you wrote — “The exciting part is that you can actually implement goto with break, continue and labelled blocks”. And I am asking for your help in doing so by implementing a simple binary tree walk (inorder — https://gist.github.com/kedarmhaswade/941527db4e6a2094525a) without the tail-call. IOW, remove the tail call from a more straightforward (recursive) tree walk routine using the ‘break, continue and labeled blocks’ in Java.

            1. I see. Well, the idea was rather academic. I actually don’t see the point of your exercise, I’m afraid. Your original attempt is wrong because you cannot use continue with if:

               l1: if (r != null) {
                    printTree(r.left);
                    System.out.print(r.key + " ");
                    r = r.right;
                    continue l1;
                  }
              

              Of course, you could use continue / break with an infinite for(;;) loop but why would you do that instead of just using the for loop normally?

              Anyway, don’t get caught up with this too much. Yes, continue and break can act like goto. No, it’s usually not a good idea and it’s technically not really the same thing.

              1. Now I am completely confused by your response. Aren’t you contradicting yourself? The easiest thing would be to remove the misleading point 7 from your post because Java does /not/ have goto.

  6. Don’t say “everyone agrees that checked exceptions were a mistake” as that’s simply wrong as I know at least one person (me) who disagrees. And that’s sufficient to make that statement wrong.

    Regarding your second point, the JVM wasn’t changed. It always disambiguated by the full signature including the return type. It might have required a bit of wording in the specification to justify the already existing behavior but that was even easier than making such a JVM change.

    Using break and continue to implement a goto-like feature still results in a kind of “structured goto” as you can transfer the control flow to the beginning or end of a block/ statement boundary only and these are clearly nested. This does not allow the full degree of the infamous spaghetti code.

    1. Holger! Thank you very much for your feedback!

      Don’t say “everyone agrees that checked exceptions were a mistake” as that’s simply wrong as I know at least one person (me) who disagrees. And that’s sufficient to make that statement wrong.

      Obviously it is wrong. Pretty much everything with a quantifier in front of it is wrong (ironically, this sentence contains the “everything” quantifier itself). This is a boulevardesque, polemic blog, so I hope you can see past the little word games. I’m sorry if this has confused or bothered you.

      Regarding your second point, the JVM wasn’t changed. It always disambiguated by the full signature including the return type. It might have required a bit of wording in the specification to justify the already existing behavior but that was even easier than making such a JVM change.

      Interesting, I wasn’t aware of that. Thanks for pointing this out. Perhaps I confused the generation of synthetic methods to implement method return type covariance with the mere fact of having the ability of addressing such synthetic methods. What would have been the original motivation of supporting overloading by return type in early JVM versions?

      Using break and continue to implement a goto-like feature still results in a kind of “structured goto” as you can transfer the control flow to the beginning or end of a block/ statement boundary only and these are clearly nested. This does not allow the full degree of the infamous spaghetti code.

      That’s right. Given that bytecode (or even assembler) level goto can jump around leaving the stack in an inconsistent state, I suspect that break and continue indeed aren’t as powerful as true goto.

      1. Actually, you can’t ‘goto’ however you want it bytecode. The bytecode verifier makes sure the operand stack looks the same everytime you run an instruction. You can’t for example use goto to create a loop that pushes 10 operands to the operand stack. (The operand stack would look different in each iteration.)

      2. Actually, you can’t ‘goto’ however/wherever you like it bytecode either. The bytecode verifier makes sure the operand stack looks the same each time you reach a specific instruction. You can’t for example use goto to create a loop that pushes 10 operands to the operand stack. (The operand stack would look different in each iteration.) So in a sense, goto is only allowed in a structured sense also in the JVM.

  7. The JVM does not define the language, the JLS does. So, there are checked exceptions. They are a compile time feature that was put in to make code more robust and explicitly show the developer’s intention. In the Java language, they defined and enforced. The representation in JVM is not relevant.

    As a rule, I always would rather the compiler catch code problems, rather than having to put code in to handle runtime errors. Which is one big reason that I prefer strongly typed languages. However, in the checked vs. unchecked debate, I agree that checked exceptions are a failed experiment.

  8. My comment is simple. I don’t agree with 1-10. But I surely have got some points. I guess it’s just a matter of taking those good pointers for yourself and disregarding those that aren’t. It’s still a good read. Thanks @lukaseder

Leave a Reply