Java Deathmatch

Results from the Java Deathmatch – A puzzle minigame for developers

A few months ago we released a new side project of ours with a minisite called Java Deathmatch, and since then over 20,000 developers have given it a try. The site features 20 multiple-choice Java questions and today after we’ve gathered stats from all the games that have been played we’re happy to share some of the results and solutions with you.

Overall we collected 61,872 answers, which gives us about 3,094 answers for each of the 20 questions. Each Java deathmatch session randomly chooses 5 questions and gives you 90 seconds to solve each one. Every question has 4 possible answers. We’ve been criticized that the questions are too hard but, well, it’s not called a deathmatch for no reason! Using those stats, we were able to determine which were the hardest questions, and which were the easiest. In this post we’d like to share the 5 toughest questions from this experiment and solve them together.

Correct answers graph

On average, 41% of attempted answers were correct, which is not bad at all. The live stats of the results and questions by index are available right here. The stats for this post are a snapshot from July 26th. Check out Java Deathmatch for the full quiz.

Find the Crap in Your Java App

Show me how >>

Fred

1. The toughest question of the Java deathmatch

Let’s start with the toughest nut to crack, a question we received from Alexandru-Constantin Bledea from Bucharest. And it’s a real brain teaser. Only 20% of the participants were able to solve this questions. This means if you would have chosen an answer at random – You’d probably have a better chance at hitting the right one. Java generics have this quality about them.

The toughest Java question

Alright, so what do we have here? We have generics with type erasure involved, and a couple of exceptions. A few things to remember here:

1. RuntimeException and SQLException both inherit from Exception, while RuntimeException is unchecked and SQLException is a checked exception.
2. Java generics are not reified, meaning that in compile time, the generic type information is “lost” and treated as if the code is replaced with the type’s bound or with Object if it doesn’t exist. This is what you call type erasure.

Naively we’d expect line 7 to cause a compilation error since you can’t cast SQLException to RuntimeException, but that’s not the case. What happens is that T is replaced with Exception so we have:

throw (Exception) t;  // t is also an Exception

Since pleaseThrow expects an Exception, and T is replaced with Exception, the cast is eliminated as if it wasn’t written. We can see that in bytecode:


private pleaseThrow(Ljava/lang/Exception;)V throws java/lang/Exception
L0
LINENUMBER 8 L0
ALOAD 1
ATHROW
L1
LOCALVARIABLE this LTemp; L0 L1 0
// signature LTemp<TT;>;
// declaration: Temp<T>
LOCALVARIABLE t Ljava/lang/Exception; L0 L1 1
MAXSTACK = 1
MAXLOCALS = 2

Just for fun, we tried to see what the bytecode will look like without generics involved, and the cast appeared right before the ATHROW statement:


CHECKCAST java/lang/RuntimeException

Now that we’re convinced there’s no casting involved, we can scratch off these two answers:
“Compilation fails because we cannot cast SQLException to RuntimeException”
“Throws ClassCastException because SQLException is not instanceof RuntimeException”

So we throw a SQLException after all, and you’d expect it to get caught by the catch block and get its stack trace. Well, not really. This game is rigged. Turns out the compiler gets confused just as we do, and the code makes it think that the catch block is unreachable. For the unsuspecting bystander, there is no SQLException. The correct answer is that compilation fails because the compiler doesn’t expect a SQLException to be thrown from the try block – When in fact it does get thrown!

Thanks again Alexandru for sharing this question with us! Another cool way to see exactly what’s wrong here and how the SQLException actually gets thrown is to replace the catch block and make it expect a RuntimeException instead. This way you’ll see the actual stack trace of the SQLException.

Debugging doesn't have to be like this

2. toString(), or not toString(), that is the question

With only 24% of correct answers, the following question was the runner up on the tough scale.

toString(), or not toString()

