Mini Tutorial #02 – Java Reflection Basics

Reflection is a useful feature in Java that allows you to programmatically navigate and manipulate an object. Java frameworks created for Object Relational Mapping (Hibernate, etc..) and Testing (JUnit, TestNG) utilizes reflection. Also, you can use reflection to simply access private member values and methods. (Sometimes required when you are dealing with third party code).

What is the Approach for Learning Reflection?

We are going to learn how to build a simple unit testing framework from scratch. This will cover the necessary basics.

What should a unit testing framework do?
1) Create an instance of a test class.
2) Get all the test methods.
3) Execute and report results.

Also, we do not need to worry about complex implementations such as data providers, before class or before test methods and XML reporting for this exercise. Therefore, this stripped-down unit testing framework is a good real world example.

Reflection: Annotation Interface

First of all, let’s create an annotation interface to use as the marker for test methods. Retention policy can be used to tell the compiler that we need to access the meta-data at run-time.¬† Target annotation is used to denote that this annotation is only for methods.

package com.pandabunnytech.unittester;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// Preserve even at runtime so we can access using reflection
@Retention(RetentionPolicy.RUNTIME)
// This will be used to annotate methods
@Target(ElementType.METHOD)
public @interface Test {
}

Creating a Sample Test Class

Now, let’s create a sample test class. Below test is created in a way that one of the test cases passes and another will fail. Can you guess which one will fail ?

package com.pandabunnytech.unittester;

public class SampleTest {
    @Test
    public void fivePlusFiveIsTen() {
        int a = 5;
        int b = 5;
        int c = a + b;
        assert a + b == c;
    }

    @Test
    public void divisibility() {
        int a = 0;
        int b = 0;
        int c = a / b;
        assert c != 0;
    }
}

Implementing The Tester Class

Furthermore, tester class is responsible for executing a given test class. Therefore, It will have a main() method which will accept a full qualified class name to be executed.

High-Level Overview of Tester

First of all, tester will check the arguments. Then, if there is only one argument given it will attempt to execute the class. Otherwise, it will print usage guide and exit.

    public static void main(String[] args) {
        if (args.length != 1) {
            printHelp();
            System.exit(-1);
        }
        executeTestClass(args[0]);
    }

    private static void printHelp() {
        System.err.println("Please use this class as following:");
        System.err.println("java com.pandabunnytech." +
                "unittester.Tester my.unittest.TestClass");
    }

    private static void executeTestClass(String className) {
        Class testClass = findClass(className);
        Object testClassObject = instantiate(testClass);
        runTests(testClass, testClassObject);
    }

Finding a Class Dynamically

Also, if you need to instantiate a class. You need to find it. Now, let’s try to find the class that we need to instantiate.

    private static Class findClass(String className) {
        Class classObject = null;
        try {
            classObject = Class.forName(className);
        } catch (ClassNotFoundException e) {
            System.err.println("Cannot locate the given classname");
            System.exit(-1);
        }
        return classObject;
    }

Using Reflection to Instantiate a Class

Additionally, a class object can be used to create a new instance of a class. Therefore, this is useful if you are planning to call it’s member methods.

    private static Object instantiate(Class classObject) {
        Object object = null;
        try {
            object = classObject.newInstance();
        } catch (InstantiationException e) {
            System.err.println("Cannot create the given classname");
            System.exit(-1);
        } catch (IllegalAccessException e) {
            System.err.println("Cannot access the given classname");
            System.exit(-1);
        }
        return object;
    }

A Reflective Test Runner

Also, here you can see how the reflection is used to access methods and invoke them dynamically.

    private static void runTests(Class testClass, Object testClassObject) {
        // Access the methods of the testClass
        Method[] methods = testClass.getMethods();
        int total = 0;
        int passed = 0;
        for (Method method : methods) {
            // See if we can get the 'Test' annotation
            Test test = method.getAnnotation(Test.class);

            // If test is null it is not a test
            if (test == null) {
                continue;
            }

            total += 1;
            try {
                method.invoke(testClassObject);
            } catch (IllegalAccessException e) {
                // Cannot access the method
                System.err.printf("Cannot access method '%s'" +
                        " for invocation%n", method.getName());
                continue;
            } catch (InvocationTargetException e) {
                // Error occurred when executing the test method
                System.err.printf("Exception occurred on" +
                        " test execution of '%s'%n", method.getName());
                System.err.println("Error::");
                e.getCause().printStackTrace();
                continue;
            }

            passed += 1;
        }

        System.out.printf("Total of %d / %d Passed%n", passed, total);
    }

Final Product

Finally, here is the full source code for the tester class.

package com.pandabunnytech.unittester;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Tester {
    public static void main(String[] args) {
        if (args.length != 1) {
            printHelp();
            System.exit(-1);
        }
        executeTestClass(args[0]);
    }

    private static void printHelp() {
        System.err.println("Please use this class as following:");
        System.err.println("java com.pandabunnytech." +
                "unittester.Tester my.unittest.TestClass");
    }

    private static void executeTestClass(String className) {
        Class testClass = findClass(className);
        Object testClassObject = instantiate(testClass);
        runTests(testClass, testClassObject);
    }

    private static Class findClass(String className) {
        Class classObject = null;
        try {
            classObject = Class.forName(className);
        } catch (ClassNotFoundException e) {
            System.err.println("Cannot locate the given classname");
            System.exit(-1);
        }
        return classObject;
    }

    private static Object instantiate(Class classObject) {
        Object object = null;
        try {
            object = classObject.newInstance();
        } catch (InstantiationException e) {
            System.err.println("Cannot create the given classname");
            System.exit(-1);
        } catch (IllegalAccessException e) {
            System.err.println("Cannot access the given classname");
            System.exit(-1);
        }
        return object;
    }

    private static void runTests(Class testClass, Object testClassObject) {
        // Access the methods of the testClass
        Method[] methods = testClass.getMethods();
        int total = 0;
        int passed = 0;
        for (Method method : methods) {
            // See if we can get the 'Test' annotation
            Test test = method.getAnnotation(Test.class);

            // If test is null it is not a test
            if (test == null) {
                continue;
            }

            total += 1;
            try {
                method.invoke(testClassObject);
            } catch (IllegalAccessException e) {
                // Cannot access the method
                System.err.printf("Cannot access method '%s'" +
                        " for invocation%n", method.getName());
                continue;
            } catch (InvocationTargetException e) {
                // Error occurred when executing the test method
                System.err.printf("Exception occurred on" +
                        " test execution of '%s'%n", method.getName());
                System.err.println("Error::");
                e.getCause().printStackTrace();
                continue;
            }

            passed += 1;
        }

        System.out.printf("Total of %d / %d Passed%n", passed, total);
    }
}

That’s it for this tutorial. Want to take it easy? Well, we’ve got a whole bunch of Python tutorials for beginners! We’ve also got more tutorials on using Irfan View to crop and resize images. Don’t forget to check out our pick of the latest trending tech news while you’re at it!

Like what you see? Subscribe to our email list and hit us with a like on our Facebook page to get the latest news updates and tutorials straight to your newsfeed!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.