VidWorks Entertainment New Developer Training Program

Tutorial #9

Reflection

 

            The Reflection API can do a lot of things. It can basically get all the information about a class--its fields, methods, constructors, even superclasses and interfaces, even if you don't know the class's name. Perhaps you received an Object object with an unknown class or someone passed you in a string at run-time with the class's name. Reflection can handle both of those cases. It can also instantiate an object from such classes, as well as modify their Field properties and get their methods and invoke them.

            Most of these abilities, however, are beyond what we need for game programming. Instead, we'll focus on the most useful ability: Instantiating an object whose class name is not known until run-time.

 

Instantiating a class using Reflection

 

            This is pretty straightforward, so rather explain all the nitty-gritty details first and then post the example, I'm just going to post the example and walk you through it.

 

/* ReflectionDemo.java */

 

public class ReflectionDemo {

      public static void main(String[] args) {

            // get the class name from command-line arguments

            String className = null;

            if (args.length >= 1) {

                  className = args[0];

            } else {

                  System.out.println("Usage: java ReflectionDemo [class name].");

                  System.exit(0);

            }

 

            // get and validate the class

            Class myClass = null;

            try {

                  myClass = Class.forName(className);

            } catch (ClassNotFoundException e) {

                  System.out.println(className + " not found!");

                  System.exit(1);

            }

            if (!isPerson(myClass)) {

                  System.out.println(className + " is not a Person!");

                  System.exit(1);

            }

 

            // instantiate the class

            Person p = null;

            try {

                  p = (Person) myClass.newInstance();

            } catch (InstantiationException e) {

                  System.out.println(className + " couldn't be instantiated!");

                  System.exit(1);

            } catch (IllegalAccessException e) {

                  System.out.println(e.getMessage());

                  System.exit(1);

            }

 

            // make the person say hello

            System.out.println(p.sayHello());

      }

 

      public static boolean isPerson(Class myClass) {

            // input validation

            if (myClass == null) { return false; }

 

            // search the interfaces and interfaces of its superclasses

            Class[] interfaces = null;

            do {

                  interfaces = myClass.getInterfaces();

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

                        if (interfaces[i] == Person.class) { return true; }

                  }

            } while ((myClass = myClass.getSuperclass()) != null);

 

            // Person not found; not a Person

            return false;

      }

}

 

 

interface Person {

      public String sayHello();

}

 

 

class Dwayne implements Person {

      public String sayHello() {

            return "Hey, man.";

      }

}

 

 

class Tina implements Person {

      public String sayHello() {

            return "Hi there!";

      }

}

 

 

abstract class Charlie implements Person { }

 

            First of all, here's an overview of what this code does. We have an interface called Person, and the code here reads from the command-line the name of a class that has implemented Person. Then, it loads up that class and calls one of the Person methods on it. Most of the code here is error-checking.

            Now, let's look at the code. We'll start at the top, in ReflectionDemo.main(). The first block of code is self-explanatory--it simply gets the class name from the command-line. The next block of code gets the class from a string containing the class name. The important part is:

 

Class.forName(string classname)

 

Note that you'll need to use the fully qualified class name. (That is, you'll need to include the package, such as java.lang.String or viltris.SomeClassInPackageViltris.)

            The next part calls the method isPerson(), which is a function that I wrote myself. Essentially, this function checks to see if the given class implements the Person interface or if any of its superclasses inherits the Person interface. The following two member methods of class Class are helpful in this respect:

 

Class[] getInterfaces()

Class getSuperclass()

 

Also note that I could have written this method recursively. Also note that since we could just simply instantiate the object then us the instanceof operator to see if it's a Person, making the isPerson() method completely unnecessary. So why did I use the isPerson() method? Two reasons: First, I thought it'd be a good tool for you to learn how to access interfaces and superclasses. Second, I had overlooked this technique until I wrote most of this tutorial (silly Dwayne).

            Next, we instantiate the class into an object. The Class class has this member method that we can use:

 

Object newInstance()

 

Since we know it's a Person, we can promptly cast it to a Person without having to check it's type.

            Finally, we call the sayHello() method that is declared in the Person interface, as a demonstration.

            The last part of the code defines the Person interface, the Dwayne and Tina objects that implement the interface, and an abstract class Charlie, which cannot be instantiated since it's abstract. Notice how the ReflectionDemo class never needs to refer to the Dwayne and Tina classes in the code, and yet, (as we shall soon see) that doesn't stop us from instantiating them.

 

            Anyway, here are a few sample runs of the program:

>java ReflectionDemo

Usage: java ReflectionDemo [class name].

 

>java ReflectionDemo Dwayne

Hey, man.

 

>java ReflectionDemo Tina

Hi there!

 

>java ReflectionDemo Sarah

Sarah not found!

 

>java ReflectionDemo java.lang.String

java.lang.String is not a Person!

 

>java ReflectionDemo Charlie

Charlie couldn't be instantiated!

 

>_

 

 

So what is Reflection useful for?

 

            Weren't you paying attention to the conclusion of Tutorial #8? Jeez, what's with you people. Just go back to Tutorial #8 and look it up for yourself.

            Naw, I'm just kidding. Because I'm a nice guy, I'll re-iterate the important parts in this section. Reflection has its uses for creating modules to extend a program's functionality without having to change the program's original code. One of my favorite examples is AI (which is incidentally the example where I learned about this technique from). Let's say you have a program that plays a game, and in the program, you have a configurable AI. Now, let's say that you want to release a new AI. Well, without Reflection, you'd have to create the new AI, add it to the source code, re-compile, and then re-distribute a new version--a hassle indeed! With Reflection, you could just read all the AI names from a data file and then pull them up via Reflection. That way, if you release a new AI, all you have to do is update the data file and add the compiled class file. This is much easier to maintain!

            Another popular term for this kind of extensibility mechanism is a plug-in. Basically, a plug-in is a module that you can add to your program after the fact ("plug" it into your program) to extend its functionality. The way you'd do a plug-in is similar to the Reflection-based AI mentioned above. Basically, you can have a data file with the names of the plug-ins, and when you install a plug-in, you add the plug-in to the data file and add the compiled class file. It's simple, intuitive, and powerful!

 

 

Conclusion

 

            As I hinted at in the introductory section, there's plenty more that Reflection can do, although that's outside of the scope of our tutorial. If you want to make a debugger that uses Reflection, by all means, go ahead. But debuggers aren't a part of our goals (even though they are a nice tool to use on the way to our goals) and, therefore, will not be part of this tutorial.

            Next time:

            Oh, wait. There won't be a next time. This is our last tutorial. Well, I'll miss you guys. Hopefully, you guys have learned a lot and you'll be able to come back and help us develop games.

 

Signing off,

Dwayne Jeng

 

(No homework assignment for this tutorial. I mean, it's not like we check anyway. If you're truly interested in learning this stuff, you'd go make your own program that uses Reflection in a practical way even if we didn't have a section called "Homework Assignment!" And for those of you who wouldn't, that's your homework assignment.)