Newsletter sign-up
View all newsletters

Sign up for our technology specific newsletters.

Enterprise Java
Email Address:

Java Tip 113: Identify subclasses at runtime

Retrieve all the classes that implement a given interface

  • Digg
  • Reddit
  • SlashDot
  • Stumble
  • del.icio.us
  • Technorati
  • dzone

Page 2 of 2

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.

About the author

Daniel Le Berre is a researcher in artificial intelligence in the Business and Technology Research Laboratory at the University of Newcastle, Australia. He has been playing with Java since late 1997. He is currently maintaining a JavaServer Pages site, SAT Live!, and an open source Java library, JSAT, dedicated to one of his research interests.
  • Digg
  • Reddit
  • SlashDot
  • Stumble
  • del.icio.us
  • Technorati
  • dzone
Comments (1)
Login
Forgot your account info?

culorotoBy Anonymous on November 12, 2009, 2:31 pmculoroto

Reply | Read entire comment

View all comments

Add comment
Anonymous comments subject to approval. Register here for member benefits.
Have a JavaWorld account? Log in here. Register now for a free account.
Resources