当子类从超类继承 main()时,是否可以确定在命令行上调用的实际类?例如,请考虑以下两个类别,其中主要由 A 实施,并由 B 继承:
public class A {
public static void main(String[] args) throws Exception {
// Replace with <some magic here> to determine the class
// invoked on the command-line
final Class<? extends A> c = A.class;
System.out.println("Invoked class: " + c.getName());
final A instance = c.newInstance();
// Do something with instance here...
}
}
public class B extends A {
}
我们可以成功调用 B (即 B 确实'继承' main - 至少在任何意义上都可以继承静态方法) ,但我还没有找到一种方法来确定用户调用的实际类:
$ java -cp . A
Invoked class: A
$ java -cp . B
Invoked class: A
我最接近的是要求子类实现 main()并在超类中调用辅助方法,然后读取线程堆栈以确定调用类:
public class AByStack {
public static void run(String[] args) throws Exception {
// Read the thread stack to find the calling class
final Class<? extends AByStack> c = (Class<? extends AByStack>)
Class.forName(Thread.currentThread().getStackTrace()[2].getClassName());
System.out.println("Invoked class: " + c.getName());
final AByStack instance = c.newInstance();
// Do something with instance here...
}
public static void main(String[] args) throws Exception {
run(args);
}
}
public class BByStack extends AByStack {
public static void main(String[] args) throws Exception {
// Call the master 'run' method
run(args);
}
}
此方法有效:
$ java -cp . AByStack
Invoked class: AByStack
$ java -cp . BByStack
Invoked class: BByStack
但是我真的想消除子类实现 main()的要求(是的,叫我挑剔......)。我不介意它是否需要一些丑陋的代码,因为它将被实现一次并埋没在基类中,并且我最感兴趣的是Sun / Oracle VM,所以我愿意考虑使用私有sun。 misc class或类似的东西。
但我确实想避免平台依赖。例如,在Linux上,我们可以查看 / proc / self / cmdline ,但这当然不能移植到Windows(我不确定Mac OS - 我的Mac没有我现在要测试这个技巧)。我认为JNI和JVMTI出于同样的原因出局。我可能错了JVMTI,但在我看来它需要一个C包装器。如果没有,也许我们可以以某种方式使用该界面。
多年前在http://www.coderanch.com/t/375326/java/java/Getting-command-line-class问了这个问题。最好的答案是每个子类中都需要一个静态初始化程序块 - 对于我所演示的主调用 run()解决方案的子类作者的不同但类似的要求。但我没有看到最近的讨论;我希望当前的VM可能允许访问在讨论时无法获得的信息。
答案 0 :(得分:0)
大概你想要这个,以便可以使用不同的名称调用某些工具,但根据调用的名称,其行为大致相同?还是一些类似的魔法?
在这种情况下,你可以简单地在所有子类中使用一个实际的main()
方法,并委托给一个方法,该方法也采用被调用类的名称:
public class Super {
protected void doMain(String invokee, String... args) {
System.out.println("I was invoked as: " + invokee);
}
}
public class ToolA {
public static void main(String... args) {
new Super().doMain("ToolA", args); // or ToolA.class.getName() to be refactor-proof
}
}
答案 1 :(得分:0)
我想我只是修改run()方法的签名,而不是使用调用堆栈,在每个子类中实现main():
public class BByStack extends AByStack {
public static void main(String[] args) throws Exception {
// Call the master 'run' method
run(BByStack.class, args);
}
}
如果您正在使用默认构造函数进行实例化,我甚至只需传入
new BByStack()
我不知道有什么方法可以做你所要求的;该方法是静态的,因此您无法获得this.getClass()等的句柄;如果未在子类上定义该方法,则也无法从堆栈中获取该信息。
答案 2 :(得分:0)
由于我浪费了相当多的时间进行调查而不是问题,我会在这里发布我的结论。
首先,试图重申问题的基本原理:
我将参数处理,I / O和其他共享任务抽象为抽象超类,我希望其他人可以扩展。在执行参数解析和共享设置之后,超类中的静态方法实例化子类的实例并调用其 run()方法。
鼓励子类的作者实现 public static void main(String [])并调用超类的主入口点。但是,与所有子类实现 run()的要求不同,我们不能在编译时静态强制执行该要求(因为Java没有抽象静态方法的概念)。 / p>
所以我试图在超类中实现一个 main(String [])方法,该方法可以确定在命令行上请求的子类的名称并实例化相应的类。
我找到了两种特定于Sun / Oracle JVM的方法。
第一个使用内部sun.jvmstat类:
import java.lang.management.ManagementFactory;
import sun.jvmstat.monitor.MonitoredVmUtil;
import sun.jvmstat.monitor.VmIdentifier;
import sun.jvmstat.perfdata.monitor.protocol.local.LocalMonitoredVm;
...
public static String jvmstatMainClass() {
// Determine the VMID (on most platforms, this will be the PID)
final String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
// Connect to the virtual machine by VMID
final VmIdentifier vmId = new VmIdentifier(pid);
final LocalMonitoredVm lmVm = new LocalMonitoredVm(vmId, 1000);
// Find the requested main-class
String mainClass = MonitoredVmUtil.mainClass(lmVm, true);
// And detach from the VM
lmVm.detach();
return mainClass;
}
第二个使用Sun的 jps 实用程序:
import java.io.BufferedReader;
import java.io.InputStreamReader;
...
public static String jpsMainClass() {
// Determine the VMID (on most platforms, this will be the PID)
final String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
// Execute the 'jps' utility
final Process jps = Runtime.getRuntime().exec(new String[] { "jps", "-l" });
final BufferedReader br = new BufferedReader(new InputStreamReader(jps.getInputStream()));
// Parse the output of jps to find the current VM by PID
for (String line = br.readLine(); line != null; line = br.readLine()) {
final String[] split = line.split(" ");
if (pid.equals(split[0])) {
return split[1];
}
}
}
return null;
}
希望我浪费的时间对其他人有帮助。