Reflection in Java Part 2

Arbitrary MethodsArbitrary Constructors

In the previous blog, we learned about Reflection in Java. If you want to know about the concept of Reflection visit the blog Reflection in Java . The Reflection mechanism is very powerful. Reflection allows us to runtime inspect objects, runtime analyze capabilities classes, runtime construct generic array manipulation code, and runtime utilize Method objects that function merely like function pointers in languages like C++. In this blog, we will be going through all the above mention concepts in detail.

Using Reflection for Analyzing the Capabilities of Classes in Java

This topic will give us an overview of the important parts of the reflection mechanism. The package java.lang.reflect contains the classes like Field, Constructor, and Method which describe the field, constructor, and method of the class. The classes mentioned above have a method getName() that returns the name of the item. The getType() method of the Field class returns an object of type Class which describes the field type. The Method and Constructor classes contain the methods used for reporting the type of parameter and the Method class also reports the return type. These three classes have the method known as getModifiers which returns the integers with various bits that are turned on and off which describes the modifiers such as public and static. We can use the static methods in the Modifier class present in java.lang.reflect package to analyze the integer that getModifiers returns.

e.g.

import java.util.*;
import java.lang.reflect.*;
public class ReflectionEx{
  public static void main(String[] args)throws ReflectiveOperationException{
    String cname;
    if (args.length > 0)
      cname = args[0];
    else{
      var in = new Scanner(System.in);
      System.out.println("Enter class name:");
      cname = in.next();
    }
    Class className = Class.forName(cname);
    Class superclassName = className.getSuperclass();
    String modifiersName = Modifier.toString(className.getModifiers());
    if (modifiersName.length() > 0)
      System.out.print(modifiersName + " ");
    System.out.print("class " + cname);
    if (superclassName != null && superclassName != Object.class)
      System.out.print(superclassName.getName());
    System.out.print("
{
");
    displayConstructors(className);
    System.out.println();
    displayMethods(className);
    System.out.println();
    displayFields(className);
    System.out.println("}");
  }
  public static void displayConstructors(Class className){
    Constructor[] namesOfConstructors = className.getDeclaredConstructors();
    for (Constructor constructorName : namesOfConstructors){
      String cname = constructorName.getName();
      System.out.print("");
      String modifiersName = Modifier.toString(constructorName.getModifiers());
      if (modifiersName.length() > 0)
        System.out.print(modifiersName + " ");
      System.out.print(cname + "(");
      Class[] parameterTypes = constructorName.getParameterTypes();
      for (int i = 0; i < parameterTypes.length; i++){
        if (i > 0) System.out.print(", ");
          System.out.print(parameterTypes[i].getName());
      }
      System.out.println(");");
    }
  }
  public static void displayMethods(Class className){
    Method[] methodsName = className.getDeclaredMethods();
    for (Method methodName : methodsName){
      Class returnType = methodName.getReturnType();
      String name = methodName.getName();
      System.out.print("");
      String modifiersName = Modifier.toString(methodName.getModifiers());
      if (modifiersName.length() > 0) System.out.print(modifiersName + " ");
        System.out.print(returnType.getName() + " " + name + " ");
      Class[] parameterTypes = methodName.getParameterTypes();
      for (int i = 0; i < parameterTypes.length; i++){
        if (i > 0) System.out.print(", ");
          System.out.print(parameterTypes[i].getName());
      }
      System.out.println(");");
    }
  }
  public static void displayFields(Class className){
    Field[] NameOfFields = className.getDeclaredFields();
    for (Field fields : NameOfFields){
      Class type = fields.getType();
      String name = fields.getName();
      System.out.print("");
      String modifiersName =Modifier.toString(fields.getModifiers());
      if (modifiersName.length() > 0)
        System.out.print(modifiersName + " ");
      System.out.println(type.getName() + " " + name + ";");
    }
  }
}

e.g.

Enter class name:
  java.util.Date
  public class java.util.Date{
    public java.util.Date(int, int, int, int, int, int);public java.util.Date();
    public java.util.Date(int, int, int, int, int);
    public java.util.Date(java.lang.String);
    public java.util.Date(long);
    public java.util.Date(int, int, int);
    public static long parse java.lang.String);
    public boolean before java.util.Date);
    public boolean after java.util.Date);
    public int getSeconds );
    public java.time.Instant toInstant );
    public int getYear );
    public void setTime long);
    public long getTime );
    public int getMonth );
    static final long getMillisOf java.util.Date);
    public void setDate int);
    private final sun.util.calendar.BaseCalendar$Date getCalendarDate );
    public void setHours int);public int getHours );
    public int getMinutes );
    public void setMonth int);
    public void setMinutes int);
    public void setSeconds int);
    public void setYear int);
    public int getDate );
    public int getDay );
    private final long getTimeImpl );
    private static final java.lang.StringBuilder convertToAbbr java.lang.StringBuilder, java.lang.String);
    public java.lang.String toLocaleString );
    public java.lang.String toGMTString );
    public int getTimezoneOffset );
    private static final sun.util.calendar.BaseCalendar getCalendarSystem long);
    private static final sun.util.calendar.BaseCalendar getCalendarSystem sun.util.calendar.BaseCalendar$Date);
    private static final sun.util.calendar.BaseCalendar getCalendarSystem int);
    private static final synchronized sun.util.calendar.BaseCalendar getJulianCalendar );
    public boolean equals java.lang.Object);
    public java.lang.String toString );public int hashCode );
    public java.lang.Object clone );
    public int compareTo java.util.Date);
    public volatile int compareTo java.lang.Object);
    public static java.util.Date from java.time.Instant);
    private void readObject java.io.ObjectInputStream);
    private void writeObject java.io.ObjectOutputStream);
    public static long UTC int, int, int, int, int, int);
    private final sun.util.calendar.BaseCalendar$Date normalize );
    private final sun.util.calendar.BaseCalendar$Date normalize sun.util.calendar.BaseCalendar$Date);
    private static final sun.util.calendar.BaseCalendar gcal;private static sun.util.calendar.BaseCalendar jcal;
    private transient long fastTime;
    private transient sun.util.calendar.BaseCalendar$Date cdate;
    private static int defaultCenturyStart;
    private static final long serialVersionUID;
    private static final [Ljava.lang.String; wtb;
    private static final [I ttb;

OR if we give java.lang.String then it will give the following output

e.g.

Enter class name:
  java.lang.String
  public final class java.lang.String{
    public java.lang.String([B);
    public java.lang.String([B, int, int);
    public java.lang.String([B, java.nio.charset.Charset);
    public java.lang.String([B, java.lang.String);
    public java.lang.String([B, int, int, java.nio.charset.Charset);
    java.lang.String([C, int, int, java.lang.Void);
    java.lang.String(java.lang.AbstractStringBuilder, java.lang.Void);
    public java.lang.String(java.lang.StringBuilder);
    public java.lang.String(java.lang.StringBuffer);
    java.lang.String([B, byte);
    public java.lang.String([C, int, int);
    public java.lang.String([C);
    public java.lang.String(java.lang.String);
    public java.lang.String();
    public java.lang.String([B, int, int, java.lang.String);
    public java.lang.String([B, int);
    public java.lang.String([B, int, int, int);
    public java.lang.String([I, int, int);

    [B value );
    public boolean equals java.lang.Object);
    public int length );
    public java.lang.String toString );
    public int hashCode );public void getChars int, int, [C, int);
    public int compareTo java.lang.String);
    public volatile int compareTo java.lang.Object);
    public int indexOf java.lang.String, int);
    public int indexOf int);
    static int indexOf [B, byte, int, java.lang.String, int);
    public int indexOf int, int);
    public int indexOf java.lang.String);
    static void checkIndex int, int);
    public static java.lang.String valueOf int);
    public static java.lang.String valueOf float);
    public static java.lang.String valueOf boolean);
    public static java.lang.String valueOf long);
    public static java.lang.String valueOf double);
    public static java.lang.String valueOf java.lang.Object);
    public static java.lang.String valueOf char);
    public static java.lang.String valueOf [C);
    public static java.lang.String valueOf [C, int, int);
    byte coder );
    private static java.lang.Void rangeCheck [C, int, int);
    public java.util.stream.IntStream codePoints );
    public boolean isEmpty );
    public char charAt int);
    public int codePointAt int);
    public int codePointBefore int);
    public int codePointCount int, int);
    public int offsetByCodePoints int, int);
    public [B getBytes java.nio.charset.Charset);
    public void getBytes int, int, [B, int);
    public [B getBytes java.lang.String);
    public [B getBytes );
    void getBytes [B, int, byte);
    public boolean contentEquals java.lang.StringBuffer);
    public boolean contentEquals java.lang.CharSequence);
    private boolean nonSyncContentEquals java.lang.AbstractStringBuilder);
    public boolean equalsIgnoreCase java.lang.String);
    public int compareToIgnoreCase java.lang.String);
    public boolean regionMatches boolean, int, java.lang.String, int, int);
    public boolean regionMatches int, java.lang.String, int, int);
    public boolean startsWith java.lang.String);
    public boolean startsWith java.lang.String, int);
    public boolean endsWith java.lang.String);
    public int lastIndexOf int);
    static int lastIndexOf [B, byte, int, java.lang.String, int);
    public int lastIndexOf java.lang.String, int);
    public int lastIndexOf java.lang.String);
    public int lastIndexOf int, int);
    public java.lang.String substring int, int);
    public java.lang.String substring int);
    public java.lang.CharSequence subSequence int, int);
    public java.lang.String concat java.lang.String);
    public java.lang.String replace java.lang.CharSequence, java.lang.CharSequence);
    public java.lang.String replace char, char);
    public boolean matches java.lang.String);
    public boolean contains java.lang.CharSequence);
    public java.lang.String replaceFirst java.lang.String, java.lang.String);
    public java.lang.String replaceAll java.lang.String, java.lang.String);
    public [Ljava.lang.String; split java.lang.String);
    public [Ljava.lang.String; split java.lang.String, int);
    public static transient java.lang.String join java.lang.CharSequence, [Ljava.lang.CharSequence;);
    public static java.lang.String join java.lang.CharSequence, java.lang.Iterable);
    public java.lang.String toLowerCase );
    public java.lang.String toLowerCase java.util.Locale);
    public java.lang.String toUpperCase java.util.Locale);
    public java.lang.String toUpperCase );
    public java.lang.String trim );
    public java.lang.String strip );
    public java.lang.String stripLeading );
    public java.lang.String stripTrailing );
    public boolean isBlank );
    private int indexOfNonWhitespace );public java.util.stream.Stream lines );
    public java.util.stream.IntStream chars );
    public [C toCharArray );
    public static transient java.lang.String format java.lang.String, [Ljava.lang.Object;);
    public static transient java.lang.String format java.util.Locale, java.lang.String, [Ljava.lang.Object;);
    public static java.lang.String copyValueOf [C, int, int);
    public static java.lang.String copyValueOf [C);
    public native java.lang.String intern );
    public java.lang.String repeat int);
    private boolean isLatin1 );
    static void checkOffset int, int);
    static void checkBoundsOffCount int, int, int);
    static void checkBoundsBeginEnd int, int, int);
    static java.lang.String valueOfCodePoint int);

    private final [B value;
    private final byte coder;
    private int hash;
    private static final long serialVersionUID;
    static final boolean COMPACT_STRINGS;
    private static final [Ljava.io.ObjectStreamField; serialPersistentFields;public static final java.util.Comparator CASE_INSENSITIVE_ORDER;

Using Reflection to Analyze Objects at Runtime

In the previous section, we have seen how we can find out the names and types of the data fields of any object. In this section, we will go one step ahead and actually look at the contents of the fields. We can easily get the contents of a specific field of an object whose name and type are known when we write code. But reflection allows us to look at fields of objects that were not known at compile time. The method which helps in achieving this is the get() method in the Field class. For example, if fieldObj is an object of type Field and obj is an object of the class of which fieldObj is a field, then fieldObj.get(obj) returns an object whose value corresponds to the obj field's current value. Let’s go through the example:

In the above example AtrowelEmployee object describes the properties of the AtrowelEmplyee, same is the case with the Class object which describes the properties of the class. The getName() method of Class is the most commonly used method. This method returns the name of the class.

e.g.

var employeeObj = new AtrowelEmployee("John Smith", 60000, 20, 1, 1985);
Class className = employeeObj.getClass();
Field fieldName = className.getDeclaredField("name");
Object obj = fieldName.get(employeeObj);

We can also set the values that we can get. The call fieldObj.set(obj, value) sets the field represented by fieldObj of the object obj to the new value. But there is a problem with the above code, i.e. the name field is a private field, the get and set methods will throw an IllegalAccessException. We can use get() and set() only with the accessible fields. The security mechanism of Java allows us to find out what fields an object has but doesn’t allow us to read or write those fields unless we have permission. The default behavior of the reflection is to respect the Java access control. But we can override access control by invoking the setAccessible() method on the Field, Method, and Constructor object.

e.g.

fieldObj.setAccessible(true);

The setAccessible() method is a method of the AccessibleObject class, which is the common superclass of the Field, Method, and Constructor classes. If the access is not granted then the call to setAccessible() throws an exception. Many libraries make use of reflection, because of which Java 9 and 10 only give a warning when we use reflection to access a nonpublic feature inside a module. Now we will look at the generic toString() method which works for any class. To obtain all the data fields the generic toString() method uses getDeclaredFields() and to make all the fields accessible we use setAccessible(). It obtains the name and value for each file. Recursively calling toString() converts each value into a string. The generic toString method requires addressing a couple of complexities. Cycle of references may cause infinite recursion. Hence, the ObjectAnalyzer keeps track of objects that were already visited. toString() can be used to peek inside any object.

e.g.

var squaresObj = new ArrayList<Integer>();
for (int i = 1; i <= 6; i++) squaresObj.add(i * i);
  System.out.println(new ObjectAnalyzer().toString(squaresObj));

e.g.

java.util.ArrayList[elementData=class java.lang.Object[]
{java.lang.Integer[value=1][][],
java.lang.Integer[value=4][][],
java.lang.Integer[value=9][][],
java.lang.Integer[value=16][][],
java.lang.Integer[value=25][][],
java.lang.Integer[value=36][][],null,null,null,null,null,null},size=6]
[modCount=6][][]

We can also use generic toString() to implement the toString() method of our own class.

e.g.

public String toString(){
  return new ObjectAnalyzer().toString(this);
}

Invoking Arbitrary Methods and Constructor in Java

We can execute an arbitrary function through a function pointer in C and C++. But Java doesn’t have method pointers. Java Designers say that method pointers are dangerous and error-prone and Java interface and lambda expressions are the superior solution. The reflection mechanism allows us to call arbitrary methods. We have already seen in the previous section that we can inspect the field of an object with the get() method of the Field class. The Method class has an invoke() method that lets us call the method that is wrapped in the current Method object. The syntax for the invoke() method is

Object invoke(Object object, Object... args)

In the above statement the first parameter is the implicit parameter and the remaining objects provide the explicit parameters. In the case of a static method the first parameter is ignored i.e. we can set it to null. For example, if method1 represents the getName() method of the AtrowelEmployee class, we can call:

String name = (String) method1.invoke(employeeObj);

Suppose if the return type is primitive, then the invoke() method will return the wrapper type. For example, if method2 represents the getSalary() method of the AtrowelWmployee class, then the returned object is actually a Double, and we must cast it accordingly. We can use automatic unboxing to turn it into a double. For example

double salary = (Double) m2.invoke(harry);

We can obtain the Method object by calling getDeclaredMethods() and search through the returned array of Method objects until we find the method we want or else we can call the getMethod() method of the Class class. It is similar to the getField() method which takes a string with the field name and returns a Field object. It may be possible that there may be multiple methods with the same names, so we must be careful to use the right method. For that reason, we must supply the parameter types of the desired method. The syntax of getMethod() is

Method getMethod(String strName, Class... parameterTypes)

e.g.

Method method1 = AtrowelEmployee.class.getMethod("getName");
Method method2 = AtrowelEmployee.class.getMethod("raiseSalary", double.class);

Similar approach is used for invoking arbitrary constructors. We must provide the constructor’s parameter types to the Class.getConstructor() method, and provide the parameter values to the Constructor.newInstance() method:

Class className = Random.class;
Constructor constructorObj = className.getConstructor(long.class);
Object obj = constructorObj.newInstance(62L);

e.g.

import java.lang.reflect.*;
public class MethodTableEx{
  public static void main(String[] args)throws ReflectiveOperationException{
    Method square = MethodTableEx.class.getMethod("square", double.class);
    Method squareroot = Math.class.getMethod("sqrt", double.class);
    displayTable(1, 5, 5, square);
    displayTable(1, 5, 5, squareroot);
  }
  public static double square(double no){
    return no * no;
  }
  public static void displayTable(double from, double to, int no, Method methodName)throws ReflectiveOperationException{
    System.out.println(methodName);
    double dx = (to - from) / (no - 1);
    for (double no1 = from; no1 <= to; no1 += dx){
      double no2 = (Double) methodName.invoke(null, no1);
      System.out.printf("%10.4f | %10.4f%n", no1, no2);
    }
  }
}

Output

public static double MethodTableEx.square(double)
1.0000 | 	1.0000
2.0000 | 	4.0000
3.0000 | 	9.0000
4.0000 |	16.0000
5.0000 |	25.0000
public static double java.lang.Math.sqrt(double)
1.0000 | 	1.0000
2.0000 |    1.4142
3.0000 | 	1.7321
4.0000 | 	2.0000
5.0000 | 	2.2361
Get in Touch

Atrowel will be pleased to receive your feedback and suggestions. Those of you who would like to contribute, please send us an email.