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
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 asFIELD
only; - annotation
@U
applies to theint
type as its@Target
is defined asTYPE_USE
only; and - annotation
@X
applies to both the field and theint
type as its@Target
is defined as bothFIELD
andTYPE_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
Inner
we 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)