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

marcioendo

Marcio Endo

Posted on December 1, 2022

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

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 an 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;

}
Enter fullscreen mode Exit fullscreen mode

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:

  • the field declaration; and/or
  • the type of the field, i.e., the int type.

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:

  • annotation @F applies to the field as its @Target is defined as FIELD only;
  • annotation @U applies to the int type as its @Target is defined as TYPE_USE only; and
  • annotation @X applies to both the field and the int type as its @Target is defined as both FIELD and TYPE_USE.

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();
|              ^---------------^
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

jshell> var inner = new Outer().new Inner();
inner ==> Outer$Inner@b1bc7ed
Enter fullscreen mode Exit fullscreen mode

The takeaway here is:

  • in order to have an instance of the Inner we must provide an instance of the Outer.

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 {}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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);

  ...
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then we create the InnerChild instance:

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

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"))
      )
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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")
      )
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Generates:

package com.example;

public class Outer {
  class Inner {}
}
Enter fullscreen mode Exit fullscreen mode

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"))
      )
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

πŸ’– πŸ’ͺ πŸ™… 🚩
marcioendo
Marcio Endo

Posted on December 1, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related