Java Reflection

Reflection is an API which is used to examine or modify the behavior of methods, classes, interfaces at runtime. It is provided by the java.lang.reflect package and elements in Class. For example by using reflection you can determine what methods, constructors and fields a class supports. There are a number of classes in the reflection package.

Using the reflection API you can write code that links different software components together at runtime, create new program flow without any source code modifications, basically they can dynamically change based on the type of objects or classes they are working on.

Using Reflections we can create

When using reflections you should be careful as you can make the code harder to maintain and understand, slower to run orcan be dangerous to execute this is because you an get access to internal structures. When using reflection you must understand that all compile time checks become runtime checks or errors, so many checks that the compiler would do we now have to do ourselves, also the compiler will try and reduce code (optimise code), where Reflection is only optimized by ourselves. When using Reflection you are writing defensive code when handling exceptions and many edge cases have to thought about and handled. Reflection code still has to be loosely decoupled this allows for testing and easy code maintence, if you have to hardcode names, etc use annotations where possible.

Below are some of the Reflection areas we will be covering in this tutorial

Class Primary Function
AccessibleObject allows you to bypass the default access control checks
Array allows you to dynamically create and manipulate arrays
Constructor provides information about a constructor
Executable an abstract superclass extended by Method and Constructor
Field provides information about a field
Method provides information about a method
Modifier provides information about class and member access modifiers
Parameter provides information about a parameters
Proxy supports dynamic proxy classes
ReflectPermission allows reflection of private or protected members of a class

The reflection entry point is the Class<?>, using Class<?> we can describe a Class object of any parameter type, as Class<?> is a super type to Class<T> of any type T, the information we can retrieve is as per below

Primitives because they are not Objects you cannot use reflection to obtain their class type or information, we will cover this in more detail later. Also using the Class.forName() to get Class information can be used with external libraries. There are a number of methods that you can use below are some example but there are many more

Introduction
public class intro1 {

    private static class Square implements Drawable {
        @Override
        public int getNumberOfCorners() {
            return 4;
        }
    }

    private interface Drawable {
        int getNumberOfCorners();
    }

    private enum Color {
        BLUE, RED, GREEN
    }

    private static void printClassInfo(Class<?> ... classes) {
        for(Class<?> clazz : classes) {
            System.out.println(String.format("class name: %s, class package: %s",
                    clazz.getSimpleName(),
                    clazz.getPackageName()));

            Class<?> [] implementedInterfaces = clazz.getInterfaces();

            for(Class<?> implementedInterface: implementedInterfaces) {
                System.out.println(String.format("class %s implements: %s",
                        clazz.getSimpleName(),
                        implementedInterface.getSimpleName()));
            }

            System.out.println("Is array: " + clazz.isArray());
            System.out.println("Is primitive: " + clazz.isPrimitive());
            System.out.println("Is enum: " + clazz.isEnum());
            System.out.println("Is interface: " + clazz.isInterface());
            System.out.println("Is anonymous:" + clazz.isAnonymousClass());

            System.out.println("");
        }
    }

    public static void main(String[] args) throws ClassNotFoundException {
        Class<String> stringClass = String.class;

        Map<String, Integer> mapObject = new HashMap<>();
        Class hashMapClass = mapObject.getClass();

        // could throw a ClassNotFoundException, notice we use the forName() method
        Class squareClass = Class.forName("uk.co.datadisk.introduction.intro1$Square");

        printClassInfo(stringClass, hashMapClass, squareClass);
        System.out.println("---------------------------------------------------------------------");

        // Object with anonymous class
        var circleObject = new Drawable() {
            @Override
            public int getNumberOfCorners() {
                return 0;
            }
        };

        printClassInfo(Collection.class, boolean.class, int [][].class,
                Color.class, circleObject.getClass());
    }
}

Output
-----------------------------------------------------------------------------
class name: String, class package: java.lang
class String implements: Serializable
class String implements: Comparable
class String implements: CharSequence
Is array: false
Is primitive: false
Is enum: false
Is interface: false
Is anonymous:false

class name: HashMap, class package: java.util
class HashMap implements: Map
class HashMap implements: Cloneable
class HashMap implements: Serializable
Is array: false
Is primitive: false
Is enum: false
Is interface: false
Is anonymous:false

class name: Square, class package: uk.co.datadisk.introduction
class Square implements: Drawable
Is array: false
Is primitive: false
Is enum: false
Is interface: false
Is anonymous:false

---------------------------------------------------------------------
class name: Collection, class package: java.util
class Collection implements: Iterable
Is array: false
Is primitive: false
Is enum: false
Is interface: true
Is anonymous:false

class name: boolean, class package: java.lang
Is array: false
Is primitive: true
Is enum: false
Is interface: false
Is anonymous: false

class name: int[][], class package: java.lang
class int[][] implements: Cloneable
class int[][] implements: Serializable
Is array: true
Is primitive: false
Is enum: false
Is interface: false
Is anonymous:false

class name: Color, class package: uk.co.datadisk.introduction
Is array: false
Is primitive: false
Is enum: true
Is interface: false
Is anonymous:false

class name: , class package: uk.co.datadisk.introduction
class  implements: Drawable
Is array: false
Is primitive: false
Is enum: false
Is interface: false
Is anonymous: true

