Java Puzzle 10: No Dot - Solution
Sep 13, 2019A couple days ago, I sent out this challenge:
Java challenge: Write Hello World without using a dot, backslash, or semicolon
$ ls NoDot.java $ grep '[.\\;]' NoDot.java $ javac NoDot.java && java NoDot Hello world $Clarification: The goal is to write the content of a single .java file without using those chars. Don't change the environment, alias java="echo Hello World" does not count. Inside the java file, anything goes.
tmtowtdi.— Wouter Coekaerts (@WouterCoekaerts) September 11, 2019
Here's some more details on the solution.
But first: Why does the puzzle disallow those characters exactly?
- The dot because that's the point of this challenge. We use them so much, it's refreshing to imagine life without them.
- The backslash because otherwise you'd just sneak in some dots as
\u
unicode escapes, likeSystem\u002Eout\u002Eprintln("Hello world")
- The semicolon because it looks weird. Where did you get the crazy idea to put a dot on top of a comma anyways?
Syntactic tricks
How do you even invoke any method without using a dot?
For starters, you can invoke constructors, like new Thread()
. Inside of a class, you also don't need to write this.
explicitly to invoke a method on the same class or instance, so you can write for example class X extends Thread { X() {/* call methods on Thread here, without using a dot */} }
. A shorter way to that is with an anonymous inner class, and using a naked {}
block to define an initializer: new Thread(){{/* code goes here */}}
. Since this requires making a subclass, you can't use this e.g. on final classes. And without using a dot, we can only refer to classes in the current package (the default package here) and java.lang. This is rather restrictive, but it's a start.
How do you write a statement without ending with a semicolon?
Block constructs like if
and while
do not include a semicolon, e.g. if (new Thread()==null){}
is a complete statement that does not include a semicolon. This only works for expressions, so you cannot invoke a void
method like this.
One more trick
Method references can be used to refer to methods, without using a dot. Note that method references (just like lambdas) can get converted to the type of an interface that has exactly one non-abstract method, if that method references matches the signature of that method. So we can e.g. do Runnable run = new Thread()::start
. But that doesn't actually run that method yet. For that, we abuse try-with-resources. A try-with-resources block takes an instance of AutoClosable
, and executes its close()
method at the end of the block. This way we can invoke any method without arguments, without needing to make a subclass.
For example, this starts a new thread: try(AutoCloseable o = new Thread()::start){}
. The Thread.start
method becomes the implementation of the close
method of the AutoCloseable
interface. Even though in the AutoCloseable interface the close method is void, method references with a non-void return type can be converted to it too, but unfortunately there is no way to capture their return value.
Solution 1: printStackTrace
The simplest way to print something out under these restrictions, is by using the fact that throwables are printed to stderr. You can't write a throw
statement without using a semicolon, but you can directly call the Throwable.printStackTrace
method.
To avoid outputting more than one line, we can use the 4-argument constructor of Throwable
that takes a boolean writableStackTrace
, that suppresses the generation of the stacktrace.
The output of printStackTrace
starts with the name of the throwable (like java.lang.Throwable: your message
) and we only want to visibly print "Hello World", so we also need to hide that classname. We can start our message with \r
(ascii 13) to move back to the beginning of the line and overwrite the name of the class in the output. Putting that together gives us:
Tada! Hello world without dot, backslash or semicolon.
This works and the output looks right, but it doesn't feel entirely satisfying. It's printing garbage that it then hides, and it uses stderr instead of stdout. Hello world should act like System.out.println("Hello world")
, not like System.err.println("Garbage\rHello World")
. We can do better.
A curious dead end
Here's another technique to invoke some method references. We can turn an interface that has multiple methods into a functional interface (one where a method reference can be converted into) by providing default implementations for all but one of its methods. Then, we can invoke that method from that default implementation. This avoids the need for a dot because in those default interface implementations this
refers to the method reference. Here we'll do that trick with the Appendable
interface, and also extend Closeable
because we know how invoke that. This is not a complete solution, because this doesn't include a way to access System.out
or System.err
without needing an import of java.io.PrintStream
.
Solution 2: defineClass
The final solution uses the Classload.defineClass
method, that takes bytecode as an argument, and it returns that as a java.lang.Class
instance. We can pass in a byte[] that contains the contents of a .class
file that calls System.out.println("Hello world")
. The defineClass
call doesn't execute any of the code of the class yet, but a call to Class.newInstance()
after it takes care of that:
I received two solutions to this puzzle, first in private from Garrett, and this tweet from tackline, both using defineClass
. Of all my java puzzles so far, this might have been the hardest. Thanks for playing everyone, I hope you enjoyed it!