OW #003: A type annotation curiosity, the qualified superclass constructor invocation and more

Marcio Endo
November 27, 2022

Hi, welcome to the third issue of Objectos Weekly.

I decided to rename this project. The old name, Last Week in Objectos, is no more. This is now called Objectos Weekly. I will sometime refer it as "OW" for short.

Let's begin.

Does this annotation apply to the type or to the declaration?

In the last issue, I wrote that, syntactically speaking, annotations are just modifiers.

While the example focused on method declarations, the same principle applies to all declarations where annotations are allowed. For example, the following is valid Java code:

public class TypeAnnotationsExample {

  @Target(ElementType.FIELD)
  @Retention(RetentionPolicy.RUNTIME)
  @interface F {}

  @Target(ElementType.TYPE_USE)
  @Retention(RetentionPolicy.RUNTIME)
  @interface U {}

  @Target({ElementType.FIELD, ElementType.TYPE_USE})
  @Retention(RetentionPolicy.RUNTIME)
  @interface X {}

  private @U volatile @F transient @X int a;

}

Remember that annotations can go on declarations such as a class, a method or a field declaration. And, since Java 8, you can also annotate the types themselves.

So, in the example above, annotations might be annotating:

But how can we know which annotation is being applied to what?

The answer lies in the @Target meta information of each of the annotations:

To confirm we write a small program:

public static void main(String[] args) throws NoSuchFieldException, SecurityException {
  var exampleClass = TypeAnnotationsExample.class;

  var field = exampleClass.getDeclaredField("a");

  var fieldAnnotations = field.getDeclaredAnnotations();

  System.out.println("Field annotations are:");

  printAnnotations(fieldAnnotations);

  var annotatedType = field.getAnnotatedType();

  System.out.println("Type annotations are:");

  printAnnotations(annotatedType.getDeclaredAnnotations());
}

private static void printAnnotations(Annotation[] annotations) {
  for (var annotation : annotations) {
    System.out.println(annotation);
  }
}

And when we run it, it prints:

Field annotations are:
@post.TypeAnnotationsExample.F()
@post.TypeAnnotationsExample.X()
Type annotations are:
@post.TypeAnnotationsExample.U()
@post.TypeAnnotationsExample.X()

The qualified superclass constructor invocation

An inner class instance must be associated to an instance of its enclosing class. In other words, to create an instance of an inner class, we must first create an instance of the outer class.

To illustrate the previous paragraph, consider the following jshell session.

First, we create the Inner class nested in the Outer class:

jshell> class Outer {
   ...>     class Inner {}
   ...> }
|  created class Outer

Let's try to create an Inner instance as if it were a static nested class:

jshell> var inner = new Outer.Inner();
|  Error:
|  an enclosing instance that contains Outer.Inner is required
|  var inner = new Outer.Inner();
|              ^---------------^

We can't. We must first create an instance of the outer class:

jshell> var outer = new Outer();
outer ==> Outer@3941a79c

jshell> var inner = outer.new Inner();
inner ==> Outer$Inner@4fca772d

We could have also written it in a one-liner:

jshell> var inner = new Outer().new Inner();
inner ==> Outer$Inner@b1bc7ed

The takeaway here is:

What if we subclass the Inner class?

If we create a subclass of the Inner class the previous requirement must still hold true. In other words, if we create a subclass InnerChild like so:

class InnerChild extends Outer.Inner {}

We must somehow provide an instance of Outer to InnerChild. And things get a little more complicated if we need to invoke a specific Inner constructor from InnerChild.

Let's start a new jshell session. Next, let's write a new version of Outer and Inner:

jshell> class Outer {
   ...>     Outer() { System.out.println("Outer()"); }
   ...>     Outer(boolean ignore) { System.out.println("Outer(bool)"); }
   ...>     
   ...>     class Inner {
   ...>         Inner() { System.out.println("Inner()"); }
   ...>         Inner(boolean ignore) { System.out.println("Inner(bool)"); }
   ...>     }
   ...> }
|  created class Outer

Next, we define the InnerChild as a subclass of Inner:

