From the Reflection (computer programming)
In computer science,
reflection is the ability of a computer program to examine (see type introspection) and modify the structure and behavior (specifically the values, meta-data, properties and functions) of the program at run time.
Reflection can be used for observing and/or modifying program execution at run time. A reflection-oriented program component can monitor the execution of an enclosure of code and can modify itself according to a desired goal related to that enclosure. This is typically accomplished by dynamically assigning program code at run time.
In object oriented programming languages such as Java, reflection allows inspection of classes, interfaces, fields and methods at run time without knowing the names of the interfaces, fields, methods at compile time. It also allows instances of new objects and invocation of methods.
Reflection can also be used to adapt a given program to different situations dynamically. For example, consider an application that uses two different classes X and Y interchangeably to perform similar operations. Without reflection-oriented programming, the application might be hard-coded to call method names of class X and class Y. However, using the reflection-oriented programming paradigm, the application could be designed and written to utilize reflection in order to invoke methods in classes X and Y without hard-coding method names. Reflection-oriented programming almost always requires additional knowledge, framework, relational mapping, and object relevance in order to take advantage of more generic code execution. Hard-coding can be avoided to the extent that reflection-oriented programming is used.
Reflection is often used as part of software testing, such as for the runtime creation/instantiation of mock objects.
Reflection is also a key strategy for metaprogramming.
In some object-oriented programming languages, such as C# and Java, reflection can be used to override member accessibility rules. For example, reflection makes it possible to change the value of a field marked "private" in a third-party library's class.
This tutorial is meant to give you a basic idea of how to use reflection, not everything will be covered. Use the Java docs for further knowledge.
Alright, let us begin.
Fully Qualified Classes
As reflection is used to provide runtime-access to classes, you cannot use import statements (which are only used at compile-time). Using the Class in the Reflection API, you are capable of loading any class that is fully-qualified.
what does fully-qualified mean?
Code:
Class<?> cls = Class.forName("java.lang.Thread");
This piece of code should have no trouble acquiring the Class Thread in the package java.lang, because the class is known by default to the ClassLoader. When you run a java program, all the java core classes are added to the class path for the ClassLoader to launch in and it starts looking in that path for classes. If you have a JAR file containing a class you want to access in your software, if you do not specify the class path to the jar file, the classes will be completely ignored by the ClassLoader and you will not be capable of detecting them.
you can specify the class paths using
Code:
java -cp MyJar.jar; main.MyMainClass
Using the -cp argument is basically telling the ClassLoader, "hey, i want you to search in this library for classes as well."
Class
Code:
public class Person {
private String name;
private int age;
public Person() {
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Accessing the class regularly, all you would do is:
Code:
Person p = new Person();
p.setName("bob");
System.out.print("My name is " + p.getName());
With reflection, it's a different approach.
Our small task is to make an object of Person, setName to bob, then print out the name using reflection. First of all, you would have to load the class.
Code:
Class<?> cls = Class.forName("Person");
Now that the class is saved to cls, instantiate it.
Code:
Object person = cls.newInstance();
Additional Information:
- If you want to access an inner class, you should use the "$" Modifier between the public class and the inner class
Code:
Class<?> cls = Class.forName("Person$SocialLife"); //where SocialLife is an inner class.
Now that a new instance has been acquired, we are free to start invoking methods and fields.
method
In Person, there are 2 public methods :
After the Person class was instantiated, it's feasible to acquire the methods now.
Code:
Method setName = person.getClass().getDeclaredMethod("setName", String.class);
Method getName = person.getClass().getDeclaredMethod("getName");
Method setAge = person.getClass().getDeclaredMethod("setAge", int.class);
Method getAge = person.getClass().getDeclaredMethod("getAge");
setName and setAge take a second argument while getName and getAge do not. Why so? The getDeclaredMethod() method takes varargs after the first argument that will indicate that the method that is being acquired takes the specified type as a parameter. Hence why setName(String) and setAge(int) take String.class and int.class respectively as arguments.
Methods have been declared, what's left? oh yeah, to invoke them.
Code:
setName.invoke(person, "bob");
setAge.invoke(person, 17);
Using the invoke() method, the first argument has to be the object you want this method to invoke on. Other arguments will be counted as values for the specified method's arguments. The preceding code is equivalent to the proceeding code:
Code:
Person person = new Person();
person.setName("bob");
person.setAge(17);
After specifying the name and age of the person object, we are free to get them using the getName and getAge methods.
Code:
String name = (String) getName.invoke(person);
int age = (int) getAge.invoke(person);
Method return type is unknown at compile time, so you have to cast it yourself as shown above.
Finally, just print the values acquired.
Code:
System.out.println("Name: " + name + ". Age: " + age);
Additional Information:
- if you have a method that returns a class such as person.getAchievements().physicalAchievements(), when invoking getAchievements, assign the value to an object.
Code:
Object person = Class.forName("Person").newInstance();
Method getAchievements = person.getClass().getDeclaredMethod("getAchievements");
Object achievements = (Object) getAchievements.invoke(person);
Method physicalAchievements = achievements.getClass().getDeclaredMethod("physicalAchievements");
// and so on...
- Do not overlook Integer.class as int.class, they are DIFFERENT TYPES. If you specify Integer.class for getAge(int), you will get an exception.
- If a method is private, you are required to grab it using getDeclaredMethod() and not getMethod(). Always use getDeclaredMethod as it will put you on the safe side.
Code:
public class PrivateAccessTest {
public static void main(String... args) {
Object object = Class.forName("PrivateAccessTest").newInstance();
Method sayHiPrivatelyIncorrect = object.getClass().getMethod("sayHiPrivately"); // this is incorrect and will throw NoSuchMethodException
Method sayHiPrivatelyCorrect = object.getClass().getDeclaredMethod("sayHiPrivately"); // this will detect the private method
}
private void sayHiPrivately() {
System.out.println("Hi");
}
}
Field
A Field provides information about, and dynamic access to, a single field of a class or an interface. The reflected field may be a class (static) field or an instance field.
Code:
Object person = Class.forName("Person").newInstance();
Field age = person.getClass().getDeclaredField("age");
Since variable age is private, you cannot utilize it unless you make it accessible first.
Note: if the variable is public, this step is redundant and may be avoided.
Code:
age.setAccessible(true);
You're good to go now, using the get() method to obtain the value of the variable.
Code:
int value = (int) age.get(person);
System.out.println("Age: " + value);
Additional Information:
- If you wish to get an array of class instances such as PlayerHandler.players[], you should do
Code:
Object playerHandler = Class.forName("server.model.players.PlayerHandler").newInstance();
Field players = playerHandler.getClass().getField("players");
Object[] values = (Object[]) players.get(playerHandler);
- If a field is private, you are required to grab it using getDeclaredField() and not getField(). Always use getDeclaredField as it will put you on the safe side.
Code:
public class PrivateAccessTest {
private String myPrivateName = "bob";
public static void main(String... args) {
Object object = Class.forName("PrivateAccessTest").newInstance();
Field myPrivateNameIncorrect = object.getClass().getField("myPrivateName"); // this is incorrect and will throw NoSuchFieldException
Field myPrivateNameCorrect = object.getClass().getDeclaredField("myPrivateName"); // this is correct and it will detect the private field
}
}
Applications
As a conclusion, the proceeding classes will demonstrate usage in actual applications such as RSPS.
General Application
Code:
/*
This Code is ready to run, put it in your favorite IDE, compile and try it out!
*/
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Scanner;
public class ReflectionApplicationGeneral {
public static void main(String[] args) {
boolean runAgain = true;
try {
while (runAgain) {
Scanner scanner = new Scanner(System.in);
Object person = Class.forName("ReflectionApplicationGeneral$Person").newInstance();
Method setName = person.getClass().getDeclaredMethod("setName", String.class);
Method setAge = person.getClass().getDeclaredMethod("setAge", int.class);
Method getName = person.getClass().getDeclaredMethod("getName");
Method getAge = person.getClass().getDeclaredMethod("getAge");
System.out.print("Enter a name: ");
String answer = scanner.nextLine();
setName.invoke(person, answer);
System.out.print("Enter an age: ");
answer = scanner.nextLine();
setAge.invoke(person, Integer.valueOf(answer));
System.out.println("My name is " + getName.invoke(person) + ", and I am " +
getAge.invoke(person) + " years old.");
Field age = person.getClass().getDeclaredField("age");
System.out.print("Make age accessible? (if you want to see what happens!) (y/n)");
answer = scanner.nextLine();
age.setAccessible(answer.equalsIgnoreCase("y"));
Field name = person.getClass().getDeclaredField("name");
System.out.print("Make name accessible? (if you want to see what happens!) (y/n)");
answer = scanner.nextLine();
name.setAccessible(answer.equalsIgnoreCase("y"));
System.out.print("Age: ");
System.out.println(age.get(person));
System.out.print("Name: ");
System.out.println(name.get(person));
System.out.println("Want to go again? (y/n)");
answer = scanner.nextLine();
runAgain = answer.equalsIgnoreCase("y");
}
} catch (NoSuchMethodException e) {
System.out.println("method does not exist!");
} catch (IllegalAccessException e) {
System.out.println("Access prohibited!");
} catch (InvocationTargetException e) {
System.out.println("Invocation error!");
} catch (ClassNotFoundException e) {
System.out.println("Class specified does not exist!");
} catch (InstantiationException e) {
System.out.println("Error instantiating.");
} catch (NoSuchFieldException e) {
System.out.println("No Such field!");
}
}
static class Person {
private String name;
private int age;
public Person() {
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
}
RSPS Application
Code:
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Scanner;
public class PlayerMessager {
public static void main(String... args) {
try {
Scanner scanner = new Scanner(System.in);
System.out.print("Enter an online player name: ");
String answer = scanner.nextLine();
Object playerHandler = Class.forName("server.model.players.PlayerHandler");
Field field = playerHandler.getClass().getDeclaredField("players");
Object[] players = (Object[]) field.get(playerHandler);
for (int i = 0; i < players.length; i++) {
Object c = players[i];
if (c != null) {
Field playerName = c.getClass().getDeclaredField("playerName");
if (playerName.get(c).toString().equalsIgnoreCase(answer)) {
System.out.println(answer + " is online, what do you wish to tell them ?");
answer = scanner.nextLine();
Method sendMessage = c.getClass().getDeclaredMethod("sendMessage", String.class);
sendMessage.invoke(c, answer);
System.out.println("Sent the message successfully");
return;
}
}
}
System.out.println("could not find player");
} catch (NoSuchFieldException e) {
System.out.println("Field not found.");
} catch (IllegalAccessException e) {
System.out.println("Illegal Access.");
} catch (InvocationTargetException e) {
System.out.println("Invocation Target Exception.");
} catch (NoSuchMethodException e) {
System.out.println("Method not found.");
} catch (ClassNotFoundException e) {
System.out.println("Class not found.");
}
}
}
Authored by Shed
Revised by Major