16
Tue, Apr
6 New Articles

Java Journal: The Reflection API

Java
Typography
  • Smaller Small Medium Big Bigger
  • Default Helvetica Segoe Georgia Times
The Java programming language has a number of innovative features that set it apart from other programming languages. First and foremost, Java is object-oriented, and, although it isn't as pure an object-oriented implementation as languages such as Smalltalk, it is much more stringent than C++. Java also gives us automated memory management and protection, saving the hours of tedious debugging that are often associated with lower-level languages. In addition, Java gives us the Java Virtual Machine (JVM) architecture that helps ensure Write Once, Run Anywhere (WORA). This is, of course, the beacon of light that attracted many of us who were lost in the darkness of proprietary languages and operating systems. Another--and often overlooked or misunderstood--Java language feature is reflection.

What Is Reflection?

Reflection is the ability of objects, classes, and interfaces to tell you about themselves at runtime. The name "reflection" comes from the idea that if something can see its reflection, it can tell you about its appearance--or, in object-oriented terms, its external interface. Objects can tell you what class instantiated them. Classes can tell you what classes they inherit from, what interfaces they implement, what methods and fields they have (including constructors), and what their names are. They can also tell you other class attributes, such as whether it is public, abstract, or final. In fact, for methods, you can even get the return type and argument types; likewise, you can also get field types. Interfaces are essentially treated as classes and can tell you everything about themselves that a class can, including whether the class in question is truly a class or actually an interface. What's even better than being able to query objects, classes, and interfaces at runtime is that the Java language designers took things one step further to allow you to manipulate things at runtime. For example, you can create an object whose name you don't know until runtime. You can then query this object for its class type, query its class for the methods that it implements, and then invoke one of these methods. You do all of this without knowing anything about the original object.

A Simple Example

The following code creates an object called myMirror of the class Mirror. In Mirror's constructor, you determine its class name and print it out:

// Mirror.java
public class Mirror
{
  public static void main(String[] args)
  {
    Mirror myMirror = new Mirror();
  }

  public Mirror()
  {
    Class c = this.getClass();
    String whoAmI = c.getName();
    System.out.println(whoAmI);
  }

}


So where would you want to use reflection? Well, suppose you want to make a lightweight way to log messages within your application, and you want to see what class the messages are coming from, but you don't want to clutter up your classes with a lot of junk code. You would first create a new class to act as the interface to your logging system and give it a static method that takes the current object and a message string as an argument. This method can then look up the class name, append the message, and write the log to the console or to a file. The following code adds a new SimpleLogger class and changes your Mirror class above to do exactly this:

// Mirror.java
public class Mirror
{
  public static void main(String[] args)
  {
    Mirror myMirror = new Mirror();
  }

  public Mirror()
  {
    SimpleLogger.writeToLog(this, "Hello");
  }
}

// SimpleLogger.java
public class SimpleLogger

  public static void writeToLog(Object o, String message)
  {
    Class c = o.getClass();
    String whoAmI = c.getName();
    System.out.println(whoAmI + ": " + message);
  }
}


The output of running this program is

Mirror: Hello

Although this example is trivial and you would obviously want to use something far more robust for commercial-strength logging, it gives you an idea of some of the creative ways you can use reflection.

Creating Classes at Runtime Using Reflection

Creating classes at runtime has two steps. First, you call a static method on the class called forName(), which takes a string as an argument, which is the name of the class that the method will return. Second, now that you have a reference to the type of Class that you would like to create, you can call the static method getInstance() on this reference to return your object. Note, however, that you will have to cast this object back to the type of object you are creating. The following code is from the previous example, but it's been modified to create the Mirror class using reflection.

// Mirror.java
public class Mirror
{
  public static void main(String[] args)
  {
    try
    {
      Class c = Class.forName("Mirror");
      Mirror myMirror = (Mirror) c.newInstance();
    }
    catch (Exception e)
    {
      System.out.println(e);
    }
  }