Object Creation and Constructors

The Classes constructor is represented by the Constructor<?>, it includes information about the constructor including

There are a number methods that you can use, some with throw an NoSuchMethodException, remember if no constructor is created Java will automatically create a default no parameter constructor.

Below is an example

Constructors
    public class constructors {
    
    public static void main(String [] args) throws IllegalAccessException, InstantiationException, InvocationTargetException {
    printConstructorsData(Person.class);
    System.out.println();
    printConstructorsData(Address.class);
    System.out.println("--------------------------------------------------------------");
    
    Address address = createInstanceWithArguments(Address.class, "First Street", 10);
    System.out.println("Address Object: " + address + "\n");
    
    Person person1 = createInstanceWithArguments(Person.class,  address, "John", 20);
    System.out.println("Person Object: " + person1);
    
    Person person2 = createInstanceWithArguments(Person.class,  "John");
    System.out.println("Person Object: " + person2);
    
    // This won't work as constructor as private access
    // Contact contact = new Contact("paul.valle@example.com", "123456789");
    
    Contact contact = createInstanceWithArguments(Contact.class, "paul.valle@example.com", "123456789");
    System.out.println("Contact Object (private constructor): " + contact);
    }
    
    // Using reflection and Generic method to create an instance of any Class with multiple constructors
    public static <T> T createInstanceWithArguments(Class<T> clazz, Object ... args) throws IllegalAccessException, InvocationTargetException, InstantiationException {
    // loop over all the constructors found
    for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
    // found the constructor with the number of parameters passed
    if(constructor.getParameterTypes().length == args.length) { constructor.setAccessible(true); // used for any private constructors
    return (T) constructor.newInstance(args);
    }
    }
    System.out.println("An appropriate constructor was not found");
    return null;
    }
    
    public static void printConstructorsData(Class<?> clazz) {
        Constructor<?>[] constructors = clazz.getDeclaredConstructors();
    
    System.out.println(String.format("class %s has %d declared constructors", clazz.getSimpleName(), constructors.length));
    
    for (int i = 0 ; i < constructors.length ; i++) {
    Class
    <?> [] parameterTypes = constructors[i].getParameterTypes();

            List<String> parameterTypeNames = Arrays.stream(parameterTypes)
                    .map(type -> type.getSimpleName())
                    .collect(Collectors.toList());

            System.out.println(" " + parameterTypeNames);
        }
    }

    public static class Person {
        private final Address address;
        private final String name;
        private final int age;

        public Person() {
            this.name = "anonymous";
            this.age = 0;
            this.address = null;
        }

        public Person(String name) {
            this.name = name;
            this.age = 0;
            this.address = null;
        }

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
            this.address = null;
        }

        public Person(Address address, String name, int age) {
            this.address = address;
            this.name = name;
            this.age = age;
        }

        @Override
        public String toString() {
            return "Person{" +
                    "address=" + address +
                    ", name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

    public static class Address {
        private String street;
        private int number;

        public Address(String street, int number) {
            this.street = street;
            this.number = number;
        }

        @Override
        public String toString() {
            return "Address{" +
                    "street='" + street + '\'' +
                    ", number=" + number +
                    '}';
        }
    }
}

Contact Class
----------------------------------------------------------------------------
public class Contact {
    private String email;
    private String mobile;

    private Contact(String email, String mobile) {
        this.email = email;
        this.mobile = mobile;
    }

    @Override
    public String toString() {
        return "email='" + email + "', contact='" + mobile + "'";
    }
}

Output
-------------------------------------------------------------------------
class Person has 4 declared constructors
 [Address, String, int]
 [String, int]
 [String]
 []

class Address has 1 declared constructors
 [String, int]
--------------------------------------------------------------
Address Object: Address{street='First Street', number=10}

Person Object: Person{address=Address{street='First Street', number=10}, name='John', age=20}
Person Object: Person{address=null, name='John', age=0}
Contact Object (private constructor): email='paul.valle@example.com', contact='123456789'

Fields

In this section we look at accessing fields which could be public, protected or private, we also will access synthentic fields which are fields that the Java compiler creates for internal usage, you don't see these fields unless you use reflection to retrieve them, however you should never touch or modify them.

Getting fields including Synthenic fields
public class field {

    public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException {
        printDeclaredFieldsInfo1(Movie.MovieStats.class);
        System.out.println("----------------------------------------------------------");

        Movie movie = new Movie("Lord of the Rings",
                2001,
                12.99,
                true,
                Category.ADVENTURE);

        printDeclaredFieldsInfo2(Movie.class, movie);
        System.out.println("-------------------------------------------------------");
        printDeclaredFieldsInfo2(Category.class, movie);
        System.out.println("-------------------------------------------------------");

        Field minPriceStaticField = Movie.class.getDeclaredField("MINIMUM_PRICE");

        System.out.println(String.format("static MINIMUM_PRICE value :%f", minPriceStaticField.get(null)));
    }

    public static void printDeclaredFieldsInfo1(Class<?> clazz) {
        for (Field field : clazz.getDeclaredFields()) {
            System.out.println(String.format("Field name : %s type : %s",
                    field.getName(),
                    field.getType().getName()));

            System.out.println(String.format("Is synthetic field : %s", field.isSynthetic()));
            System.out.println();
        }
    }

