Groovy: Public Variables Aren't
One of the most obvious ways with which Groovy tries to “reduce” boiler-plate is to make all members (both variables and methods) of any class public by default, as opposed to Java in which members are package-private by default. While this is already a questionable design decision at best, an interesting facet of the implementation is that implicitly public member variables are private, though another Groovy feature masks this detail when operating within Groovy.
We’ll start with a Groovy class that just has some members of varying types and access modifiers. You can compile these files yourself by dropping them into the base project.
Base.groovy
1package gl.lin;
2
3public class Base {
4 public int pub;
5 int integer;
6 boolean bool;
7
8 protected int prot;
9}
If we make a derived class in Java, where the compiler actually verifies that things we reference exist, we discover some interesting things about what Groovy did with the access modifiers.
Deriv.java
1package gl.lin;
2
3public class Deriv extends Base {
4 public void foo() {
5 pub -= 1;
6 integer += 1;
7 bool = !bool;
8 prot += 2;
9 }
10}
1$ ./gradlew build
2
3...
4
5src/main/groovy/gl/lin/Deriv.java:6: cannot find symbol
6symbol : variable integer
7location: class gl.lin.Deriv
8 integer += 1;
9 ^
10src/main/groovy/gl/lin/Deriv.java:6: operator + cannot be applied to integer,int
11 integer += 1;
12 ^
13/tmp/base-project/src/main/groovy/gl/lin/Deriv.java:7: cannot find symbol
14symbol : variable bool
15location: class gl.lin.Deriv
16 bool = !bool;
17 ^
183 errors
Analysing the compiler’s output tells us the following:
pub
really was public, as we declared it to be. This is exactly what anyone in their right mind would expect.prot
was similarly at least protected, as the derived class could access it directly. Again, exactly what should be expected.integer
andbool
are nominally public, and in other Groovy code can be accessed as such. However, our Java code failed to compile — since both classes are in the same package, and one class derives from the other, this means that these two variables are actually private.
What Groovy actually does with implicitly-public variables is to make them
private, then generate JavaBeans-style get()
and set()
accessor methods for
them. Groovy implicitly calls these when a non-existent field is accessed, so
the fields really look public to other Groovy code.
But, of course, in Java we end up having to write things like
Deriv2.java
1package gl.lin;
2
3public class Deriv2 extends Base {
4 public void foo() {
5 pub -= 1;
6 setInteger(getInteger() + 1);
7 setBool(!isBool());
8 prot += 2;
9 }
10}
Notice in particular how the verb that should be get
is actually dependent on
the declared type of the variable — it changes to is
for booleans. This
quirk, albeit conventional for JavaBeans, is especially interesting since
variable types are often undeclared in Groovy.
As is par for the Codehaus course, Groovy has taken something from Java that
couldn’t possibly be simpler — the public
access modifier — and transformed
it into something inconsistent and surprising. Groovy is the only language I
know of where making a variable protected
increases the accessible scope of
what previously was a “public” field.
Of course, some people will say that a private-field-with-accessors pattern is “good programming practise”, and that this is obviously what Groovy is doing. This is an empty argument: A getter-setter pair whose contract indicates that the setter takes no further action, and whose getter returns the most recent set value, is a public variable, merely a less convenient one.
Regardless of how one feels about public variables in code, a programmer has to be able to deal with them effictively, especially in a code-base built in Groovy, which is almost certainly full of variables that are implicitly public (Is it intentional? Maybe the original programmer forgot the access modifier?). Writing new code in Java within such a code-base becomes unnecessarily tedious, especially in the context of an anæmic domain model, giving the programmer a choice of continuing the use of Groovy and worsening the problem, or writing Java littered with verbose get/set calls.