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.
Java get generic type of collection
Your approach is fine. It should work for both List<String>
and Collection<String>
. For example see this sample code:
public class Demo {
List<String> list = new ArrayList<>();
Collection<String> coll = new ArrayList<>();
public static void main(String args[]){
Class<Demo> clazz = Demo.class;
Field[] fields = clazz.getDeclaredFields();
for (Field field: fields) {
Type type = field.getGenericType();
if (type instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType)type;
Type[] arr = pType.getActualTypeArguments();
for (Type tp: arr) {
Class<?> clzz = (Class<?>)tp;
System.out.println(clzz.getName());
}
}
}
}
}
this prints out:
java.lang.String
java.lang.String
How do you get List<Type> with reflection?
Type listType = method.getGenericParameterTypes()[0];
if (listType instanceof ParameterizedType) {
Type elementType = ((ParameterizedType) listType).getActualTypeArguments()[0];
}
Note that the element type needn't be an actual Class
like String
-- it could be a type variable, a wildcarded type, etc.
You can read more about scraping generics info from reflected items here and here.
Why is this generic type information for member field not erased in Java?
Your Questions
1. Is this behavior formally defined in the JLS/JVMS spec (if it is, where?), or up to different vendors implementing the language?
The Java Language Specification appears to specifically not describe reflection:
Consequently, this specification does not describe reflection in any detail.
But instead leaves the full behavior of reflection to be documented by the API (i.e. in the Javadoc).
However, the Java Virtual Machine Specification does explain that generic information must be emitted by a compiler:
4.7.9. The
Signature
AttributeThe
Signature
attribute is a fixed-length attribute in the attributes table of aClassFile
,field_info
, ormethod_info
structure (§4.1, §4.5, §4.6). ASignature
attribute records a signature (§4.7.9.1) for a class, interface, constructor, method, or field whose declaration in the Java programming language uses type variables or parameterized types. See The Java Language Specification, Java SE 15 Edition for details about these constructs.[...]
4.7.9.1. Signatures
Signatures encode declarations written in the Java programming language that use types outside the type system of the Java Virtual Machine. They support reflection and debugging, as well as compilation when only
class
files are available.A Java compiler must emit a signature for any class, interface, constructor, method, or field whose declaration uses type variables or parameterized types. Specifically, a Java compiler must emit:
A class signature for any class or interface declaration which is either generic, or has a parameterized type as a superclass or superinterface, or both.
A method signature for any method or constructor declaration which is either generic, or has a type variable or parameterized type as the return type or a formal parameter type, or has a type variable in a
throws
clause, or any combination thereof.If the
throws
clause of a method or constructor declaration does not involve type variables, then a compiler may treat the declaration as having nothrows
clause for the purpose of emitting a method signature.A field signature for any field, formal parameter, or local variable declaration whose type uses a type variable or a parameterized type.
[...]
2. Is it possible to apply the reflection approach to the local variable foo
to get something like Foo<java.lang.String>
?
No, because local variables are not reflectively accessible. At least not directly by the Java Language. But let's say they were. You have:
Foo foo = new Foo<String>();
What would be reflected is the left-hand-side. That is a raw type, so all you would know is that the type of foo
is Foo
. You would not be able to tell that the instance created by the right-hand-side was parameterized with String
.
Some Clarification (Hopefully)
When we say "generics are erased at run-time" we don't mean in this context. The statically defined type of reflectively accessible constructs, such as fields, is saved in the byte-code. For example, the following:
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.List;
public class Main {
private static List<? extends Number> list = new ArrayList<Integer>();
public static void main(String[] args) throws Exception {
Field field = Main.class.getDeclaredField("list");
// Due to List being a generic type the returned Type is actually
// an instance of java.lang.reflect.ParameterizedType
Type genericType = field.getGenericType();
System.out.println("Generic Type = " + genericType);
// The raw type can be gotten from the ParameterizedType. Here the
// returned Type will actually be an instance of java.lang.Class
Type rawType = ((ParameterizedType) genericType).getRawType();
System.out.println("Raw Type = " + rawType);
// The ParameterizedType gives us access to the actual type
// arguments declared. Also, since a bounded wildcard was used
// the returned Type is actually an instance of
// java.lang.reflect.WildcardType
Type typeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0];
System.out.println("Type Argument = " + typeArgument);
// We know in this case that there is a single upper bound. Here
// the returned Type will actually be an instance of java.lang.Class
Type upperBound = ((WildcardType) typeArgument).getUpperBounds()[0];
System.out.println("Upper Bound = " + upperBound);
}
}
Will output:
Generic Type = java.util.List<? extends java.lang.Number>
Raw Type = interface java.util.List
Type Argument = ? extends java.lang.Number
Upper Bound = class java.lang.Number
All that information is there in the source code. Note that we are reflectively looking at the list
field. We are not looking at an instance (i.e. a run-time object) referenced by said field. Knowing the generic type of the field is really no different than knowing that the field's name is list
.
What we don't know is that the ArrayList
was parameterized with Integer
. Changing the above to:
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.List;
public class Main {
private static List<? extends Number> list = new ArrayList<Integer>();
public static void main(String[] args) {
Class<?> clazz = list.getClass();
System.out.println("Class = " + clazz);
TypeVariable<?> typeParameter = clazz.getTypeParameters()[0];
System.out.println("Type Parameter = " + typeParameter);
}
}
Outputs:
Class = class java.util.ArrayList
Type Parameter = E
We can see we know that the instance referenced by list
is an instance of java.util.ArrayList
. But from there all we can determine is that the ArrayList
class is generic and has a single type parameter E
. We have no way of determining that the list
field was assigned an ArrayList
with a type argument of Integer
. In other words, the ArrayList
instance itself does not know what type of elements it's declared to contain—that information has been erased.
To put it another way, the list
field's type is known at run-time but the ArrayList
instance (i.e the object created at run-time) only knows it's an ArrayList
.
Get generic type of Collection without checking an element out
No. Generics are a tool for making type-checking more useful at compilation. They don't exist at runtime.
At runtime, a Collection<whatever> is nothing else than a Collection without further information. Only its content describes what it contains.
How to get the simple generic type name?
You need to write simple class which recursively traverse type and generate simple name on each level:
class TypeSimpleName {
private final Type value;
public TypeSimpleName(Type value) {
this.value = value;
}
public String getName() {
return getName(value);
}
private String getName(Type type) {
if (type instanceof ParameterizedType) {
return getParameterizedTypeName((ParameterizedType) type);
}
if (type instanceof Class) {
return getClassSimpleName(type);
}
// handle other types if needed
return type.getTypeName();
}
private String getParameterizedTypeName(ParameterizedType type) {
StringBuilder builder = new StringBuilder();
builder.append(getName(type.getRawType()));
Type[] typeArguments = type.getActualTypeArguments();
if (typeArguments.length > 0) {
builder.append("<");
for (int i = 0; i < typeArguments.length; i++) {
Type arg = typeArguments[i];
builder.append(getName(arg));
if (i < typeArguments.length - 1) {
builder.append(", ");
}
}
builder.append(">");
}
return builder.toString();
}
private String getClassSimpleName(Type type) {
return ((Class) type).getSimpleName();
}
@Override
public String toString() {
return getName();
}
}
How to use it:
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.springframework.util.ReflectionUtils;
public class Test {
public static void main(String[] args) {
System.out.println(getReturnTypeName("getList"));
System.out.println(getReturnTypeName("getListList"));
System.out.println(getReturnTypeName("getObj"));
System.out.println(getReturnTypeName("getMapMap"));
}
private static String getReturnTypeName(String method) {
Type returnType = ReflectionUtils.findMethod(Test.class, method).getGenericReturnType();
return new TypeSimpleName(returnType).getName();
}
public List<String> getList() {
return null;
}
public List<List<String>> getListList() {
return null;
}
public Integer getObj() {
return 1;
}
public Map<String, Map<Integer, BigDecimal>> getMapMap() {
return null;
}
}
Above code prints:
List<String>
List<List<String>>
Integer
Map<String, Map<Integer, BigDecimal>>
Related Topics
Jtable Design to Synchronize With Back-End Data-Structure
How to Set the Java.Library.Path from Eclipse
Servlet For Serving Static Content
How to Prevent Java.Lang.Numberformatexception: For Input String: "N/A"
MySQL Jdbc Driver 5.1.33 - Time Zone Issue
How to Get a Path to a Resource in a Java Jar File
What Is the Ellipsis (...) For in This Method Signature
How to Convert Number to Words in Java
Are Static Methods Inherited in Java
How to Extract Numbers from a String and Get an Array of Ints
Understanding Java'S Protected Modifier
Should Java 8 Getters Return Optional Type
How to Do Url Decoding in Java
Java.Util.Date VS Java.Sql.Date
Program Not Accessing Method Paintcomponent() of Extended Jpanel Class
Wait For Page Load in Selenium