Every class in Groovy has a defaulty constructor
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 ofRunner
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”.