Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

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

Tech May 10 34

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

SBUS Signal Analysis and Communication Implementation Using STM32 with Fus Remote Controller

Overview In a recent project, I utilized the SBUS protocol with the Fus remote controller to control a vehicle's basic operations, including movement, lights, and mode switching. This article is aimed...

Comprehensive Guide to Hive SQL Syntax and Operations

This article provides a detailed walkthrough of Hive SQL, categorizing its features and syntax for practical use. Hive SQL is segmented into the following categories: DDL Statements: Operations on...

Understanding the MP4 File Format and Analysis

Table of Contents Overview Fundamentals of the MP4 Format Key Concepts of the Container Format Box Structure Track Samples Sample Tables Chunks Detailed Explanation of Core Boxes Additional Boxes...

Leave a Comment

Anonymous

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