  public Mirror()
  {
    SimpleLogger.writeToLog(this, "Hello");
  }


The addition of the try/catch block that protects the instantiation of the new object here only catches the generic exception. The method forName() can throw IllegalAccessException or ClassNotFoundException. In addition, newInstance() can throw InstantiationException. Each of these three types of exceptions can be caught separately in order to deal with them appropriately.

When you're first working with a new API, it is a good idea to purposefully put something in your code that should generate an exception. That way, you will be able to see if the API throws the type of exception you are expecting. You can easily do this here by changing the "Mirror" in the line

Class c = Class.forName("Mirror");


to the name of a class that does not exist. Then, recompile and run the example. You should get a java.lang.ClassNotFoundException.

A Not-So-Simple Example

Actually, the following example is fairly simple; it's just a good-sized chunk of code. The class below, ObjectRipper, uses the reflection API to tell you just about everything you could possibly want to know about an object:

// ObjectRipper.java
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class ObjectRipper
{
  public static void getObjectInfo(Object anObject)
  {
    getClassInfo(anObject);
    getFieldInfo(anObject);
    getMethodInfo(anObject);
  }

  public static void getClassInfo(Object anObject)
  {
    Class aClass = anObject.getClass();

    String className = aClass.getName();
    String classModifiers = Modifier.toString(aClass.getModifiers());

    System.out.println("Class: " + className);
    System.out.println("  Modifiers: " + classModifiers);
  }

  public static void getFieldInfo(Object anObject)
  {
    System.out.println("Fields");
    Class aClass = anObject.getClass();
    Field[] publicFields = aClass.getFields();

    for (int i = 0; i < publicFields.length; i++)
    {
      String fieldName = publicFields[i].getName();
      Class fieldClass = publicFields[i].getType();
      String fieldClassName = fieldClass.getName();
      String fieldModifiers = Modifier.toString(publicFields[i].getModifiers());
      String fieldValue = "Not a primative";

      if (fieldClass.isPrimitive() == true)
      {
        try
        {
           Object fieldObject = publicFields[i].get(anObject);
           fieldValue = fieldObject.toString();
 }
 catch (IllegalAccessException e)
 {
           System.out.println(e);
 }
      }

      System.out.println("  " + fieldName);
      System.out.println("    "  + "Modifiers: " + fieldModifiers);
      System.out.println("    "  + "Type: " + fieldClassName);
      System.out.println("    "  + "Value: " + fieldValue);
    }
  }

  public static void getMethodInfo(Object anObject)
  {
    System.out.println("Methods");
    Class aClass = anObject.getClass();
    Method[] theMethods = aClass.getMethods();

    for (int i = 0; i < theMethods.length; i++)
    {
      String methodName = theMethods[i].getName();
      System.out.println("  " + methodName);
      String returnType = theMethods[i].getReturnType().getName();
      System.out.println("   Return Type: " + returnType);

      Class[] parameterTypes = theMethods[i].getParameterTypes();
      System.out.print("   Parameters:");

      for (int k = 0; k < parameterTypes.length; k ++)
      {
        String parameterString = parameterTypes[k].getName();
        System.out.print(" " + parameterString);
      }
      System.out.println();
    }
  }
}


Let's break this example down into more manageable chunks. First, you will notice that this class is composed of four static methods:

getObjectInfo()
getClassInfo()
getFieldInfo()
getMethodInfo()


Since all the methods are static, you will never need to instantiate ObjectStripper. Instead, you can just call its methods directly by prefixing them with the class name. Also, although you can call any of the four methods directly, getObjectInfo() is provided as a convenience method, which in turn calls the other three methods.

Let's look at getClassInfo(). The purpose of getClassInfo() is to retrieve and print out the class name and modifiers for the class in question. Class modifiers tell you whether the class is public, protected, private, static, final, or abstract. It also tells you whether it's an interface. Multiple modifiers are possible. Now, let's break down the method line by line:

Class aClass = anObject.getClass();


This line simply retrieves the class of the object in question. You will do this in several places because most of the reflection calls deal with the class of an object rather than the object itself.

String className = aClass.getName();
String classModifiers = Modifier.toString(aClass.getModifiers());


Once you know the class, you can make a call to getName(), which returns the name of the class as a string. Next, you call the method getModifier() on the class, which returns an integer representation of the modifiers. You could decode this directly yourself, but it is safer and more practical to call the static toSting() method on the modifiers class, which gives you a nice string representation of the modifiers without your having to be aware of their internal integer values. In general, this is a good practice. For example, what if the next release of the language includes new modifiers? If you don't follow this practice, you will have to modify your code to check for them explicitly.

These two lines just format and print your results:

System.out.println("Class: " + className);
System.out.println("  Modifiers: " + classModifiers);


Next, take a look at getFieldInfo():

System.out.println("Fields");
Class aClass = anObject.getClass();
Field[] publicFields = aClass.getFields();


First, you print out a label so that you have some context for the output to follow. Then, as you did in getClassInfo(), you look up the class from the object. Once you have the class of the object, you make a call to the getFields() method, which returns an array of field objects, each of which represents one public field of the object you are examining.

for (int i = 0; i < publicFields.length; i++)


This for loop iterates over each of the field objects contained within your object.

String fieldName = publicFields[i].getName();
Class fieldClass = publicFields[i].getType();
String fieldClassName = fieldClass.getName();
String fieldModifiers = Modifier.toString(publicFields[i].getModifiers());


Within the for loop, you use the getName(), getType(), and getModifiers() methods on each of the field objects.

String fieldValue = "Not a primitive";

if (fieldClass.isPrimitive() == true)
{
  try
  {
    Object fieldObject = publicFields[i].get(anObject);
    fieldValue = fieldObject.toString();
  }
  catch (IllegalAccessException e)
  {
    System.out.println(e);
  }
}


Within the for loop operating on each of the field objects, examine each field object to determine if it is a primitive type. If it is, grab the object stored in the field object using a get() method and extract its value to print out later using a toString(). Note that the try/catch block is required by the get() method of the Field Object and may throw an IllegalAccessException.

System.out.println("  " + fieldName);
System.out.println("    "  + "Modifiers: " + fieldModifiers);
System.out.println("    "  + "Type: " + fieldClassName);
System.out.println("    "  + "Value: " + fieldValue);


This final block of code within the for loop formats and prints your results for each field object.

Now, take a look at the third and final method, getMethodInfo().

System.out.println("Methods");
Class aClass = anObject.getClass();
Method[] theMethods = aClass.getMethods();


The first three lines are almost identical to those of getFieldInfo(), except that instead of an array of field objects, you get an array of method objects.

for (int i = 0; i < theMethods.length; i++)


As with the previous method, set up a for loop to iterate over your array.

String methodName = theMethods[i].getName();
System.out.println("  " + methodName);
String returnType = theMethods[i].getReturnType().getName();
System.out.println("   Return Type: " + returnType);


Here, you make calls to the getName() and getReturnType() methods of each method object and print the results.

Class[] parameterTypes = theMethods[i].getParameterTypes();
System.out.print("   Parameters:");

for (int k = 0; k < parameterTypes.length; k ++)
{
  String parameterString = parameterTypes[k].getName();
  System.out.print(" " + parameterString);
}
System.out.println();


Since each method can have multiple parameters, make a call to getParameterTypes() on each method object and then iterate over the array of classes returned, printing the types of each as you go.

That wraps up how the example works, so go ahead and compile it and try running a few of your own objects through it, or grab some out of your favorite API. If you are even more ambitious, you can extend the example to include constructors or list all of the parent classes and interfaces that your object's class implements. Or how about modifying the SimpleLogger Class to call getFieldInfo()? If you do this, you might also want to make fieldValue print out Strings.

I think it's interesting to note that if you changed the example to take classes as arguments and added some HTML tags, you would have the basic framework for JavaDoc. The fact that this basic framework fits in fewer than 100 lines of code speaks volumes to the power of reflection.

These examples only scratch the surface of the capabilities of reflection. It is easy to see how critical reflection is to programmers writing Integrated Development Environments (IDEs) and tools like JavaDoc, but what about the not-so-obvious places like JAX-RPC? Because you can create objects at runtime based on their names in text strings and invoke methods from text strings also, you can see how easy it is to convert from an XML message to a method invocation on an object without using a mile-long if/then/else statement.

Myths About Reflection

I would like to debunk two basic myths about reflection. The first myth is that reflection is used only by tool designers and doesn't really have a practical use for those of us not creating tools. Well, I think the general usefulness of these examples disproves this myth, and by this point, you may already be thinking about how you can incorporate reflection into your applications. The second myth is that reflection is slow. Like many myths, this one is partially grounded in the truth. Early versions of J2SE did suffer some from their implementation of reflection; however, as of J2SE 1.4, you will find that using reflection has no discernable performance implications.

Conclusion

Reflection is another of those truly great features--like automated memory management or WORA--that set Java apart from most other programming languages. Reflection allows you to examine and manipulate classes and objects at runtime. You can tell just about everything about a class, including its inheritance tree, constructors, public fields, and public methods. With objects, you can determine the class that instantiated them as well as the state of their public fields. You can also create classes at runtime, knowing nothing more about the class than its name as a string. Similarly, you can invoke methods on an object, based on knowing the name of a method as a string. You can easily combine these two capabilities to create objects and invoke methods as defined in text files or XML, thereby providing yourself with a simple and adaptable solution to a common computing problem.

Where to Go for More Information

If you would like more information on reflection, you can read the whole 1.4.1 API from Sun. Or, for a more hands-on approach, you can work through the reflection API track.

Michael J. Floyd is an extreme programmer and the Software Engineering Technical Lead for DivXNetworks. He is also a consultant for San Diego State University and can be reached at This email address is being protected from spambots. You need JavaScript enabled to view it..

Michael Floyd

Michael J. Floyd is the Vice President of Engineering for DivX, Inc.

BLOG COMMENTS POWERED BY DISQUS

LATEST COMMENTS

Support MC Press Online

$0.00 Raised:
$

Book Reviews

Resource Center

