views:

5827

answers:

10

I want to do something like this:

List<Animal> animals = new ArrayList<Animal>();

for( Class c: list_of_all_classes_available_to_my_app() )
   if (c is Anamal)
      animals.add( new c() );

So, I want to look at all of the classes in my application's universe and when I find one that descends from Animal, I want to create a new object of that type and add it to the list. This allows me to add functionality without having to update a list of things. I can avoid the following:

List<Animal> animals = new ArrayList<Animal>();
animals.add( new Dog() );
animals.add( new Cat() );
animals.add( new Donkey() );
...

With the above approach, I can simply create a new class that extends Animal and it'll get picked up automatically.

UPDATE: 10/16/2008 9:00am Pacific:

This question has generated a lot of great responses -- thank you. From the responses and my research, I've found that what I really want to do is just not possible under Java. There are approaches, such as ddimitrov's ServiceLoader mechanism that can work -- but they are very heavy for what I want, and I believe I simply move the problem from Java code to an external configuration file.

Another way to state what I want: a static function in my Animal class finds and instantiates all classes that inherit from Animal -- without any further configuration/coding. If I have to configure, I might as well just instantiate them in the Animal class anyway. I understand that because a Java program is just a loose federation of .class files that that's just the way it is.

Interestingly, it seems this is fairly trivial in C#.

A: 

Java dynamically loads classes, so your universe of classes would be only those that have already been loaded (and not yet unloaded). Perhaps you can do something with a custom class loader that could check the supertypes of each loaded class. I don't think there's an API to query the set of loaded classes.

thoroughly
A: 

Unfortunately this isn't entirely possible as the ClassLoader won't tell you what classes are available. You can, however, get fairly close doing something like this:

for (String classpathEntry : System.getProperty("java.class.path").split(System.getProperty("path.separator")) {
    if (classpathEntry.endsWith(".jar")) {
        File jar = new File(classpathEntry);
        JarInputStream is = new JarInputStream(new ByteArrayInputStream(jar));

        JarEntry entry;
        while( (entry = is.getNextJarEntry()) != null) {
            if(entry.getName().endsWith(".class")) {
                // Class.forName(entry.getName()) and check
                // for implementation of the interface
            }
        }
    }
}

Edit: johnstok is correct (in the comments) that this only works for standalone Java applications, and won't work under an application server.

jonathan-stafford
This doesn't work well when classes are loaded by other means, For example, in a web or J2EE container.
johnstok
A: 

This is a tough problem and you will need to find out this information using static analysis, its not available easily at runtime. Basically get the classpath of your app and scan through the available classes and read the bytecode information of a class which class it inherits from. Note that a class Dog may not directly inherit from Animal but might inherit from Pet which is turn inherits from Animal,so you will need to keep track of that hierarchy.

pdeva
A: 

Thanks all who answered this question.

It seems this is indeed a tough nut to crack. I ended up giving up and creating a static array and getter in my baseclass.

public abstract class Animal{
    private static Animal[] animals= null;
    public static Animal[] getAnimals(){
        if (animals==null){
            animals = new Animal[]{
                new Dog(),
                new Cat(),
                new Lion()
            };
        }
        return animals;
    }
}

It seems that Java just isn't set up for self-discoverability the way C# is. I suppose the problem is that since a Java app is just a collection of .class files out in a directory / jar file somewhere, the runtime doesn't know about a class until it's referenced. At that time the loader loads it -- what I'm trying to do is discover it before I reference it which is not possible without going out to the file system and looking.

I always like code that can discover itself instead of me having to tell it about itself, but alas this works too.

Thanks again!

JohnnyLambada
Java is set up for self-discovery. You just need to do a bit more work. See my answer for details.
Dave Jarvis
A: 

One way is to make the classes use a static initializers... I don't think these are inherited (it won't work if they are):

public class Dog extends Animal{

static
{
   Animal a = new Dog();
   //add a to the List
}

It requires you to add this code to all of the classes involved. But it avoids having a big ugly loop somewhere, testing every class searching for children of Animal.

This doesn't work in my case. Since the class is not referenced anywhere it is never loaded so the static initializer is never called. It seems that a static initializer is called before everything else when the class is loaded -- but in my case the class is never loaded.
JohnnyLambada
A: 

There is a lot of similarity between this problem and a previously asked question regarding getting all classes within a package.

http://stackoverflow.com/questions/176527/how-can-i-enumerate-all-classes-in-a-package-and-add-them-to-a-list#189525

Diastrophism
+2  A: 

Think about this from an aspect-oriented point of view; what you want to do, really, is know all the classes at runtime that HAVE extended the Animal class. (I think that's a slightly more accurate description of your problem than your title; otherwise, I don't think you have a runtime question.)

So what I think you want is to create a constructor of your base class (Animal) which adds to your static array (I prefer ArrayLists, myself, but to each their own) the type of the current Class which is being instantiated.

So, roughly;

public abstract class Animal
    {
    private static ArrayList<Class> instantiatedDerivedTypes;
    public Animal() {
        Class derivedClass = this.getClass();
        if (!instantiatedDerivedClass.contains(derivedClass)) {
            instantiatedDerivedClass.Add(derivedClass);
        }
    }

Of course, you'll need a static constructor on Animal to initialize instantiatedDerivedClass... I think this'll do what you probably want. Note that this is execution-path dependent; if you have a Dog class that derives from Animal that never gets invoked, you won't have it in your Animal Class list.

McWafflestix
+3  A: 

The Java way to do what you want is to use the ServiceLoader mechanism.

Also many people roll their own by having a file in a well known classpath location (i.e. /META-INF/services/myplugin.properties) and then using ClassLoader.getResources() to enumerate all files with this name from all jars. This allows each jar to export its own providers and you can instantiate them by reflection using Class.forName()

ddimitrov
A: 

After searching for awhile, it seems that this is a difficult nut to crack in Java. Here's a thread that has an implementation, but it's too heavyweight for my needs: http://forums.sun.com/thread.jspa?threadID=341935&amp;start=15

JohnnyLambada
+2  A: 

The problem is that class files can be anywhere, including embedded within JAR files that have not yet been loaded (but are on the classpath). Solving this problem takes a lot more effort than using a custom ClassLoader, or leveraging the bundled one.

Hierarchy includes a simple API for probing details about any inheritance hierarchy. The full source code (Apache 2.0 License) provided includes:

  • Class Analyzer: a reusable, standalone API for performing general class file analysis.
  • Hierarchy: a complete application that uses Class Analyzer and prefuse to draw the graph.
  • prefuse example: shows how to use the prefuse API.
  • GraphViz example: shows how to display the hierarchy using dot.
  • BCEL example: shows how to use the BCEL API to analyze class files.
  • Fully documented source code (every package, class, and method).
  • SRS, technical documentation, and User Manual.
  • build example: an example for performing a variety of common tasks using ant.

The whole application is a lot more than what you need, however it should not be too difficult to extract and use just the class analyzer.

Dave Jarvis