    public static <T> void printDeclaredFieldsInfo2(Class<? extends T> clazz, T instance) throws IllegalAccessException {
        for (Field field : clazz.getDeclaredFields()) {
            System.out.println(String.format("Field name : %s type : %s",
                    field.getName(),
                    field.getType().getName()));

            System.out.println(String.format("Is synthetic field : %s", field.isSynthetic()));
            System.out.println(String.format("Field value is : %s", field.get(instance)));
            System.out.println();
        }
    }

    public enum Category {
        ADVENTURE,
        ACTION,
        COMEDY
    }

    public static class Movie extends Product {
        public static final double MINIMUM_PRICE = 10.99;

        private boolean isReleased;
        private Category category;
        private double actualPrice;

        public Movie(String name, int year, double price, boolean isReleased, Category category) {
            super(name, year);
            this.isReleased = isReleased;
            this.category = category;
            this.actualPrice = Math.max(price, MINIMUM_PRICE);
        }

        // Nested class (this link will be synthetic link to the movie class)
        public class MovieStats {
            private double timesWatched;

            public MovieStats(double timesWatched) {
                this.timesWatched = timesWatched;
            }

            public double getRevenue() {
                return timesWatched * actualPrice;
            }
        }
    }

    // Superclass
    public static class Product {
        protected String name;
        protected int year;
        protected double actualPrice;

        public Product(String name, int year) {
            this.name = name;
            this.year = year;
        }
    }
}

output
-------------------------------------------------------------------------
Field name : timesWatched type : double
Is synthetic field : false

Field name : this$0 type : uk.co.datadisk.fields_arrays.field$Movie   (this must be the nested class)
Is synthetic field : true

----------------------------------------------------------
Field name : MINIMUM_PRICE type : double
Is synthetic field : false
Field value is : 10.99

Field name : isReleased type : boolean
Is synthetic field : false
Field value is : true

Field name : category type : uk.co.datadisk.fields_arrays.field$Category
Is synthetic field : false
Field value is : ADVENTURE

Field name : actualPrice type : double
Is synthetic field : false
Field value is : 12.99

-------------------------------------------------------
Field name : ADVENTURE type : uk.co.datadisk.fields_arrays.field$Category
Is synthetic field : false
Field value is : ADVENTURE

Field name : ACTION type : uk.co.datadisk.fields_arrays.field$Category
Is synthetic field : false
Field value is : ACTION

Field name : COMEDY type : uk.co.datadisk.fields_arrays.field$Category
Is synthetic field : false
Field value is : COMEDY

Field name : $VALUES type : [Luk.co.datadisk.fields_arrays.field$Category;  (this is the enum values array)
Is synthetic field : true
Field value is : [Luk.co.datadisk.fields_arrays.field$Category;@4590c9c3

-------------------------------------------------------
static MINIMUM_PRICE value :10.990000
Object JSON String builder
public class field2 {

    public static void main(String[] args) throws IllegalAccessException {
        Company company = new Company("Udemy", "San Francisco", new Address("Harrison Street", (short) 600));
        Address address = new Address("Main Street", (short) 1);
        Person person = new Person("John", true, 29, 100.555f, address, company);

        String json = objectToJson(person, 0);

        System.out.println(json);
    }

    public static String objectToJson(Object instance, int indentSize) throws IllegalAccessException {
        Field[] fields = instance.getClass().getDeclaredFields();
        StringBuilder stringBuilder = new StringBuilder();

        stringBuilder.append(indent(indentSize));
        stringBuilder.append("{");
        stringBuilder.append("\n");

        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            field.setAccessible(true);

            if (field.isSynthetic()) {
                continue;
            }

            stringBuilder.append(indent(indentSize + 1));
            stringBuilder.append(formatStringValue(field.getName()));

            stringBuilder.append(":");

            if (field.getType().isPrimitive()) {
                stringBuilder.append(formatPrimitiveValue(field, instance));
            } else if (field.getType().equals(String.class)) {
                stringBuilder.append(formatStringValue(field.get(instance).toString()));
            } else {
                stringBuilder.append(objectToJson(field.get(instance), indentSize + 1));
            }

            if (i != fields.length - 1) {
                stringBuilder.append(",");
            }
            stringBuilder.append("\n");
        }

        stringBuilder.append(indent(indentSize));
        stringBuilder.append("}");
        return stringBuilder.toString();
    }

    private static String indent(int indentSize) {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < indentSize; i++) {
            stringBuilder.append("\t");
        }
        return stringBuilder.toString();
    }

    private static String formatPrimitiveValue(Field field, Object parentInstance) throws IllegalAccessException {
        if (field.getType().equals(boolean.class)
                || field.getType().equals(int.class)
                || field.getType().equals(long.class)
                || field.getType().equals(short.class)) {
            return field.get(parentInstance).toString();
        } else if (field.getType().equals(double.class) || field.getType().equals(float.class)) {
            return String.format("%.02f", field.get(parentInstance));
        }

        throw new RuntimeException(String.format("Type : %s is unsupported", field.getType().getName()));
    }

    private static String formatStringValue(String value) {
        return String.format("\"%s\"", value);
    }
}