This one is actually much more simple, just from looking at line 12 we can see that this code prints out m1 and m2, rather than, m1.name and m2.name. The tricky part here was remembering that when printing out a class, Java uses its toString method. The “name” field was artificially added. If you miss that and follow the rest of the code correctly, you might be tricked to choose m1 & new name.

This line sets both names to “m1”:


m1.name = m2.name = "m1";

Then callMe sets m2’s name to new name, and we’re done.

But this snippet will actually print out something like this, including the class name and hashcode:


MyClass@3d0bc85 & MyClass@7d08c1b7

And the correct answer would be “None of the above”.

3. Google Guava Sets

This question didn’t really require specific knowledge of Guava sets, but left most of the respondents confused. Only 25% answered it correctly, the same as choosing an answer at random.

Guava Sets

So what are we seeing here? We have a method that returns a set containing a “clique” of a person’s best friends. We see that there’s a loop that checks if a person has a best friend, and adds them to the results set. If a person indeed has a best friend, it repeats the process for them, so we end up having a set of best friends until we reach a person who doesn’t have a best friend OR that its best friend is already in the set. That last part might be a bit tricky – we can’t add a person who is already in the set so there’s no potential for an infinite loop.

The problem here is that we’re risking an out of memory exception. There’s no bound on the set so we can keep adding and adding people until we run out of memory.

By the way, if you’re into Google Guava, check out this post we wrote about some of the lesser known yet useful features about it.

4. Double brace initialization, lol wut?!

This one was one of the shortest questions, but it was enough to get most of the developers confused. Only 26% got it right.

Double brace initialization

Not many developers are aware of this syntax that comes in handy when you need to initialize a constant collection, although some side-effects are included. Actually, this lack of popularity might be a good thing. So when the WAT?! effect wears off, you can see that we add an element to the list, and then try to print it out. Normally you’d expect it to print out [John] but double brace initialization has other plans in mind. What we see here is an anonymous class that is used to initialize the List. When it tries to print out NAMES, it actually comes out as null. Since the initializer wasn’t consumed yet and the list is empty.

You can read more about double brace initialization right here.

5. The curious case of the map at runtime

This one is another community-contributed question coming from Barak Yaish from Israel. Only 27% of the participants were able to solve this question.

The curious case of the map at runtime

Alright, compute looks up a value in the map. If it’s null, it adds it and returns its value. Since the list is empty, “foo” doesn’t exist, v is null, and we map “foo” to a new ArrayList<Object>(). The ArrayList is empty, so it prints out [].

For the second line, “foo” does exist in the map so we evaluate the expression on the right. The ArrayList is cast to a List successfully, and “ber” is added to it. add returns true and that’s what it prints out.

The correct answer is [] true. Thanks again Barak for sharing this question with us!

Bonus: And the easiest question is…

This time we have a question coming from Peter Lawrey of OpenHFT who also blogs on Vanilla Java. Peter is on the top 50 list of StackOverflow and this time he moved over to the other side and asked a question that 76% of you got right.

Bonus: Easiest Question
Answer C is simpler than A, B & D doesn’t compile.

Conclusion

From time to time we really like playing this kind of puzzles to sharpen our Java knowledge, but if you ever find yourself spending too much time on these puzzlers in your own codebase, it will probably be less than ideal. Especially if someone calls in the middle of the night to fix a critical production error. For this kind of situation, we’ve built Takipi for Java. Takipi is a Java agent that knows how to track uncaught exceptions, caught exceptions and log errors on servers in production. It lets you see the variable values that cause errors, all across the stack, and overlays them on your code.

fredjava

15 tools to use when deploying new code to production – View tool list

Java 8

Java 8 exceptions have never been so beautiful – Try Takipi for Java 8

