Java Reflection: Method and Constructor Invocation, Arrays, and Enums
Invoking Methods via Reflection
Reflection allows invoking methods on class instances when the type isn't known at compile time. Use java.lang.reflect.Method.invoke(). The first argument is the target object (or null for static methods), followed by method arguments. Exceptions thrown by the method are wrapped in InvocationTargetException; retrieve the original via getCause().
Finding and Invoking Specific Methods
The following example searches for public methods starting with "test", returning boolean, and accepting a single Locale parameter:
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Locale;
import static java.lang.System.out;
import static java.lang.System.err;
public class Deet {
private boolean testDeet(Locale l) {
out.format("Locale = %s, ISO Language Code = %s%n",
l.getDisplayName(), l.getISO3Language());
return true;
}
private int testFoo(Locale l) { return 0; }
private boolean testBar() { return true; }
public static void main(String... args) {
if (args.length != 4) {
err.format("Usage: java Deet <classname> <lang> <country> <variant>%n");
return;
}
try {
Class<?> clazz = Class.forName(args[0]);
Object instance = clazz.getDeclaredConstructor().newInstance();
for (Method method : clazz.getDeclaredMethods()) {
String name = method.getName();
if (!name.startsWith("test") ||
method.getReturnType() != boolean.class) {
continue;
}
Type[] paramTypes = method.getGenericParameterTypes();
if (paramTypes.length != 1 ||
!Locale.class.isAssignableFrom((Class<?>) paramTypes[0])) {
continue;
}
out.format("invoking %s()%n", name);
try {
method.setAccessible(true);
Object result = method.invoke(instance,
new Locale(args[1], args[2], args[3]));
out.format("%s() returned %b%n", name, (Boolean) result);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
err.format("invocation of %s failed: %s%n",
name, cause.getMessage());
}
}
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
}
Invoking Varargs Methods
Varargs methods except arrays as agruments. To invoke main(String[]):
import java.lang.reflect.Method;
import java.util.Arrays;
public class InvokeMain {
public static void main(String... args) {
try {
Class<?> targetClass = Class.forName(args[0]);
Method mainMethod = targetClass.getDeclaredMethod("main", String[].class);
String[] mainArgs = Arrays.copyOfRange(args, 1, args.length);
mainMethod.invoke(null, (Object) mainArgs);
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
}
Common Method Invocation Issues
NoSuchMethodException Due to Type Erasure
Generic type enformation is erased at runtime. Searching for lookup(Integer) fails because the actual signature uses Object:
public class MethodTrouble<T> {
public void lookup(T t) {}
public void find(Integer i) {}
public static void main(String... args) throws Exception {
Class<?> clazz = new MethodTrouble<Integer>(){}.getClass();
// Fails: MethodTrouble.lookup(java.lang.Integer)
Method m1 = clazz.getMethod("lookup", Integer.class);
// Works: MethodTrouble.lookup(java.lang.Object)
Method m2 = clazz.getMethod("lookup", Object.class);
}
}
IllegalAccessException for Inaccessible Methods
Private methods require setAccessible(true):
class AnotherClass {
private void m() {}
}
public class MethodTroubleAgain {
public static void main(String... args) throws Exception {
AnotherClass obj = new AnotherClass();
Method method = AnotherClass.class.getDeclaredMethod("m");
method.setAccessible(true); // Required
method.invoke(obj);
}
}
IllegalArgumentException with Varargs
Passing null or boxed arrays incorrectly causes errors:
public class MethodTroubleToo {
public void ping() { System.out.println("PONG!"); }
public static void main(String... args) throws Exception {
Method m = MethodTroubleToo.class.getMethod("ping");
switch (Integer.parseInt(args[0])) {
case 0: m.invoke(new MethodTroubleToo()); break; // OK
case 1: m.invoke(new MethodTroubleToo(), (Object[]) null); break; // OK
case 2: m.invoke(new MethodTroubleToo(), (Object) null); // Fails
case 3: m.invoke(new MethodTroubleToo(), new Object[0]); break; // OK
case 4: m.invoke(new MethodTroubleToo(), (Object) new Object[0]); // Fails
}
}
}
Working with Constructors
Use Constructor.newInstance() instead of Class.newInstance() for better exception handling and access to non-zero-arg constructors.
Finding Constructors
import java.lang.reflect.Constructor;
public class ConstructorSift {
public static void main(String... args) throws Exception {
Class<?> targetClass = Class.forName(args[0]);
Class<?> paramType = Class.forName(args[1]);
for (Constructor<?> ctor : targetClass.getDeclaredConstructors()) {
for (Class<?> param : ctor.getParameterTypes()) {
if (param.equals(paramType)) {
System.out.println(ctor.toGenericString());
break;
}
}
}
}
}
Creating Instances with Private Constructors
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class ConsoleCharset {
public static void main(String... args) throws Exception {
Constructor<?> ctor = java.io.Console.class.getDeclaredConstructor();
ctor.setAccessible(true);
java.io.Console console = (java.io.Console) ctor.newInstance();
Field charsetField = console.getClass().getDeclaredField("cs");
charsetField.setAccessible(true);
System.out.println("Console charset: " + charsetField.get(console));
}
}
Array Handling via Reflection
Use java.lang.reflect.Array for dynamic array operations.
Identifying Array Types
public class ArrayFind {
public static void main(String... args) throws Exception {
Class<?> clazz = Class.forName(args[0]);
for (java.lang.reflect.Field field : clazz.getDeclaredFields()) {
Class<?> type = field.getType();
if (type.isArray()) {
System.out.printf("Field: %s, Component: %s%n",
field.getName(), type.getComponentType());
}
}
}
}
Creating and Accessing Arrays
import java.lang.reflect.Array;
public class CreateMatrix {
public static void main(String... args) {
Object matrix = Array.newInstance(int.class, 2, 2);
Array.setInt(Array.get(matrix, 0), 0, 1);
Array.setInt(Array.get(matrix, 0), 1, 2);
Array.setInt(Array.get(matrix, 1), 0, 3);
Array.setInt(Array.get(matrix, 1), 1, 4);
int[][] mat = (int[][]) matrix;
for (int i = 0; i < 2; i++)
for (int j = 0; j < 2; j++)
System.out.printf("matrix[%d][%d] = %d%n", i, j, mat[i][j]);
}
}
Enum Reflection
Enums are classes with special reflection APIs.
Retrieving Enum Constants
public class EnumConstants {
public static void main(String... args) throws Exception {
Class<?> enumClass = (args.length == 0) ?
java.lang.annotation.RetentionPolicy.class : Class.forName(args[0]);
System.out.println("Constants: " +
java.util.Arrays.toString(enumClass.getEnumConstants()));
}
}
Setting Enum Fields
enum TraceLevel { OFF, LOW, MEDIUM, HIGH, DEBUG }
class MyServer {
private TraceLevel level = TraceLevel.OFF;
}
public class SetTrace {
public static void main(String... args) throws Exception {
MyServer server = new MyServer();
java.lang.reflect.Field field = MyServer.class.getDeclaredField("level");
field.setAccessible(true);
TraceLevel newLevel = TraceLevel.valueOf(args[0]);
field.set(server, newLevel);
System.out.println("New level: " + field.get(server));
}
}
Common Pitfalls
- Enum Instantiation:
Constructor.newInstance()throwsIllegalArgumentExceptionfor enums - Type Mismatch: Setting enum fields requires exact type compatibility
- Array Boxing: Primitive arrays can't accept boxed values via
Array.setInt() - Narrowing Conversions:
Array.setLong()fails onint[]arrays