  • SB Profound WC 5536 Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application. You can find Part 1 here. In Part 2 of our free Node.js Webinar Series, Brian May teaches you the different tooling options available for writing code, debugging, and using Git for version control. Brian will briefly discuss the different tools available, and demonstrate his preferred setup for Node development on IBM i or any platform. Attend this webinar to learn:

  • SB Profound WP 5539More than ever, there is a demand for IT to deliver innovation. Your IBM i has been an essential part of your business operations for years. However, your organization may struggle to maintain the current system and implement new projects. The thousands of customers we've worked with and surveyed state that expectations regarding the digital footprint and vision of the company are not aligned with the current IT environment.

  • SB HelpSystems ROBOT Generic IBM announced the E1080 servers using the latest Power10 processor in September 2021. The most powerful processor from IBM to date, Power10 is designed to handle the demands of doing business in today’s high-tech atmosphere, including running cloud applications, supporting big data, and managing AI workloads. But what does Power10 mean for your data center? In this recorded webinar, IBMers Dan Sundt and Dylan Boday join IBM Power Champion Tom Huntington for a discussion on why Power10 technology is the right strategic investment if you run IBM i, AIX, or Linux. In this action-packed hour, Tom will share trends from the IBM i and AIX user communities while Dan and Dylan dive into the tech specs for key hardware, including:

