user1378063
user1378063

Reputation: 283

Get all declared subclasses at runtime in Java?

Let's say I have a package that contains all subclasses of 'Device', like this one:

class TV extends Device{

   @override
   public void run() {
       //code goes here
   }

   private void moreMethods(String args){
       // more code
   }

}

In the main class it should be possible to instantiate and run every subclass of Device at runtime by calling everyone's 'run' method.

The benefit of this is to allow users to simply put new Device files into the package and said files will run automatically without having to edit the main class.

Is there a clean way to do this? I was thinking that maybe if I had a txt file in that package with every subclass name, it would be possible. Minor inconvenience being the user had to edit the file and add the new device's name.

Is there another way to achieve the same result? I'm interested in providing a way for users to add new device files without editing existent code.

Upvotes: 4

Views: 11498

Answers (4)

Maurice Perry
Maurice Perry

Reputation: 32831

Another method would consist in using a list of class in a text file (say META-INF/device-classes.txt), in each jar containing device classes. You can then list the URLs of each device-classes.txt file as follows:

ClassLoader cl = Thread.currentThread().getContextClassLoader();
Enumeration<URL> enm = cl.findResources("META-INF/device-classes.txt");
while (enm.hasMoreElements()) {
    runAllClassesIn(enm.nextElement());
}

Upvotes: 0

Joop Eggen
Joop Eggen

Reputation: 109613

There is the Java Service Provider Interface. That is a mechanism where - given an interface one can find all instances that implement this interface. Requirement for an implementation provider that its jar contains a file in META-INF subdirectory with as name the interface: java.lang.Runnable and as content one or more full class names.

This is more or less what you proposed with a .txt file. Only you can use separate jars.

You could require that all provided classes come in their jar with the SPI specification. Place the jars in a subdirectory plugins and have that path as Class-Path in the MANIFEST.MF of your own jar. As you know in a class path besides jars also directories are allowed.


More tricking is the alternative using the Reflections library. I find it a kind of abuse, but it offers just what you want, with some efficiency cost.


Alternatively you could require that the classes should be annotated, so you can scan all class annotations. A bit of coding effort required.

Upvotes: 1

user1869209
user1869209

Reputation:

Why do you wan't to use Reflection it is heavy cost to do so and could be error prone. You can use a DeviceProvider where the client can register a Device. He can fetch the Device by it's Class Object or it's canonical name. Then He can downcast to access custom method or deal with the Device instance and call common methods.

From Effective Java:

// Service interface
public interface Service {
... // Service-specific methods go here
}

// Service provider interface
public interface Provider {
      Service newService();
}

// Noninstantiable class for service registration and access
public class Services {

     private Services() { } // Prevents instantiation (Item 4)
     // Maps service names to services
     private static final Map<String, Provider> providers = new ConcurrentHashMap<String, Provider>();
     public static final String DEFAULT_PROVIDER_NAME = "<def>";
     // Provider registration API
     public static void registerDefaultProvider(Provider p) {
          registerProvider(DEFAULT_PROVIDER_NAME, p);
     }

     public static void registerProvider(String name, Provider p){
        providers.put(name, p);
     }

      // Service access API
     public static Service newInstance() {
         return newInstance(DEFAULT_PROVIDER_NAME);
     }

     public static Service newInstance(String name) {
       Provider p = providers.get(name);
         if (p == null)
           throw new IllegalArgumentException(
           "No provider registered with name: " + name);

        return p.newService();
     }

}

The advantage is that you can provide a default implementation and allow the client to register their custom ones. JDBC DriverManager works quite the same way

Upvotes: 0

sp00m
sp00m

Reputation: 48837

If reflection is an option, you could first find all the classes of the dedicated package (Can you find all classes in a package using reflection?, How to get all classes names in a package?), then use:

for (Class<?> candidateClass : foundClasses) {
    if (Device.class.isAssignableFrom(candidateClass)) {
        candidateClass.newInstance().run();
    }
}

Upvotes: 4

Related Questions