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

marcioendo

Marcio Endo

Posted on December 9, 2022

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

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

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

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

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

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

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

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:

  • in order to have an instance of the inner class we must provide an instance of the outer class.

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

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:

  • allow null values to the Super fields; but
  • disallow null values to the Inner fields.

We can annotate our class like so:

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

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

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

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

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

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

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

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;
}
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.

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

If you liked this content please consider subscribing to the blog via email.

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

Posted on December 9, 2022

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

Sign up to receive the latest update from our blog.

Related