email
Some kind of monster @ OverOps, GDG Haifa lead.
  • Dmitry Salychev

    Soo, the difficulty of the question #7 is that many developers don’t remember that generics are for developers and the compiler, and not for runtime. Good point to know about generic types is “OCA/OCP Programmer Study Guide”, especially Objective 4.3, I guess.

    • http://www.takipi.com/ Alex Zhitnitsky

      What’s that objective?

      • Dmitry Salychev

        OCP Objective 4.3 “Analyse the interoperability of collections that use raw and generic types”.

  • Slim Ouertani

    In bonus question, why A doesn’t compile ? Is it refering to String pool concept here ?

    • Rade Martinović

      Yep. I thought that A compiles too? I don’t have any IDE open right now, but it should work I think…

    • Yonatan Graber

      A compiles. They meant: (Answer C is simpler than A) && (B & D doesn’t compile)

    • http://www.takipi.com/ Alex Zhitnitsky

      Hey, right, so as the other comments state, A compiles as well, C is just simpler.

  • Yonatan Graber

    As for question 1 – Lombok (https://projectlombok.org) is exploiting this trick for the @SneakyThrows feature – allowing to throw checked exception without declaring them. One of the use cases for that feature is having to handle the “UnsupportedEncodingException” exception when the encoding is “utf-8”, while that exception must be supported by all JVMs.

    • http://www.takipi.com/ Alex Zhitnitsky

      Cool, thanks for the reference Yonatan! Sneaky indeed 🙂

  • Benedek Kiss

    I wonder if people can answer my bonus question after reading the solution for question #5.
    What does “collection” contain after the code snippet is run?
    Eg if I added this line:

    System.out.println(collection);

    what would it print?

  • Simon Peter Haverdings

    How can example 3 ever compile?
    An import statement followed by a method declaration is invalid in java.
    There should be a class declaration between. Or the import should have been a comment if it should have only been seen as an explanation of the used utility class.

    The ‘potential out of memory’ is only in case the friends are an lazy loaded entity, which are loaded into memory upon request. In all other cases you could assume that the related persons are already ‘in memory’, making the answer ‘nothing’ the perfect fit. Remember that you don’t clone the entities, but just the reference to one.

    • Bit Man

      Who knows the implementation of hashcode() ? 😉

    • Michał Janiec

      I also dislike this question. At first I thought that it is wrong because returned set may not be a clique (at least not in graph theory means). And stating that it can run out of memory is like stating that any code could. If it really does then it do not mean that the code is wrong, but that is badly used.

  • Marionett

    Hmm, I seem to be stuck in my own thinking regarding no 1. The compiler indeed does not accept an SQLException but it does accept both RuntimeException and Exception so the catch block is not really unreachable for all Exceptions. And even if it was possible to do the (T)t cast in runtime, then I cannot see how the expected result would ever be an SQLException neither in compile nor runtime, since it has already been casted to the type of T (which is in compile time the type bound for the generic type (i.e. Exception) and in runtime intended to be the type of T (RuntimeException))?

    • Alexandru-Constantin

      I can answer that. The reason why you would use this (I’m not saying people should, this is very advanced usage for people that know what they are doing) is to get around limitations put in place by some api that you are using and cannot modify.

      Imagine that you have a api that is not defined as throwing any kind of exception. Then you are forever limited by the api and would have to wrap your actual exception in a runtime exception. Then, when catching it, you do if (runtimeex.getCause instanceof SQLException …. ugly!).

      By mocking a checked exception as a runtime exception, you can have as many checked exceptions as you want and now you can actually catch the actual exception. But how? The compiler doesn’t allow catching SQLException…

      Actually, it does, all you need to do is to create a method that calls the object which in turn throws the checked exception and define it to throw whatever you want. Like this;

      public static void execute(Runnable runnable) throws SQLException {
      runnable.run();
      }

      Notice that runnable will “never” throw a SQLException in normal circumstances, but with a bit of mockery it does. Now the compiler doesn’t complain anymore because it knows that execute is defined as throing sqlexception.

      Again, this is not something that I suggest people do on a regular basis, it is an exceptional case for people that know what they are doing.

  • Chinthaka Dassanayake

    You know, questions are answered, while problems are solved.