Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Java Reflection: Method and Constructor Invocation, Arrays, and Enums

Tech May 10 4

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() throws IllegalArgumentException for 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 on int[] arrays

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.