  • Magic MarkTRY the one package that solves all your document design and printing challenges on all your platforms. Produce bar code labels, electronic forms, ad hoc reports, and RFID tags – without programming! MarkMagic is the only document design and print solution that combines report writing, WYSIWYG label and forms design, and conditional printing in one integrated product. Make sure your data survives when catastrophe hits. Request your trial now!  Request Now.

  • SB HelpSystems ROBOT GenericForms of ransomware has been around for over 30 years, and with more and more organizations suffering attacks each year, it continues to endure. What has made ransomware such a durable threat and what is the best way to combat it? In order to prevent ransomware, organizations must first understand how it works.

  • SB HelpSystems ROBOT GenericIT security is a top priority for businesses around the world, but most IBM i pros don’t know where to begin—and most cybersecurity experts don’t know IBM i. In this session, Robin Tatam explores the business impact of lax IBM i security, the top vulnerabilities putting IBM i at risk, and the steps you can take to protect your organization. If you’re looking to avoid unexpected downtime or corrupted data, you don’t want to miss this session.

  • SB HelpSystems ROBOT GenericCan you trust all of your users all of the time? A typical end user receives 16 malicious emails each month, but only 17 percent of these phishing campaigns are reported to IT. Once an attack is underway, most organizations won’t discover the breach until six months later. A staggering amount of damage can occur in that time. Despite these risks, 93 percent of organizations are leaving their IBM i systems vulnerable to cybercrime. In this on-demand webinar, IBM i security experts Robin Tatam and Sandi Moore will reveal:

