Providing runtime type information without changing type erasure

Red IO redio.development at gmail.com
Sun Dec 4 09:06:49 UTC 2022


I was once again writing some generic Java class and suddenly I stand there
again, I could not get the type of T.

I’m sure this scenario sound familiar to every Java developer out there.

If you first stumble across this problem type erasure comes up as the
culprit: “Type T is not there at runtime it is replaced with Object”

There are many solutions to this problem some hacky some require braking
change to the generic system.

Now my idea was why don’t we trace back the types at compile time? I mean
every generic class is constructed somewhere in 6 possible ways:

Foo<String> foo = new Foo<>();
Foo<T> foo2 = new Foo<>();
Foo foo3 = new Foo();
Foo<?> foo4 = new Foo<>();
Foo<? extends String> foo5 = new Foo<>();
Foo<? super String> foo6 = new Foo<>();
In case 1 we are already at our destination. The code the compiler gets
contains the information we want. Why don’t we attach the type written in
plain sight to the generic constructor?

Like an implicit Foo<String> foo = new Foo<>(java.lang.String.class);
(which explicitly is a current way of solving this. Which is in my opinion
pretty ugly.)

It would be syntactic sugar for the syntax above which spares the
developers of repeating themselves. (just like <> does)

If the compiler would just use the information he is erasing to fill it
into the constructor. The only thing changing would be that the constructor
would take 1 additional hidden argument and since the argument is filled at
compile time it would not brake any code.

In case 2 we are in some generic context like a nested class or a generic
method. In both cases the actual type of T will be available. Such
situations might require multiple passes to solve all dependencies. Then we
can proceed like in 1.

In case 3 we are dealing with legacy code and the type is always object.
(Foo foo = new Foo(java.lang.Object))

In case 4 we are dealing with an open type bound. The type is not
constrained so we can only assume Object (Foo<?> foo = new
Foo<>(java.lang.Object))

In case 5 we have a upper constrained type bound. We can assume any Object
passing that bound Is a subclass of String (Foo<? extends String> foo = new
Foo<>(java.lang.String))

In case 6 we have a lower constrained type bound. The Object passing this
type bound could be Object. (Foo<? super String> foo = new
Foo<>(java.lang.Object))



We would store the types at constructor or Method Invocation as arguments
and then map them to the accessor T.class. This would allow type checking
of T at runtime by tracing the real type at compile time.

Since the type of T.class would be Class<T> or Class<?> we would not need
to create a method or a class for each different version of the method or
class like other languages do.

I’m not quite sure how reflection is implemented but I’m sure there is a
central invocation where the compiler could add the T.class argument. Of
course we would need a way for reflection to specify the type T for this to
work. We would simply need a method that is designed for generic classes
like “<T> Constructor<T> getGenericConstructor(Class<?>[]
typeParameterTypes, Class<?>… parameterTypes)” (could have a better
signature).

If the “getConstructor” is used on a generic class there would be a warning
and the parameters would be set to Object (resulting in a raw object like
any Objects generated by reflection currently are).



Risks:

It would require the addition of n fields to any generic class or method
where n stands for the number of generic arguments, which could result in
performance and memory issues when adding this many new fields to the heap
and stack. This issue could be reduced by only adding this mechanism when
T.class or instanceof T are actually used in the context and would be
skiped if the field is not used in the first place. Resulting in unchanged
field count in all class files if the feature is used nowhere.

Another rist would be the obvious change in class files, since the hidden
arguments need to be stored at compile time near the invoke of the
method/constructor.



This is just an idea based on my knowledge on generics in java.

Please feel free to correct any misconceptions in this idea and tell me if
I missed something.



Great regards

RedIODev
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/valhalla-dev/attachments/20221204/9163df1d/attachment.htm>


More information about the valhalla-dev mailing list