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' |
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); } } |
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 ----------------------------------------------------------- |
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); } } |
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 |
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; } } } |