为特定类型的类声明String到Class的映射

Mar*_*ett 1 java generics reflection collections jar

我正在尝试围绕其他所有都具有main方法的其他类创建命令行包装器类。

我已经声明了字符串到类的映射,但是现在我想确保所有类都实现一个特定的接口。

这个想法是,您要给类别名或“命令名”作为第一个参数,然后调用该类的主类,并将其余的argv数组传递给它。

最初我有这个:

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 );
    ...
}};
Run Code Online (Sandbox Code Playgroud)

但是我想让用户在不使用args的情况下运行命令,并列出类及其操作。从以前的工作来看,大多数类确实都有描述。

我决定在一个界面中对此进行形式化:

public interface HasDescription {
    String getDescription();
}
Run Code Online (Sandbox Code Playgroud)

然后:

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 ...
}
Run Code Online (Sandbox Code Playgroud)

但是我不确定如何声明它,我有很多变体。这是一个无效的示例:

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 );
}};
Run Code Online (Sandbox Code Playgroud)

变体/教训:

  • 我试过了尖括号内没有前导字母的情况
  • 我都试过implementsextends
  • 即使在Java 7中,也不能只new LinkedHashMap<>()在内联声明时放

问题:

  • 理想情况下,.getDescription()将是静态的。
  • 我假设如果我具有正确的签名,则使用.get()将其从哈希中拉出,然后将其分配给一个类似声明的变量(没有顶层Mop <>)
  • 如果提取出适当限定的类变量,是否有一个快捷方式或特殊版本的反射,仅让我说((HasDescription)clazz).getDescription()吗?
  • 想知道我是在重新发明轮子,还是出于某种原因这是一个非常糟糕的主意
  • 在这种使用情况下,指定所有类都必须实现特定的接口是否可以为我带来任何收益?至少,它似乎允许编译器为我做一些检查。在运行时,我想知道获取合格的类引用是否比常规反射给我带来了特殊的好处。

为什么我要这样做?

  • 我正在让Maven生成完整的自包含.jar文件
  • 我希望人们仅使用java -jar my_jar.jar ...来运行它,因此我需要选择一个特定的类;你会和java -jar projname.jar command_name arg1 arg2 arg3
  • 如果只使用它,java -jar projname.jar您会得到一个类列表以及它们的作用
  • 另一种方法是像java -cp projname.jar com.foobar.package_a.ClassOne arg1 arg2 arg3这样运行每个命令,但这对于人们键入来说有点麻烦,并且每个类名都需要.sh和.bat。

Roh*_*ain 5

您不会使用这样的类型参数。类型参数可以在类级别方法级别声明。使用这样的字段声明,您必须使用通配符:

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 );
}};
Run Code Online (Sandbox Code Playgroud)

我都试过implementsextends

在泛型中,您仅使用extends关键字来表示这两个概念。

理想情况下,.getDescription()将是静态的。

不,你不能那样做。这不会给您当前所需的多态行为。

如果提取出适当限定的类变量,是否有一个快捷方式或特殊版本的反射,让我说些类似的东西( (HasDescription)clazz ).getDescription()

如果要这样调用方法,那么为什么要存储Class实例而不是实例本身?如果要存储Class实例,则必须使用Class#newInstance()方法实例化它,并确保所有实现类都为此提供了0-arg构造函数。

在这种使用情况下,指定所有类都必须实现特定的接口是否可以为我带来任何收益?

如果要存储Class实例,则必须这样做。但是,如果您自己存储实例,则只需保留引用即可HasDescription

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());
Run Code Online (Sandbox Code Playgroud)

然后您可以执行以下操作:

map.get("command1").getDescription();
Run Code Online (Sandbox Code Playgroud)