Every class in Groovy has a defaulty constructor

2013-10-27 in groovy java
Groovy new considered harmful

In Java, a default constructor is simply one without arguments. If you make a non-abstract class with no explicit constructor, the compiler gives your class a do-nothing default default constructor. This happens in Groovy as well. This is not the subject of this post. Also consider that the title of the post did not say “every Groovy class” — that would be a far less disturbing topic.

Consider the following pure-Java class:

Runner.java

 1package gl.lin;
 2
 3public final class Runner {
 4  private final Runnable runnable;
 5
 6  /** Contract: runnable != null */
 7  public Runner(Runnable runnable) {
 8    System.out.println("Constructing a Runner with runnable=" + runnable);
 9    this.runnable = runnable;
10  }
11
12  public void run() {
13    runnable.run();
14  }
15}

Note the following:

  • The file is pure-Java. There is zero possibility of internal Groovy influence.
  • The class is final. This means that Groovy cannot silently create a subclass of Runner in order to have its way with it.
  • The class defines one constructor, which is not a default constructor. In Java (unlike C++), this entirely suppresses the generation of a default default constructor.

The last point can be illustrated by trying to compile the following file, which is also pure-Java.

Bad.java

1package gl.lin;
2
3public class Bad {
4  public void test() {
5    new Runner();
6  }
7}
1src/main/groovy/gl/lin/Bad.java:5: cannot find symbol
2symbol  : constructor Runner()
3location: class gl.lin.Runner
4    new Runner();
5    ^
61 error

The default constructor simply does not exist, and the compiler yells at us for asserting that it does. This is a good thing.

Now, run the following Groovy code in the same code-base.

Main.groovy

 1package gl.lin;
 2
 3class Main {
 4  static void main(args) {
 5    getRunner().run();
 6  }
 7
 8  /* Imagine this is provided by code elsewhere */
 9  static def getRunner() {
10    return new Runner();
11  }
12}

It compiles successfully, which isn’t too surprising since Groovy allows defining new constructors for classes via its “metaclass” system. It probably throws an exception when we try to actually call the non-existent default constructor, right?

1Constructing a Runner with runnable=null
2Exception in thread "main" java.lang.NullPointerException
3	at gl.lin.Runner.run(Runner.java:14)
4	at gl.lin.Runner$run.call(Unknown Source)
5	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)
6	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
7	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:112)
8	at gl.lin.Main.main(Main.groovy:6)

Nope! Groovy saw that the class we were trying to instantiate didn’t have an accessible default constructor, so it picked another constructor and passed default values (null, 0, or false) as the arguments. Only after the class is fully constructed and returned does the code fail, when we try to call a method on the object, which assumes the class was properly constructed, does the code fail.

But wait, it gets weirder. Let’s look at java.lang.Runtime. In particular, note the second paragraph of the documentation: “An application cannot create its own instance of this class”. Runtime is final and has no constructors whatsoever. Groovy couldn’t possibly do anything weird here, right? The JVM wouldn’t allow it. Right?

Voodoo.groovy

1package gl.lin;
2
3class Voodoo {
4  static void main(args) {
5    /* Create a new Runtime instance, theoretically impossible */
6    Runtime newRuntime = new Runtime();
7    RuntimeAnalyser.analyse(newRuntime);
8  }
9}

RuntimeAnalyser.java

 1package gl.lin;
 2
 3public class RuntimeAnalyser {
 4  /**
 5   * Performs some analysis on an Object which Groovy says is a Runtime
 6   * instance, then calls exit() on it.
 7   */
 8  public static void analyse(Object imposter) {
 9    Runtime authentic = Runtime.getRuntime();
10    System.out.println("Comparing imposter runtime (" + imposter +
11                       ") to real runtime (" + authentic + ")");
12    System.out.println("Class of purported Runtime is " + imposter.getClass());
13    System.out.println("Equal to real Runtime class? " +
14                       (imposter.getClass() == Runtime.class));
15    System.out.println("Authentic classloader is " +
16                       Runtime.class.getClassLoader());
17    System.out.println("Imposter classloader is " +
18                       imposter.getClass().getClassLoader());
19    Runtime other = (Runtime)imposter;
20    System.out.println("Runtimes are the same? " + (other == authentic));
21
22    System.out.println("Calling exit() on imposter...");
23    other.exit(0);
24  }
25}

1Comparing imposter runtime (java.lang.Runtime@578cbd65) to real runtime (java.lang.Runtime@58e65eca)
2Class of purported Runtime is class java.lang.Runtime
3Equal to real Runtime class? true
4Authentic classloader is null
5Imposter classloader is null
6Runtimes are the same? false
7Calling exit() on imposter...
8
9BUILD SUCCESSFUL

I’ve included the epilogue from Gradle to illustrate that the program really does exit successfully. So, not only does Groovy deliberately instantiate a class with no accessible constructor, the JVM lets it! Most likely, Groovy is merely using reflection to bypass access restrictions, which the JVM for some reason allows even for private members. Regardless, the fault lies with Groovy for not honouring such a basic language concept.

Why “considered harmful” and not “not my favourite programming language”? Because it even affects code outside of that written in Groovy, by undermining some of the most fundamental principles of object-oriented and design-by-contract programming. Code which has an unchecked non-null contract in its constructor can no longer assume that the caller knows what its doing, because Groovy may choose to come along and implicitly pass in the one possible illegal value, which only serves to further the Java culture of checking for null more frequently than an insecure teenage girl checks her make-up.

Worse yet, it prevents the enforcement of factory or singleton patterns, which are regrettably necessary in the Java world. With Groovy, anyone can just come by and create an instance of your class without your permission, producing uninitialised or unconfigured instances that should have been retrieved from a factory, or turning your singleton into a multiton, completely destroying the one reason the class exists and ruining it for everyone.

In practise, Groovy’s new operator manages to be less correct and less type-safe than using the high-level reflection API directly. Of course, nobody does this due to the tedium it would involve, and just tries to “be careful” — nominally a C-like concept, but typically turned into overly defensive programming by the Java culture.

1 I believe Eric Phelps coined the wonderful portmanteau that is “defaulty”.