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;
}
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
inttype.
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
@Fapplies to the field as its@Targetis defined asFIELDonly; - annotation
@Uapplies to theinttype as its@Targetis defined asTYPE_USEonly; and - annotation
@Xapplies to both the field and theinttype as its@Targetis defined as bothFIELDandTYPE_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);
}
}
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:
- in order to have an instance of the
Innerwe must provide an instance of theOuter.
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.
Top comments (0)