Address Class
-------------------------------------------------------------------------------------------
public class Address {
    private final String street;
    private final short apartment;

    public Address(String street, short apartment) {
        this.street = street;
        this.apartment = apartment;
    }
}

Company Class
-------------------------------------------------------------------------------------------
public class Company {
    private String name;
    private String city;
    private Address address;

    public Company(String name, String city, Address address) {
        this.name = name;
        this.city = city;
        this.address = address;
    }
}

Person Class
--------------------------------------------------------------------------------------
public class Person {
    private final String name;
    private final boolean employed;
    private final int age;
    private final float salary;
    private final Address address;
    private final Company job;

    public Person(String name, boolean employed, int age, float salary, Address address, Company job) {
        this.name = name;
        this.employed = employed;
        this.age = age;
        this.salary = salary;
        this.address = address;
        this.job = job;
    }
}

Output
-----------------------------------------------------------------------------------
{
	"name":"John",
	"employed":true,
	"age":29,
	"salary":100.56,
	"address":	{
		"street":"Main Street",
		"apartment":1
	},
	"job":	{
		"name":"Udemy",
		"city":"San Francisco",
		"address":		{
			"street":"Harrison Street",
			"apartment":600
		}
	}
}

We can even set the field value during runtime, including private fields. However there are some rules even if you set the accessible flag to true

Setting fields
public class field3 {

    public static class SecretText {

        private String secretText;

        public SecretText(String secretText) {
            this.secretText = secretText;
        }

        @Override
        public String toString() {
            return "secretText='" + secretText + "'";
        }
    }

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        SecretText secretText1 = new SecretText("Hello World");
        System.out.println(secretText1);

        // Get access to field using reflection
        Field secretText2 = SecretText.class.getDeclaredField("secretText");
        System.out.println(secretText2.get(secretText1));   // confirm we have "Hello World"
        System.out.println("-----------------------------------------");
		
		// set the field using reflection
		secretText2.setAccessible(true);            // set if private access
        secretText2.set(secretText1, "GoodBye World");
       

        // we have changed the value secretText1 using reflection
        System.out.println(secretText1);
    }
}

Arrays

You can use Reflection on Arrays, getting information of the Array itself and element values, this is regardless of its type or dimensions, you can also create arrays below are some examples.

Arrays
public class array {

    public static void main(String[] args) {
        int[] oneDimensionalArray = {1, 2};
        inspectArrayObject(oneDimensionalArray);
        System.out.println("----------------------------------------------------");

        double[][] towDimensionalArray = {{1.5, 2.5}, {3.5, 4.5}};

        inspectArrayObject(towDimensionalArray);    // Array information
        inspectArrayValues(towDimensionalArray);    // Array value information
    }

    // Many of the Arrays methods use reflection to retrieve information about the array
    public static void inspectArrayValues(Object arrayObject) {
        int arrayLength = Array.getLength(arrayObject);      // reflections is being used behind the scenes

        System.out.print("[");
        for (int i = 0; i < arrayLength; i++) {
            Object element = Array.get(arrayObject, i);      // reflections is being used behind the scenes 

            if (element.getClass().isArray()) {
                inspectArrayValues(element);
            } else {
                System.out.print(element);
            }

            if (i != arrayLength - 1) {
                System.out.print(" , ");
            }
        }
        System.out.print("]");
    }

    public static void inspectArrayObject(Object arrayObject) {
        Class
    clazz = arrayObject.getClass();
    System.out.println(String.format("Is array : %s", clazz.isArray()));
    
    Class
     arrayComponentType = clazz.getComponentType();
        System.out.println(String.format("This is an array of type : %s", arrayComponentType.getTypeName()));
    }
}
Array creation
public class array2 {

    public static void main(String[] args) {

        double[] array1 = (double[]) parseArray(double.class, "2.0,3.4,7.8,9.8");
        Arrays.stream(array1).forEach(System.out::println);
        System.out.println("-----------------------------------------------------------");

        int[] array2 = (int[]) parseArray(int.class, "2,3,7,9,10,5");
        Arrays.stream(array2).forEach(System.out::println);
        System.out.println("-----------------------------------------------------------");

        String[] array3 = (String[]) parseArray(String.class, "Paul,Will,Moore,Graham");
        Arrays.stream(array3).forEach(System.out::println);
        System.out.println("-----------------------------------------------------------");
    }

    private static Object parseArray(Class<?>
    arrayElementType, String value) {
    String[] elementValues = value.split(",");
    
    Object arrayObject = Array.newInstance(arrayElementType, elementValues.length);
    
    for (int i = 0; i < elementValues.length; i++) {
    Array.set(arrayObject, i, parseValue(arrayElementType, elementValues[i]));
    }
    return arrayObject;
    }
    
    private static Object parseValue(Class<?> type, String value) {
        if (type.equals(int.class)) {
            return Integer.parseInt(value);
        } else if (type.equals(short.class)) {
            return Short.parseShort(value);
        } else if (type.equals(long.class)) {
            return Long.parseLong(value);
        } else if (type.equals(double.class)) {
            return Double.parseDouble(value);
        } else if (type.equals(float.class)) {
            return Float.parseFloat(value);
        } else if (type.equals(String.class)) {
            return value;
        }
        throw new RuntimeException(String.format("Type : %s unsupported", type.getTypeName()));
    }
}

