Java Reflection APIs and Java interfaces provide great tools for writing reusable code. Take, for example, the case of a generic command launcher: Suppose you had a set of classes performing various tasks -- such as switching on or off the light; opening, closing, or locking the door; and so forth. The name of those classes would be LightOn
, LightOff
, DoorOpen
, DoorClose
, and DoorLock
, respectively. All these classes conveniently implement the Command
interface defined below:
public interface Command { public void process(); }
You can write a simple generic launcher in the following way:
public class Launcher{ public static void main(String[] args){ if (args.length>0) { try { Command command = (Command)Class.forName(args[0]).newInstance(); command.process(); } catch (Exception ex) { System.out.println("Invalid command"); } } else { System.out.println("Usage: Launcher <command>"); } } } // Launcher
You use the Class.forName()
method to get a Class
object for the class given in a parameter. Then you create an instance of this class using the newInstance()
method. Since the class is expected to implement the Command
interface, you cast the object into Command
, and then call the process()
method that will perform the task. If an exception is launched -- due to a misspelling of the class, a wrong name, or a security exception -- you display an Invalid command
message.
You can use the command launcher in the following way:
%java Launcher LightOn
If new tasks are implemented, you don't need to change anything to the launcher. From a programmer point of view, that's great. But what about the user? Suppose a user enters:
%java Launcher OpenDoor Invalid command
Does the Invalid command
message mean the user cannot open the door? No, it is simply a name problem (OpenDoor
instead of DoorOpen
). The user should be able to see a list of available commands. To keep a generic launcher, he or she must be able to find those commands at runtime.
Java Reflection provides a lot of information about a given class at runtime: you can easily know all its super classes, implemented interfaces, methods, constructors, fields, and so on. But in this case, we are interested in all the classes that implement a given interface. No such information is available via Java Reflection. Before reading Mike Clark's "Java Tip 105: Mastering the Classpath with JWhich," I had no clue how to obtain that information. The remainder of this tip will show you how to apply a process for retrieving information on classes that implement a given interface.
A user-friendly generic command launcher
JWhich
provides a way to obtain a File
object from a package name. Since packages in Java are directories, it is easy to retrieve all the classes contained in a package using the File
object's list()
method.
The idea is to check, for each class file in the package, whether or not the corresponding class implements the Command
interface using the instanceof
statement. This means you can only check each class file's public class, and that the interface and its implementations must be located in a package.
Here is the code:
public static void find(String pckgname) { // Code from JWhich // ====== // Translate the package name into an absolute path String name = new String(pckgname); if (!name.startsWith("/")) { name = "/" + name; } name = name.replace('.','/'); // Get a File object for the package URL url = Launcher.class.getResource(name); File directory = new File(url.getFile()); // New code // ====== if (directory.exists()) { // Get the list of the files contained in the package String [] files = directory.list(); for (int i=0;I<files.length;i++) { // we are only interested in .class files if (files[i].endsWith(".class")) { // removes the .class extension String classname = files[i].substring(0,files[i].length()-6); try { // Try to create an instance of the object Object o = Class.forName(pckgname+"."+classname).newInstance(); if (o instanceof Command) { System.out.println(classname); } } catch (ClassNotFoundException cnfex) { System.err.println(cnfex); } catch (InstantiationException iex) { // We try to instantiate an interface // or an object that does not have a // default constructor } catch (IllegalAccessException iaex) { // The class is not public } } } } }
To perform the task at hand, you just need to modify the original launcher a bit. You can assume now that the interface and its implementations are in the package commands
:
public static void main(String[] args){ if (args.length>0) { try { Command command = (Command)Class.forName("commands."+args[0]).newInstance(); command.process(); } catch (Exception ex) { System.out.println("Invalid command"); System.out.println("Available commands:"); find("commands"); } } else { System.out.println("Usage: Launcher <command>"); } }
Here is the new result of a wrong command:
%java Launcher OpenDoor Invalid command Available commands: LightOn LightOff DoorOpen DoorClose DoorLock
Runtime subclass identification
You can refine the find()
method to find any subclass of a given class. To do so, you use the dynamic version of instanceof
, isInstance()
. You replace the (o instanceof Command)
with (tosubclass.isInstance(o))
, where tosubclass
is the class given in the parameter of the find()
method.
Now you have a method that can find any subclass of a given class in a given package. You can improve the method by letting it look for the subclasses in the currently loaded packages. To do that, you use the Package.getPackages()
method, which returns the exact packages loaded by the current class loader. Then you just need to call the find()
method for each package:
public static void find(String tosubclassname) { try { Class tosubclass = Class.forName(tosubclassname); Package [] pcks = Package.getPackages(); for (int i=0;I<pcks.length;i++) { find(pcks[i].getName(),tosubclass); } } catch (ClassNotFoundException ex) { System.err.println("Class "+tosubclassname+" not found!"); } }
The result of this method mainly depends on when it is called. In the case of the generic command launcher, few packages will be loaded when the find()
method is called. For instance, here are the packages loaded on my NT box before calling the find()
method:
package java.util.zip, Java Platform API Specification, version 1.3 package java.security, Java Platform API Specification, version 1.3 package java.io, Java Platform API Specification, version 1.3 package sun.net.www.protocol.file, Java Platform API Specification, version 1.3 package sun.net.www.protocol.jar, Java Platform API Specification, version 1.3 package sun.net.www, Java Platform API Specification, version 1.3 package java.util.jar, Java Platform API Specification, version 1.3 package sun.security.action, Java Platform API Specification, version 1.3 package java.lang, Java Platform API Specification, version 1.3 package sun.io, Java Platform API Specification, version 1.3 package java.util, Java Platform API Specification, version 1.3 package sun.misc, Java Platform API Specification, version 1.3 package java.security.cert, Java Platform API Specification, version 1.3 package java.lang.reflect, Java Platform API Specification, version 1.3 package java.net, Java Platform API Specification, version 1.3 package sun.security.util, Java Platform API Specification, version 1.3 package java.lang.ref, Java Platform API Specification, version 1.3 package sun.security.provider, Java Platform API Specification, version 1.3 package com.sun.rsajca
Since, in the command launcher, the interface and all its implementations are in the same package, you get the loaded packages after loading the class, thus allowing the search for subclasses in that package. This is the only way to find a relevant package. The complete code for the RTSI
class can be found in Resources. Once you unpack the zip file, you can test the code with the following:
% java -cp classes RTSI commands.Command
Working with jar files
The code I have described works fine when the class files are present on the file system, but no longer works when the class files are in a (or several) jar file(s). In the source code, you will find a solution to that problem. You can test that capability with the following:
% java -jar RTSI.jar commands.Command
Lessons learned
You have seen how to dynamically retrieve all the subclasses of a given class contained in a given package, or available on the loaded packages. This feature is useful for the design of generic programs. As shown by the command launcher example, it also helps the user.
Note from author: Some readers pointed out that this tip only detects subclasses that have a default constructor. They proposed that I use the isAssignableFrom()
method from Class
instead of the isInstance()
. You can obtain an updated version of RTSI.java here or in the updated source code below.
This story, "Java Tip 113: Identify subclasses at runtime" was originally published by JavaWorld.