Introduction
The tip and the code inside will help a beginner who is trying to learn reflection understand the importance of reflection.
Background
Reflection is to load the classes and use their functionality by simple named loading and calling. Reflection is used in most of the frameworks for IoC. The reader should have basic knowledge about Java syntax and class libraries.
Using the Code
The code in the tip can be used to load any of the variables, methods or constructors and access them. We will be taking a look at each of them individually:
- Loading a class
- Accessing the constructors
- Accessing the fields
- Accessing the methods
- Accessing the annotations
Loading a Class
For loading a class, a static
method forName(String fullySpecifiedName)
of the "Class" class should be used. This method loads the class specified from the class path of the executing code and throws a ClassNotFoundException
in case the class was not found in the class path. If you are familiar with the type 4 JDBC calls, you must have used this method in order to load the drivers. I think after reading this tip, it will give you some idea about how the loaded driver is used in database connectivity.
try{
String fullySpecifiedName = "reflections.TargetedForReflection"
Class<? extends Object> c = Class.forName(fullySpecifiedName?);
System.out.println("Canonical name for the class is: " + c.getCanonicalName());
System.out.println("Super class for the class is: " + c.getSuperclass());
} catch(ClassNotFoundException cnfe){
cnfe.printStackTrace();
}
Accessing the Constructors
The constructors' information of the loaded class or type can be loaded and any of the constructors can be used to create an instance of the class. An array of Constructor class can be retrieved from the class instance using the getConstructors()
method. All the constructors can be loaded of the loaded type as follows (The above class loader code continues):
Constructor<?>[] availableConstructors = c.getConstructors();
if(availableConstructors != null && availableConstructors.length > 0){
System.out.println("***Constructor information***");
for(Constructor<?> ctr : availableConstructors){
try {
System.out.println("Initiating the object...");
Class<?>[] parameters = ctr.getParameterTypes();
ci = ctr.newInstance(new Object[0]);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
Accessing the Fields
We can access the fields of the loaded class. The getFields()
of Class returns an array of Field which is public
. We can also access the private
fields of the class instance but in that case we need to know the name of the field in prior. We will see that too in the code snippet.
Field[] fields = c.getFields();
if(fields != null && fields.length > 0){
System.out.println("***Printing the fields***");
for(Field f : fields){
System.out.println(f.getName());
int modifierInformation = f.getModifiers();
System.out.println("The modification code is: "+modifierInformation);
}
}
else{
System.out.println("***There are no public fields****");
System.out.println("Trying to access the private field...");
try {
Field privateField = c.getDeclaredField("categoryId");
privateField.setAccessible(true);
System.out.println("Got access to: "+privateField.getName());
privateField.set(ci, 56);
System.out.println(privateField.get(ci));
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
}
Accessing the Methods
We can access the public
methods of the class from the method getMethods()
. This method returns all the methods of the classes which are public
. The above method will return an array of Method. It will return an array of zero length in case there are no public
methods or in case of interface. For invoking a method from the reflection, we need to provide an instance of the class and also the parameters of the method required as Object[]
. If we know the above mentioned parameters in advance for the invoke
method, we can invoke the function and also get the return value of the function if any.
Method[] methods = c.getMethods();
if(methods != null && methods.length > 0){
System.out.println("\t-> " +
pt.getCanonicalName());for(Method m : methods){
System.out.println("---Method information---");
System.out.println("Method Name: "+m.getName());
System.out.println("Method's return type: "+m.getReturnType().toString());
Class<? extends Object>[] paramTypes = m.getParameterTypes();
Object[] params = new Object[0];
if(paramTypes != null && paramTypes.length > 0){
params = new Object[paramTypes.length];
System.out.println("Parameters of the methods =>");
int paramIndex = 0;
for(Class<? extends Object> pt : paramTypes){
params[paramIndex++] = generateParameter(pt);
}
}
try {
m.invoke(ci, params);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
Accessing the Annotations
Annotation of any class or method or field can be loaded and appropriate actions can be taken. Its importance can be appreciated in case you want to make a field which is not serializable. Can you do it with the use of annotation? The annotations can be accessed as mentioned below:
Annotation[] annotations = c.getAnnotations();
if(annotations != null && annotations.length > 0){
for(Annotation a: annotations){
System.out.println("--------------Annotation information-------------");
System.out.println("Annotation name: " + a.toString());
System.out.println("------------------------------");
}
}
Points of Interest
From the above tip, we can understand how the dependency injection or object explorer or some discovery logic works. We can think of intellisense working for some IDE. Though the intellisense does not work in the same way, we can get a feeling from it.