Records and type annotations
Michael Hixson
michael.hixson at gmail.com
Sun Dec 22 17:32:58 UTC 2019
Hello,
I'm trying records with type annotations and I notice a couple of
unexpected behaviors in terms of how the annotations are applied. I'm
using build 14-ea+28-1366.
1. If I explicitly declare the canonical constructor but omit the
parameter list, the parameters of that constructor don't inherit the
type annotations of the components. I expected the type annotations
to be inherited in the same way they would have been had I not
declared the canonical constructor.
2. The type annotations of the components appear in the annotations of
the accessor methods rather than in the annotated return types of
those methods. I expected the type annotations to be in the annotated
return types because that's what happens when I write out a regular
non-record class with type annotations.
I've inlined the source code I'm using and its output at the end of
this email. The output for Record2's constructor shows issue #1 (its
annotatedType is different than Record1's), and the output for the
three classes' accessor methods shows issue #2 (the regular class's
method is different from the two records').
Issue #1 seems like the more problematic of the two. It feels like my
types are being mangled. "Hey, who said you could change the type
from `@Nullable String` to `String`, which is a different and
incompatible type? Not me." Issue #2 is just something that caught
my eye while investigating #1.
-Michael
-------------------------------
package example;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Arrays;
public class RecordsTest {
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
@interface Nullable {}
static class RegularClass {
private final @Nullable String t;
public RegularClass(@Nullable String t) { this.t = t; }
public @Nullable String t() { return t; }
}
record Record1(@Nullable String t) {}
record Record2(@Nullable String t) {
public Record2 {}
}
public static void main(String[] args) {
describeClass(RegularClass.class);
describeClass(Record1.class);
describeClass(Record2.class);
}
static void describeClass(Class<?> clazz) {
System.out.println(clazz);
for (var field : clazz.getDeclaredFields()) {
System.out.println(
" field " + field);
System.out.println(
" annotatedType "
+ field.getAnnotatedType());
System.out.println(
" annotations "
+ Arrays.toString(field.getAnnotations()));
}
for (var constructor : clazz.getConstructors()) {
System.out.println(
" constructor " + constructor);
for (var parameter : constructor.getParameters()) {
System.out.println(
" parameter " + parameter);
System.out.println(
" annotatedType "
+ parameter.getAnnotatedType());
System.out.println(
" annotations "
+ Arrays.toString(parameter.getAnnotations()));
}
}
for (var method : clazz.getDeclaredMethods()) {
var name = method.getName();
if (name.equals("equals")
|| name.equals("hashCode")
|| name.equals("toString"))
continue;
System.out.println(
" method " + method);
System.out.println(
" annotatedReturnType "
+ method.getAnnotatedReturnType());
System.out.println(
" annotations "
+ Arrays.toString(method.getAnnotations()));
}
System.out.println();
}
}
-------------------------------
class example.RecordsTest$RegularClass
field private final java.lang.String example.RecordsTest$RegularClass.t
annotatedType @example.RecordsTest$Nullable() java.lang.String
annotations []
constructor public example.RecordsTest$RegularClass(java.lang.String)
parameter java.lang.String arg0
annotatedType @example.RecordsTest$Nullable() java.lang.String
annotations []
method public java.lang.String example.RecordsTest$RegularClass.t()
annotatedReturnType @example.RecordsTest$Nullable() java.lang.String
annotations []
class example.RecordsTest$Record1
field private final java.lang.String example.RecordsTest$Record1.t
annotatedType @example.RecordsTest$Nullable() java.lang.String
annotations []
constructor public example.RecordsTest$Record1(java.lang.String)
parameter java.lang.String t
annotatedType @example.RecordsTest$Nullable() java.lang.String
annotations []
method public java.lang.String example.RecordsTest$Record1.t()
annotatedReturnType java.lang.String
annotations [@example.RecordsTest$Nullable()]
class example.RecordsTest$Record2
field private final java.lang.String example.RecordsTest$Record2.t
annotatedType @example.RecordsTest$Nullable() java.lang.String
annotations []
constructor public example.RecordsTest$Record2(java.lang.String)
parameter final java.lang.String t
annotatedType java.lang.String
annotations []
method public java.lang.String example.RecordsTest$Record2.t()
annotatedReturnType java.lang.String
annotations [@example.RecordsTest$Nullable()]
More information about the amber-dev
mailing list