Output
-----------------------------------------------------------------------
2.0
3.4
7.8
9.8
-----------------------------------------------------------
2
3
7
9
10
5
-----------------------------------------------------------
Paul
Will
Moore
Graham
-----------------------------------------------------------

Methods

Using Reflection we can obtain all the methods of an object and obtain information about them, just likes fields there are a number of ways we can do this

Below are some notes above invoking methods using reflection

Now for some examples

Methods (getting)
public class method1 {

    public static void main(String[] args) {
        testGetters(Product.class);
    }

    public static void testGetters(Class<?> dataClass) {
        Field[] fields = dataClass.getDeclaredFields();

        Map<String, Method> methodNameToMethod = mapMethodNameToMethod(dataClass);

        for (Field field : fields) {
            System.out.println("Getting field: " + field.getName());
            String getterName = "get" + capitalizeFirstLetter(field.getName());

            if (!methodNameToMethod.containsKey(getterName)) {
                throw new IllegalStateException(String.format("Field : %s doesn't have a getter method", field.getName()));
            }

            Method getter = methodNameToMethod.get(getterName);
            System.out.println("Getter method: " + getter.getName());

            if (!getter.getReturnType().equals(field.getType())) {
                throw new IllegalStateException(
                        String.format("Getter method : %s() has return type %s but expected %s",
                                getter.getName(),
                                getter.getReturnType().getTypeName(),
                                field.getType().getTypeName()));
            }

            if (getter.getParameterCount() > 0) {
                throw new IllegalStateException(String.format("Getter : %s has %d arguments", getterName));
            }
            System.out.println();
        }
    }

    private static String capitalizeFirstLetter(String fieldName) {
        return fieldName.substring(0, 1).toUpperCase().concat(fieldName.substring(1));
    }

    private static Map<String, Method> mapMethodNameToMethod(Class<?> dataClass) {
        // Here we retrieve the methods
        Method[] allMethods = dataClass.getMethods();

        // Create a hashmap with method name and the actual method
        Map<String, Method> nameToMethod = new HashMap<>();

        for (Method method : allMethods) {
            nameToMethod.put(method.getName(), method);
        }

        return nameToMethod;
    }
}

Product Class
------------------------------------------------------------------------
public class Product {

    private double price;
    private String name;
    private long quantity;
    private Date expirationDate;

    // Getters
    public double getPrice() {
        return price;
    }

    public String getName() {
        return name;
    }

    public long getQuantity() {
        return quantity;
    }

    public Date getExpirationDate() {
        return expirationDate;
    }
}

Output
------------------------------------------------------------------------
Getting field: price
Getter method: getPrice

Getting field: name
Getter method: getName

Getting field: quantity
Getter method: getQuantity

Getting field: expirationDate
Getter method: getExpirationDate
Methods (Getters and Setters)
public class method2 {

    public static void main(String[] args) {
        testGetters(ClothingProduct.class);
        testSetters(ClothingProduct.class);
    }

    public static void testSetters(Class<?> dataClass) {
        List<Field> fields = getAllFields(dataClass);

        for (Field field : fields) {
            System.out.println("Getting field: " + field.getName());
            String setterName = "set" + capitalizeFirstLetter(field.getName());

            Method setterMethod = null;
            try {
                setterMethod = dataClass.getMethod(setterName, field.getType());
                System.out.println("Setter method: " + setterMethod.getName());
            } catch (NoSuchMethodException e) {
                throw new IllegalStateException(String.format("Setter : %s not found", setterName));
            }

            if (!setterMethod.getReturnType().equals(void.class)) {
                throw new IllegalStateException(String.format("Setter method : %s has to return void", setterName));
            }
        }
    }

    public static void testGetters(Class<?> dataClass) {
        List<Field> fields = getAllFields(dataClass);

        Map<String, Method> methodNameToMethod = mapMethodNameToMethod(dataClass);

        for (Field field : fields) {
            String getterName = "get" + capitalizeFirstLetter(field.getName());

            if (!methodNameToMethod.containsKey(getterName)) {
                throw new IllegalStateException(String.format("Field : %s doesn't have a getter method", field.getName()));
            }

            Method getter = methodNameToMethod.get(getterName);
            System.out.println("Getter method: " + getter.getName());

            if (!getter.getReturnType().equals(field.getType())) {
                throw new IllegalStateException(
                        String.format("Getter method : %s() has return type %s but expected %s",
                                getter.getName(),
                                getter.getReturnType().getTypeName(),
                                field.getType().getTypeName()));
            }

            if (getter.getParameterCount() > 0) {
                throw new IllegalStateException(String.format("Getter : %s has %d arguments", getterName));
            }
        }
    }

    private static List<Field> getAllFields(Class<?> clazz) {
        if (clazz == null || clazz.equals(Object.class)) {
            return Collections.emptyList();
        }

        Field[] currentClassFields = clazz.getDeclaredFields();

        List<Field> inheritedFields = getAllFields(clazz.getSuperclass());

        List<Field> allFields = new ArrayList<>();

        allFields.addAll(Arrays.asList(currentClassFields));
        allFields.addAll(inheritedFields);

        return allFields;
    }