jshell> class InnerChild extends Outer.Inner {
   ...>     InnerChild() {
   ...>         new Outer().super();
   ...> 
   ...>         System.out.println("InnerChild()");
   ...>     }
   ...> 
   ...>     InnerChild(Outer outer) {
   ...>         outer.super(true);
   ...> 
   ...>         System.out.println("InnerChild(Outer)");
   ...>     }
   ...> }
|  created class InnerChild

As mentioned, we must, somehow, provide an instance of Outer to InnerChild. The first statement in the first constructor does exactly that:

new Outer().super();

It explicitly create an Outer instance and immediately invokes the Inner constructor that takes no arguments.

In the second constructor, the Outer instance is passed as a parameter:

InnerChild(Outer outer) {
  outer.super(true);
  
  ...
}

And from the Outer instance, it invokes the Inner constructor that takes the boolean argument.

These two super statements are examples of qualified superclass constructor invocation.

Let's see them in action. First, we create an InnerChild with no arguments:

jshell> new InnerChild();
Outer()
Inner()
InnerChild()
$7 ==> InnerChild@7cd84586

Let's now create an InnerChild with the Outer argument. First we create an Outer instance:

jshell> var outer = new Outer(true);
Outer(bool)
outer ==> Outer@30dae81

Then we create the InnerChild instance:

jshell> new InnerChild(outer);
Inner(bool)
InnerChild(Outer)
$9 ==> InnerChild@4edde6e5

I have never used them in "real-life" code. And, in fact, I have just learned about them. So I thought it would be interesting to share.

Objectos Code Weekly

Work on Objectos Code is still in progress. Here are a few examples of what is already possible.

Simple Box type

You can already write simple classes. The following Objectos Code:

public class BoxObjectosCodeExample extends JavaTemplate {
  public @interface TypeAnnotation {}

  @Override
  protected final void definition() {
    _package("com.example");

    autoImports();

    _class(
      annotation(TypeAnnotation.class),
      _public(), id("Box"),

      field(
        _private(), _final(), _int(), id("value")
      ),

      constructor(
        _public(),
        param(_int(), id("value")),
        assign(n(_this(), "value"), n("value"))
      ),

      method(
        _public(), _final(), _int(), id("get"),
        _return(n("value"))
      )
    );
  }
}

Generates:

package com.example;

import post.BoxObjectosCodeExample.TypeAnnotation;

@TypeAnnotation
public class Box {
  private final int value;

  public Box(int value) {
    this.value = value;
  }

  public final int get() {
    return value;
  }
}

Nested classes

You can also have nested classes:

public class NestedClassObjectosCodeExample extends JavaTemplate {
  @Override
  protected final void definition() {
    _package("com.example");

    autoImports();

    _class(
      _public(), id("Outer"),

      _class(
        id("Inner")
      )
    );
  }
}

Generates:

package com.example;

public class Outer {
  class Inner {}
}

Chained methods

You can already declare chained method invocations:

public class ChainedMethodsExample extends JavaTemplate {
  class User {}

  @Override
  protected final void definition() {
    _package("com.example");

    autoImports();

    _class(
      _public(), id("ChainedMethods"),

      method(
        _public(), _static(), _void(), id("main"),
        param(t(t(String.class), dim()), id("args")),

        var("user", chain(
          invoke(t(User.class), "builder"), nl(),
          invoke("id", i(123)), nl(),
          invoke("login", s("foo")), nl(),
          invoke("build")
        )),

        invoke(n(ClassName.of(System.class), "out"), "println", n("user"))
      )
    );
  }
}

Generates:

package com.example;

import post.ChainedMethodsExample.User;

public class ChainedMethods {
  public static void main(String[] args) {
    var user = User.builder()
        .id(123)
        .login("foo")
        .build();
    System.out.println(user);
  }
}

Until the next issue of Objectos Weekly

So that's it for today. I hope you've enjoyed reading.

You can subscribe to this blog via RSS. You can also follow me on Twitter.

The source code of all of the examples are in this GitHub repository.

Please send me an e-mail if you have comments, questions or corrections regarding this post.