Question

I'm trying to do a command line wrapper class around some other other classes that all have a main method.

I've declared a map of Strings to classes, but now I'd like to insure that all the classes implement a particular interface.

The idea is that you'd give the a class alias or "command name" as the first argument, then I'd call the main of that class and pass the rest of the argv array to it.

Initially I had this:

static final Map<String, Class> CLASSES = new LinkedHashMap<String,Class>() {{
    put( "command1", com.foobar.package_a.ClassOne.class   );
    put( "command2", com.foobar.package_a.ClassTwo.class   );
    put( "command3", com.foobar.package_b.ClassThree.class );
    ...
}};

But I wanted to let users run the command with no args and list the classes and what they do. And from previous work, most of the classes did have a description.

I decided to formalize this in an interface:

public interface HasDescription {
    String getDescription();
}

And then:

public class ClassOne implements HasDescription {
    // I'd prefer static, but that's a different concern
    public String getDescription() {
        return "check files for gophers";
    }
    ... rest of class ...
}

But I'm not sure how to declare it, I've had many variations. Here's one example of what doesn't work:

static final Map<String, Class<T implements HasDescription>> CLASSES = new LinkedHashMap<String,Class<T implements HasDescription>>() {{
    put( "command1", com.foobar.package_a.ClassOne.class   );
    put( "command2", com.foobar.package_a.ClassTwo.class   );
    put( "command3", com.foobar.package_b.ClassThree.class );
}};

Variations / Lessons:

  • I've tried with an without a leading letter inside the angle brackets
  • I've tried both implements and extends
  • Even in Java 7 you can't just put new LinkedHashMap<>() when declaring it inline

Questions:

  • Ideally .getDescription() would be static.
  • I assume that if I had the correct signature, then pulling it out of the hash with .get() I'd assign to a similarly declared variable (sans the top level Mop< >)
  • If a pull out a properly qualified class variable, is there a shortcut or special version of reflection that just lets me say something like ( (HasDescription)clazz ).getDescription() ?
  • Wondering if I'm either reinventing the wheel, or that there's some reason this is a horribly bad idea
  • Does specifying that all classes must implement a particular interface buy me anything in this usage scenario? At a minimum, seems like it lets the compiler do some checking for me. At runtime, I'm wondering if getting a qualified class reference gives me an special advantage over regular reflection.

Why an I doing this?

  • I'm having Maven produce a full, self contained .jar file
  • I'd like folks to run it with just java -jar my_jar.jar ... so I need to choose one specific class; you'd run with java -jar projname.jar command_name arg1 arg2 arg3
  • If you run with just java -jar projname.jar you get a list of classes and what they do
  • The alternative is to run each command as java -cp projname.jar com.foobar.package_a.ClassOne arg1 arg2 arg3 but this is a bit ugly for folks to type in, and would require .sh and .bat for each class name.
Was it helpful?

Solution

You don't use type parameters like that. The type parameters can either be declared at Class level or method level. With field declaration like that, you have to use wildcards:

static final Map<String, Class<? extends HasDescription>> CLASSES = new LinkedHashMap<String,Class<? extends HasDescription>>() {{
    put( "command1", com.foobar.package_a.ClassOne.class   );
    put( "command2", com.foobar.package_a.ClassTwo.class   );
    put( "command3", com.foobar.package_b.ClassThree.class );
}};

I've tried both implements and extends

In generics, you just use extends keyword to represent both the concepts.

Ideally .getDescription() would be static.

No you can't do that. That would not give you polymorphic behaviour that you want currently.

If a pull out a properly qualified class variable, is there a shortcut or special version of reflection that just lets me say something like ( (HasDescription)clazz ).getDescription() ?

If you want to invoke the method like that, then why are you storing the Class instance instead of the instance itself? If you are storing the Class instance, you would have to instantiate it using Class#newInstance() method, and ensure that all the implementating classes provide a 0-arg constructor for that.

Does specifying that all classes must implement a particular interface buy me anything in this usage scenario?

If you're storing the Class instance, then you have to do that. But if you store the instances themselves, then just keeping the reference as HasDescription would work fine:

static final Map<String, HasDescription> map = new LinkedHashMap<>();
map.put("command1", new com.foobar.package_a.ClassOne());
map.put("command2", new com.foobar.package_a.ClassTwo());

and then you can do:

map.get("command1").getDescription();

OTHER TIPS

You would not declare the Interface in your string to class map. You can see if a particular class implements an interface by using something like:

Class<?> testClass = [ lookup your class, or test class before adding to  your map];
boolean isProperInterface = HasDescription.class.isAssignableFrom(testClass);

(Under different circumstances you might check to see if an object is an instance of a class or interface by using

 boolean isGood = object instanceof [class or interface name]
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top