    private static String capitalizeFirstLetter(String fieldName) {
        return fieldName.substring(0, 1).toUpperCase().concat(fieldName.substring(1));
    }

    private static Map<String, Method> mapMethodNameToMethod(Class<?> dataClass) {
        Method[] allMethods = dataClass.getMethods();

        Map<String, Method> nameToMethod = new HashMap<>();

        for (Method method : allMethods) {
            nameToMethod.put(method.getName(), method);
        }

        return nameToMethod;
    }
}

ClothingProduct Class
---------------------------------------------------------------
enum Size {
    SMALL,
    MEDIUM,
    LARGE,
    XLARGE
}

public class ClothingProduct {

    private Size size;
    private String color;

    // Getters
    public Size getSize() {
        return size;
    }

    public void setSize(Size size) {
        this.size = size;
    }

    // Setters

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}

Output
---------------------------------------------------------------
Getter method: getSize
Getter method: getColor
Getting field: size
Setter method: setSize
Getting field: color
Setter method: setColor
Method invoking
class SecretMessage {
    private String secretMessage;

    public String getSecretMessage() {
        return secretMessage;
    }

    public void setSecretMessage(String message) {
        this.secretMessage = message;
    }
}

public class method3 {

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

        // Create a product object
        SecretMessage secretMessage = new SecretMessage();

        // Get both the getter and setter method
        Method getMethod = SecretMessage.class.getMethod("getSecretMessage");
        Method setMethod = SecretMessage.class.getMethod("setSecretMessage", String.class);

        // Invoke the setter method, we also need to pass in the secretMessage object
        Boolean setResult = (Boolean) setMethod.invoke(secretMessage, "Hello World");

        if (setResult != null && setResult.equals(Boolean.FALSE)) {
            System.out.println("Received negative result. Aborting ...");
            return;
        }

        // Invoke the getter method, result is "Hello World"
        String getResult = (String) getMethod.invoke(secretMessage);
        System.out.println(getResult);
    }
}

Modifiers

You can use Reflection on access modifiers, this applies to both access (private, protected and public) and non-access modifiers (such abstract, final, static, interface). You can use the getModifiers method to retrieve the modifiers.

When the modifiers are return its in the form of a bitwise map, we have to perform bitwise operations to extract the information we need, also there are bitwise help methods that can also tell use about the access modifiers, below is an example of this.

Modifiers
public class modifier {

    public static void main(String[] args) throws ClassNotFoundException {
        printClassModifiers(Auction.class);
        printClassModifiers(Bid.class);
        printClassModifiers(Bid.Builder.class);

        printMethodsModifiers(Auction.class.getDeclaredMethods());

        printFieldsModifiers(Auction.class.getDeclaredFields());
        printFieldsModifiers(Bid.class.getDeclaredFields());
    }

    public static void printFieldsModifiers(Field[] fields) {
        for (Field field : fields) {
            int modifier = field.getModifiers();

            System.out.println(String.format("Field \"%s\" access modifier is %s",
                    field.getName(),
                    getAccessModifierName(modifier)));

            if (Modifier.isVolatile(modifier)) {
                System.out.println("The field is volatile");
            }

            if (Modifier.isFinal(modifier)) {
                System.out.println("The field is final");
            }

            if (Modifier.isTransient(modifier)) {
                System.out.println("The field is transient and will not be serialized");
            }
            System.out.println();
        }
    }

    public static void printMethodsModifiers(Method[] methods) {
        for (Method method : methods) {
            int modifier = method.getModifiers();

            System.out.println(String.format("%s() access modifier is %s",
                    method.getName(),
                    getAccessModifierName(modifier)));

            if (Modifier.isSynchronized(modifier)) {
                System.out.println("The method is synchronized");
            } else {
                System.out.println("The method is not synchronized");
            }
        }
    }

    public static void printClassModifiers(Class<?> clazz) {
        int modifier = clazz.getModifiers();
        String className = clazz.getSimpleName();

        System.out.println(String.format("Class %s access modifier is %s",
                className,
                getAccessModifierName(modifier)));

        if (Modifier.isAbstract(modifier)) {
            System.out.println("The " + className + " class is abstract");
        }

        if (Modifier.isInterface(modifier)) {
            System.out.println("The " + className + " class is an interface");
        }

        if (Modifier.isStatic(modifier)) {
            System.out.println("The " + className + " class is static");
        }
        System.out.println();
    }

    private static String getAccessModifierName(int modifier) {
        if (Modifier.isPublic(modifier)) {
            return "public";
        } else if (Modifier.isPrivate(modifier)) {
            return "private";
        } else if (Modifier.isProtected(modifier)) {
            return "protected";
        } else {
            return "package-private";
        }
    }
}

Auction Class
-----------------------------------------------------------------
public class Auction implements Serializable {
    private final List<Bid> bids = new ArrayList<>();

    private transient volatile boolean isAuctionStarted;

    // Methods
    public synchronized void addBid(Bid bid) {
        this.bids.add(bid);
    }

    public synchronized List<Bid> getAllBids() {
        return Collections.unmodifiableList(bids);
    }