  • FORTRA Disaster protection is vital to every business. Yet, it often consists of patched together procedures that are prone to error. From automatic backups to data encryption to media management, Robot automates the routine (yet often complex) tasks of iSeries backup and recovery, saving you time and money and making the process safer and more reliable. Automate your backups with the Robot Backup and Recovery Solution. Key features include:

  • FORTRAManaging messages on your IBM i can be more than a full-time job if you have to do it manually. Messages need a response and resources must be monitored—often over multiple systems and across platforms. How can you be sure you won’t miss important system events? Automate your message center with the Robot Message Management Solution. Key features include:

  • FORTRAThe thought of printing, distributing, and storing iSeries reports manually may reduce you to tears. Paper and labor costs associated with report generation can spiral out of control. Mountains of paper threaten to swamp your files. Robot automates report bursting, distribution, bundling, and archiving, and offers secure, selective online report viewing. Manage your reports with the Robot Report Management Solution. Key features include:

  • FORTRAFor over 30 years, Robot has been a leader in systems management for IBM i. With batch job creation and scheduling at its core, the Robot Job Scheduling Solution reduces the opportunity for human error and helps you maintain service levels, automating even the biggest, most complex runbooks. Manage your job schedule with the Robot Job Scheduling Solution. Key features include:

  • LANSA Business users want new applications now. Market and regulatory pressures require faster application updates and delivery into production. Your IBM i developers may be approaching retirement, and you see no sure way to fill their positions with experienced developers. In addition, you may be caught between maintaining your existing applications and the uncertainty of moving to something new.

  • LANSAWhen it comes to creating your business applications, there are hundreds of coding platforms and programming languages to choose from. These options range from very complex traditional programming languages to Low-Code platforms where sometimes no traditional coding experience is needed. Download our whitepaper, The Power of Writing Code in a Low-Code Solution, and:

  • LANSASupply Chain is becoming increasingly complex and unpredictable. From raw materials for manufacturing to food supply chains, the journey from source to production to delivery to consumers is marred with inefficiencies, manual processes, shortages, recalls, counterfeits, and scandals. In this webinar, we discuss how:

  • The MC Resource Centers bring you the widest selection of white papers, trial software, and on-demand webcasts for you to choose from. >> Review the list of White Papers, Trial Software or On-Demand Webcast at the MC Press Resource Center. >> Add the items to yru Cart and complet he checkout process and submit

  • Profound Logic Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application.

  • SB Profound WC 5536Join us for this hour-long webcast that will explore:

  • Fortra IT managers hoping to find new IBM i talent are discovering that the pool of experienced RPG programmers and operators or administrators with intimate knowledge of the operating system and the applications that run on it is small. This begs the question: How will you manage the platform that supports such a big part of your business? This guide offers strategies and software suggestions to help you plan IT staffing and resources and smooth the transition after your AS/400 talent retires. Read on to learn: