Objectos Weekly #004: Type annotations, canonical names and inner classes

Marcio Endo
December 4, 2022

edit 2022-12-10: add the 'static nested class' subsection

Welcome to Objectos Weekly issue #004.

I was writing this issue when the power in my apartment went out and I heard a loud noise outside. I looked outside from my balcony and I saw that the tree in front of my building had fallen taking a power line with it. Scary: a live power line was exposed right at the sidewalk. It burned a hole through the pavement. Luckily, there was no one there when it happened.

The power was restored later the same day. But it was already too late to do any meaningful work; I finished this issue the next day.

Type annotations, canonical names and inner classes

I have recently learned a detail regarding type annotations, canonical names and inner classes.

Suppose a class Example which is a subclass of the Super type:

package post;

import post.util.Super;

public class Example extends Super {}

Notice that the superclass is in a different package of the subclass. For this reason, a import declaration was required.

Let's annotate the superclass with a Nullable annotation.

package post;

import post.annotation.Nullable;
import post.util.Super;

public class Example extends @Nullable Super {}

The annotation might indicate to a hypothetical static analysis tool that the superclass fields can hold null values.

The code above compiles without errors.

Referring to the superclass by its canonical name

What happens if we use the canonical name of the superclass instead? We can remove the import declaration, like so:

package post;

import post.annotation.Nullable;

// does not compile!
public class Example extends @Nullable post.util.Super {}

After this change, the code no longer compiles! javac issues the following error message:

$ javac -d /tmp src/main/java/post/{,annotation,util}/*.java
src/main/java/post/Example.java:10: error: cannot find symbol
public class Example extends @Nullable post.util.Super {
                                       ^
  symbol: class post
1 error

It seems javac was expecting a class right after the Nullable annotation. Eclipse JDT error message gives more information:

Illegally placed annotation: type annotations must directly precede the simple name of the type they are meant to affect (or the [] for arrays)

OK, let's try to fix it then.

Fixing the compilation error

We must write the annotation right before the simple name of the class, like so:

package post;

import post.annotation.Nullable;

public class Example extends post.util. @Nullable Super {}

Notice how the annotation is after the trailing dot character of the package name.

The code now compiles without errors:

$ javac -d /tmp src/main/java/post/{,annotation,util}/*.java

[no errors emitted]

Well, I didn't know about that.

Why though?

I don't know for sure. But I think inner classes are one of the reasons.

In the previous issue I talked about inner classes and qualified superclass constructor invocations. The main takeaway to remember is:

Extending an inner class of Super

Let's say our superclass Super declares an inner class called Inner.

Let's have our Example class extend Inner instead of Super:

package post;

public class Example extends post.util.Super.Inner {
  public Example(post.util.Super outer) {
    outer.super();
  }
}

As Inner is an inner class, we must provide an instance of Super. That is why we had to add the constructor and use the qualified superclass constructor invocation.

Let's get back to our hypothetical static analysis tool. Let's say we would like to:

We can annotate our class like so:

package post;

import post.annotation.NotNull;
import post.annotation.Nullable;

public class Example 
    extends 
    post.util.@Nullable Super.@NotNull Inner {
    
  public Example(post.util.Super outer) {
    outer.super();
  }
  
}

That's peculiar. I can definitely say I don't see it everyday.

It compiles without errors:

$ javac -d /tmp src/main/java/post/{,annotation,util}/*.java

[no errors emitted]

Static nested class

You should know that the form of the last example:

post.util.@Nullable Super.@NotNull Inner

Is allowed because Inner is an inner class. If it were a static nested class, a compilation error would occur instead.

To illustrate, consider the following example:

package post.nested;

import post.annotation.NotNull;
import post.annotation.ReadOnly;

public class Example1 {

  class Nested {}

  post.nested.Example1.@NotNull Nested a;
  
  post.nested.@ReadOnly Example1.Nested b;
  
  post.nested.@ReadOnly Example1.@NotNull Nested c;
}

As Nested is an inner class, all of the forms compile without errors.

However, if we turn Nested into a static nested class, only the first form is allowed:

package post.nested;

import post.annotation.NotNull;
import post.annotation.ReadOnly;

public class Example2 {

  static class Nested {}

  // ok
  post.nested.Example2.@NotNull Nested a;

  // does not compile!
  post.nested.@ReadOnly Example2.Nested b;

  // does not compile!
  post.nested.@ReadOnly Example2.@NotNull Nested c;

}

The code above does not compile!

$ javac -d /tmp src/main/java/post/{,annotation,nested,util}/*.java
src/main/java/post/nested/Example2.java:27: error: scoping construct
    cannot be annotated with type-use annotation: @post.annotation.ReadOnly
  post.nested.@ReadOnly Example2.Nested b;
             ^
src/main/java/post/nested/Example2.java:29: error: scoping construct
    cannot be annotated with type-use annotation: @post.annotation.ReadOnly
  post.nested.@ReadOnly Example2.@NotNull Nested c;
             ^
2 errors

As noted, fields b and c fail to compile.

As Nested is now a static nested class, the enclosing Example2 class serves only as a scoping construct.

Objectos Code Weekly

Work on Objectos Code is still in progress. Please note that the syntax is still subject to change. In fact I experimented a bit with names that are reserved keywords.

For instance, to write a package declaration in Objectos you write:

_package("com.example.model");

As package is a reserved keyword, the name of the method has a leading underscore character. I tried the following variations:

packageDeclaration("com.example.model");

packageDecl("com.example.model");

package_("com.example.model");

package$("com.example.model");

But I was not pleased with any of them. So I decided to stick with the leading underscore version.

Method type parameters and type variables

You can declare a method with type parameters. The following Objectos Code:

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

    autoImports();

    _class(
      _public(), _final(), id("Util"),

      constructor(_private()),

      method(
        _public(), _static(),
        tparam("T", t("com.example", "Foo")),
        _void(),
        id("sort"),
        param(t(t("java.util", "List"), tvar("T")), id("list")),

        invoke(t(Collections.class), "sort", n("list"))
      )
    );
  }
}

Generates:

package objectos.example;

import com.example.Foo;
import java.util.Collections;
import java.util.List;

public final class Util {
  private Util() {}

  public static <T extends Foo> void sort(List<T> list) {
    Collections.sort(list);
  }
}

Parameterized types

You might have noticed in the previous example that you can write type arguments of a generic type. The following Objectos Code:

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

    autoImports();

    _class(
      _public(), id("ParameterizedTypesExample"),
    
      field(
        t(t("java.util", "Map"), t(String.class), t("java.lang", "Integer")),
        id("map")
      ),

      field(t(t(Set.class), t(OpenOption.class)), id("set")),

      field(
        t(
          t("java.util", "List"),
          t(t(Map.class), t(String.class), t(String.class))
        ), id("list")
      )
    );
  }
}

Generates:

package objectos.example;

import java.nio.file.OpenOption;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class ParameterizedTypesExample {
  Map<String, Integer> map;

  Set<OpenOption> set;

  List<Map<String, String>> list;
}

Until the next issue of Objectos Weekly

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

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.