    public synchronized Optional<Bid> getHighestBid() {
        return bids.stream()
                .max(Comparator.comparing(Bid::getPrice));
    }


    public void startAuction() {
        this.isAuctionStarted = true;
    }

    public void stopAuction() {
        this.isAuctionStarted = false;
    }

    public boolean isAuctionRunning() {
        return isAuctionStarted;
    }
}

Bid Class
--------------------------------------------------------------
public abstract class Bid implements Serializable {

    // Fields
    protected int price;
    protected String bidderName;

    // Builder
    public static Builder builder() {
        return new Builder();
    }

    // Getters
    public int getPrice() {
        return price;
    }

    public String getBidderName() {
        return bidderName;
    }

    @Override
    public String toString() {
        return "Bid{" +
                "price=" + price +
                ", bidderName='" + bidderName + '\'' +
                '}';
    }


    // Bid Builder class
    public static class Builder {
        private int price;
        private String bidderName;

        public Builder setPrice(int price) {
            this.price = price;
            return this;
        }

        public Builder setBidderName(String bidderName) {
            this.bidderName = bidderName;
            return this;
        }

        public Bid build() {
            return new BidImpl();
        }

        // Bid Implementation
        private class BidImpl extends Bid {

            private BidImpl() {
                super.price = Builder.this.price;
                super.bidderName = Builder.this.bidderName;
            }
        }
    }
}


Output
------------------------------------------------------
Class Auction access modifier is public

Class Bid access modifier is public
The Bid class is abstract

Class Builder access modifier is public
The Builder class is static

addBid() access modifier is public
The method is synchronized
getHighestBid() access modifier is public
The method is synchronized
stopAuction() access modifier is public
The method is not synchronized
getAllBids() access modifier is public
The method is synchronized
isAuctionRunning() access modifier is public
The method is not synchronized
startAuction() access modifier is public
The method is not synchronized
Field "bids" access modifier is private
The field is final

Field "isAuctionStarted" access modifier is private
The field is volatile
The field is transient and will not be serialized

Field "price" access modifier is protected

Field "bidderName" access modifier is protected

Annotations

As we know annotations start with a @ sign, its metadata about the target it annotates or the program as a whole. However we can carry the annotation into the JVM which allows us to access them using Reflection. Annotation elements can also be accessed via Reflection. One area is in testing and the JUnit annotations which Java uses Reflections heavily.

Annotations
public class annotation {

    public static void main(String[] args) {
        initialize(AutoSaver.class);
    }

    public static void initialize(Class<?> ... classes) {

        for (Class<?> clazz : classes) {
            if (!clazz.isAnnotationPresent(InitializerClass.class)) {       // our own annotation
                continue;
            }
            System.out.println(clazz.getName() + " has a class annotation");
            getAllInitializingMethods(clazz);
        }
    }

    private static void getAllInitializingMethods(Class<?> clazz) {
        List<Method> initializingMethods = new ArrayList<>();
        for (Method method : clazz.getDeclaredMethods()) {
            if (method.isAnnotationPresent(InitializerMethod.class)) {      // our own annotation
                initializingMethods.add(method);
                System.out.println("Method: " + method.getName());
                InitializerMethod initializerMethod = method.getAnnotation(InitializerMethod.class);
                System.out.println("Annotation Type: " + initializerMethod.annotationType());
                System.out.println("Annotation value (option): " + initializerMethod.option());
                System.out.println("Annotation value (dummy): " + initializerMethod.dummy());

                for (Parameter parameter : method.getParameters()) {
                    Object value = null;
                    if (parameter.isAnnotationPresent(DependsOn.class)) {
                        String dependencyOperationName = parameter.getAnnotation(DependsOn.class).value();
                        System.out.println("DependsOn value: " + dependencyOperationName);
                    }
                }
            }
        }
        System.out.println();
    }
}

