Where Are Generic Types Stored in Java Class Files

Where are generic types stored in java class files?

They are stored in Signature attributes; see section 4.8.8 of the updated Java Virtual Machine Specification, as well as section 4.4.4 for the format of the field type signature.

Here's an example using javap -verbose java.util.Map:

public interface java.util.Map
SourceFile: "Map.java"
Signature: length = 0x2
00 1E
[other attributes omitted]

The Signature attribute here specifies (if you read this as big-endian, like all integer quantities in the JVM class file format are) constant pool value #30 (30 = 0x1E). So let's have a look there:

const #30 = Asciz       <K:Ljava/lang/Object;V:Ljava/lang/Object;>Ljava/lang/Object;;

Read this in the context of the grammar specified in 4.4.4. So, this uses two type parameters, K extends java.lang.Object and V extends java.lang.Object. The type itself (Map) also extends class java.lang.Object, and no interfaces.

Displaying generic type parameters from compiled class files

A decompiler such as http://java.decompiler.free.fr/ will give you an approximation to the original source code, including the generic signatures.

How do I get a class instance of generic type T?

The short answer is, that there is no way to find out the runtime type of generic type parameters in Java. I suggest reading the chapter about type erasure in the Java Tutorial for more details.

A popular solution to this is to pass the Class of the type parameter into the constructor of the generic type, e.g.

class Foo<T> {
final Class<T> typeParameterClass;

public Foo(Class<T> typeParameterClass) {
this.typeParameterClass = typeParameterClass;
}

public void bar() {
// you can access the typeParameterClass here and do whatever you like
}
}

Java Generics : Is any meta information about the generic type preserved at runtime as well?

Of course the information that a class is generic is supported.

In other words: when you decompile ArrayList.class you will find hints about the fact that this class allows for one generic type parameter. In other words: class files contain meta information. And using reflection it is possible to inspect this meta information at runtime.

But when you have another class that uses some List<Integer> object - then you do not find information about that "list uses an Integer" in the compiled class - unless you use some specific patterns, as outlined here for example.

So the answer is basically: for almost all use cases of practical relevance, "generics" are compile time only.

Example:

public class GenericsExample<T> {
private T member;
public T foo(T bar) {
return member;
}
}

Now run: javap -p -c GenericsExample

Compiled from "GenericsExample.java"
public class GenericsExample<T> {
private T member;

public GenericsExample();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return

public T foo(T);
Code:
0: aload_0
1: getfield #2 // Field member:Ljava/lang/Object;
4: areturn
}

As you can see the decompiler understands that the class uses that generic type T. For more details see here or there.

Get Class-object representation of generic parameter in Java

The reason why

Class<T> persistentClass = (Class<T>)
((ParameterizedType)getClass().getGenericSuperclass())
.getActualTypeArguments()[0];

works was because the superclass of this happens to be a class with a parameterised type as its superclass. Therefore you can get the actual type arguments of that type. The type parameters of superclasses are stored as metadata in the class file if you write them in the source file.

However, in your case, whatever is passed to the onCatch parameter is not going to have a superclass of Consumer<T>. After all, Consumer<T> is not a class! You need to use getGenericInterfaces and find the one that has the name that starts with java.util.function.Consumer.

System.out.println(
// I've assumed the Consumer interface is the first one, to keep it brief
((ParameterizedType)onCatch.getClass().getGenericInterfaces()[0])
.getActualTypeArguments()[0]
);

This would work if the caller calls onCatch like this:

onCatch(new Consumer<RuntimeException>() {
@Override
public void accept(RuntimeException e) {

}
});

The anonymous class will implement Consumer<RuntimeException>, and this information will be written to the class file representing the anonymous class.

However, if you use a lambda:

onCatch((RuntimeException e) -> {});

Then only a method like this is generated in the same class as the caller:

private static void lambda$caller$0(RuntimeException e) {

}

and at runtime, invokedynamic is used to create an instance that implements Consumer, and this is the bad news: the type parameter RuntimeException is not part of the generated class for this instance, for whatever reason.

The only way you can find RuntimeException now then, is if you somehow know who the caller is, and find the lambda$caller$0 method, and look at its parameter.

That said, everything I've wrote so far is pretty much all implementation detail, and I wouldn't use any of that in production code. I would say you should just add a Class<E> parameter:

onCatch(RuntimeException.class, e -> {});

It doesn't look that different on the caller's side anyway.

Get generic type of java.util.List

If those are actually fields of a certain class, then you can get them with a little help of reflection:

package test;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;

public class Test {

List<String> stringList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();

public static void main(String... args) throws Exception {
Field stringListField = Test.class.getDeclaredField("stringList");
ParameterizedType stringListType = (ParameterizedType) stringListField.getGenericType();
Class<?> stringListClass = (Class<?>) stringListType.getActualTypeArguments()[0];
System.out.println(stringListClass); // class java.lang.String.

Field integerListField = Test.class.getDeclaredField("integerList");
ParameterizedType integerListType = (ParameterizedType) integerListField.getGenericType();
Class<?> integerListClass = (Class<?>) integerListType.getActualTypeArguments()[0];
System.out.println(integerListClass); // class java.lang.Integer.
}
}

You can also do that for parameter types and return type of methods.

But if they're inside the same scope of the class/method where you need to know about them, then there's no point of knowing them, because you already have declared them yourself.

How does java compiler know of generics type information of a code in jar?

Type erasure does not erase all information related to generics. Simply put, signature of the method in question (public void check(List<String> p)) is preserved in full detail.
The preserved information can be obtained at run-time with reflection API (like getGenericParameterTypes(), getTypeParameters() & others methods).



Related Topics



Leave a reply



Submit