Annotations Classes
----------------------------------------------------
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface InitializerClass {
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface InitializerMethod {
    String option() default "None";
    String dummy() default "None";
}

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface DependsOn {
    String value() default "None";
}

AutoSaver Class
---------------------------------------------------
@InitializerClass
public class AutoSaver {

    @InitializerMethod
    public void method1() {
        System.out.println("Method One");
    }

    @InitializerMethod(option="Hello World", dummy="Dummy")
    public void method2() {
        System.out.println("Method Two");
    }

    @InitializerMethod
    public void method3(@DependsOn("DependsOn") String dependOn) {
        System.out.println("Method Three");
    }

    public void method4() {
        System.out.println("Method Four");
    }
}

Output
---------------------------------------------------
uk.co.datadisk.annotations.AutoSaver has a class annotation
Method: method1
Annotation Type: interface uk.co.datadisk.annotations.InitializerMethod
Annotation value (option): None
Annotation value (dummy): None
Method: method2
Annotation Type: interface uk.co.datadisk.annotations.InitializerMethod
Annotation value (option): Hello World
Annotation value (dummy): Dummy
Method: method3
Annotation Type: interface uk.co.datadisk.annotations.InitializerMethod
Annotation value (option): None
Annotation value (dummy): None
DependsOn value: DependsOn
Fields with Annotations
public class annotation2 {

    public static void main(String[] args) {
        printDeclaredFields(FieldClass.class);
    }

    public static void printDeclaredFields(Class<?> clazz) {
        for (Field field : clazz.getDeclaredFields()) {

            if (!field.isAnnotationPresent(FieldAnno.class)) {
                // ignore any non @FieldAnno fields
                continue;
            }

            FieldAnno fieldAnno = field.getAnnotation(FieldAnno.class);
            System.out.println("Field: " + field.getName());
            System.out.println("Type: " + fieldAnno.annotationType());
            System.out.println("Value: " + fieldAnno.value() + "\n");
        }
    }
}

Annotation
----------------------------------------------
@Target({ElementType.FIELD, ElementType.PARAMETER})      // Notice FIELD and PARAMETER are used
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldAnno {
    String value();					 // the PARAMETER will be type String
}

FieldAnno Class
-----------------------------------------------
public class FieldClass {

    @FieldAnno("ids_anno_value")
    private List<String> ids;

    @FieldAnno("name_anno_value")
    private String name;

    private int numberOfFields;
}


Output
-----------------------------------------------
Field: ids
Type: interface uk.co.datadisk.annotations.FieldAnno
Value: ids_anno_value

Field: name
Type: interface uk.co.datadisk.annotations.FieldAnno
Value: name_anno_value
Repeatable Annotations
public class annotation3 {

    public static void main(String[] args) {

        printDeclaredClass(RoleTest.class);
        printDeclaredFields(RoleTest.class);
    }

    public static void printDeclaredClass(Class<?> clazz) {

        if(clazz.getAnnotationsByType(RoleClass.class).length != 0) {
            RoleClass[] roleClass = clazz.getAnnotationsByType(RoleClass.class);

            for (RoleClass r : roleClass) {
                System.out.println("Role Class: " + r.value());
            }
            System.out.println();
        }
    }

    public static void printDeclaredFields(Class<?> clazz) {

        for (Field field : clazz.getDeclaredFields()) {
            System.out.println("Checking field: " + field.getName());
            if (field.getAnnotationsByType(RoleField.class).length != 0) {
                RoleField[] roleField = field.getAnnotationsByType(RoleField.class);
                for(RoleField rf : roleField) {
                    System.out.println("Role Type: " + rf.annotationType());
                    System.out.println("Role Field Value: " + rf.name());
                }
            }
        }
    }
}

Annotations
------------------------------------------------------
@Repeatable(RoleClass.List.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RoleClass {

    String value();

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE})
    @interface List {
        RoleClass[] value();
    }
}

@Repeatable(RoleFields.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface RoleField {
    String name();
}

@Retention(RetentionPolicy.RUNTIME)
public @interface RoleFields {

    RoleField[] value() default {};
}

RoleTest Class
-------------------------------------------------
@RoleClass("roleClass1")
@RoleClass("roleClass2")
@RoleClass("roleClass3")
public class RoleTest {

    @RoleField(name="roleField1")
    @RoleField(name="roleField2")
    @RoleField(name="roleField3")
    private List<String> roles;

}

Output
-------------------------------------------------
Role Class: roleClass1
Role Class: roleClass2
Role Class: roleClass3

Checking field: roles
Role Type: interface uk.co.datadisk.annotations.RoleField
Role Field Value: roleField1
Role Type: interface uk.co.datadisk.annotations.RoleField
Role Field Value: roleField2
Role Type: interface uk.co.datadisk.annotations.RoleField
Role Field Value: roleField3

Using Reflection to create a proxy

There are many ways to use Reflection below is just one example on how to use reflection to create a proxy class, if you drill down into the Proxy class itself you will see reflection being used inside that class as well.

Proxy example using reflection
public class proxy1 {

    public static void main(String[] args) {

        // a proxy is return
        List<String> listOfGreetings = createProxy(new ArrayList<>());

        // we are executing the proxy
        listOfGreetings.add("hello");
        listOfGreetings.add("good morning");
        listOfGreetings.remove("hello");
    }

    @SuppressWarnings("unchecked")
    public static <T> T createProxy(Object originalObject) {
        Class<?>[] interfaces = originalObject.getClass().getInterfaces();

        Arrays.stream(interfaces).forEach(i -> System.out.println("Interface: " + i));
        System.out.println();

        TimeMeasuringProxyHandler timeMeasuringProxyHandler = new TimeMeasuringProxyHandler(originalObject);

        return (T) Proxy.newProxyInstance(originalObject.getClass().getClassLoader(), interfaces, timeMeasuringProxyHandler);
    }

    public static class TimeMeasuringProxyHandler implements InvocationHandler {
        private final Object originalObject;

        public TimeMeasuringProxyHandler(Object originalObject) {
            this.originalObject = originalObject;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object result;

            System.out.println(String.format("Measuring Proxy - Before Executing method : %s()", method.getName()));

            long startTime = System.nanoTime();
            try {
                result = method.invoke(originalObject, args);
            } catch (InvocationTargetException e) {
                throw e.getTargetException();
            }
            long endTime = System.nanoTime();

            System.out.println();
            System.out.println(String.format("Measuring Proxy - Execution of %s() took %dns \n", method.getName(), endTime - startTime));

            return result;
        }
    }
}