我正在使用以下代码将我的Java应用程序与Mac OS X集成:
if (System.getProperty("os.name").equals("Mac OS X"))
{
System.setProperty("com.apple.mrj.application.apple.menu.about.name", "JoText"); // Program name
com.apple.eawt.Application.getApplication().setDockIconImage(new javax.swing.ImageIcon( JotextMain.class.getResource("icons/Icon.png") ).getImage()); // Dock icon
System.setProperty("apple.laf.useScreenMenuBar", "true"); // Use global menu bar
com.apple.eawt.Application.getApplication().addApplicationListener(new MacAboutAndQuit()); //Make "Quit" and "About" options work
}
在Mac OS X上,它运行正常。但是在Ubuntu上:
Exception in thread "main" java.lang.NoClassDefFoundError: com/apple/eawt/ApplicationListener
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Class.java:2451)
at java.lang.Class.getMethod0(Class.java:2694)
at java.lang.Class.getMethod(Class.java:1622)
at sun.launcher.LauncherHelper.getMainMethod(LauncherHelper.java:494)
at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:486)
Caused by: java.lang.ClassNotFoundException: com.apple.eawt.ApplicationListener
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:423)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:356)
... 6 more
当我注释掉该代码时,它会在Ubuntu上正常启动。 奇怪,因为它在执行Mac OS X特定操作之前首先检查Mac OS X. 我没有Windows,但是我的朋友使用Windows也报告我的.jar没有启动,而是从Java虚拟机启动器发出“A Java Exception has Ocured”消息。在学校的Windows PC上,同样的问题。
造成这种情况的原因是什么?
答案 0 :(得分:3)
if
块的第二行是问题(以及第四行)。即使您的代码没有调用该函数,JVM仍然必须尝试在包含该com.apple.eawt.Application
语句的任何类的初始化时加载if
类,这仅仅是因为它的引用。当你不在Mac上时,该类不存在,因此出错。
解决方法是使用Reflection来进行该函数调用。这应该可以防止类加载器在实际调用之前尝试加载该类。
示例强>
javax.swing.ImageIcon icon = ...
Class c = Class.forName("com.apple.eawt.Application");
Method m = c.getMethod("getApplication");
Object applicationInstance = m.invoke(null);
m = applicationInstance.getClass().getMethod("setDockIconImage", javax.swing.ImageIcon.class);
m.invoke(applicationInstance,icon);
这是与if
块中第二行相对的Reflection。面对良好的开发实践,有很多关于此的东西 - 如果类/方法名称发生变化,您将不会收到编译器警告,但它确实允许您对特定类型进行调用,而无需在源代码中直接引用它们。这可以防止类加载器加载那些特定于操作系统的类,直到你实际进入if
语句。
为了清理这一点,您可以创建一个特殊的类来处理所有Mac-OS特定的东西,在单个方法中包含该功能,然后使用Reflection调用该方法,就像这个例子一样 - 这将为您节省其中一个Method m = ...
风格的电话。
反思警告
除了这段代码丑陋且不易维护之外,反射非常缓慢。它最适用于这样的情况,您可能会在程序期间初始化一次。通常情况下,如果不是出于可维护性问题,反射是您希望远离性能的原因。
阅读本文以获取有关反射性能的更多信息:
更优雅的解决方案
我不是用反射代码替换if
块内的所有内容,而是创建一个接口,如下所示:
interface SpecificPlatform{
void initialize(javax.swing.ImageIcon icon);
}
然后,我将为Mac实现它:
class MacOSX implements SpecificPlatform{
@Override
public void initialize(javax.swing.ImageIcon icon){
System.setProperty("com.apple.mrj.application.apple.menu.about.name", "JoText");
Application.getApplication().setDockIconImage(new javax.swing.ImageIcon(
JotextMain.class.getResource("icons/Icon.png") ).getImage()); // Dock icon
System.setProperty("apple.laf.useScreenMenuBar", "true"); // Use global menu bar
Application.getApplication().addApplicationListener(new MacAboutAndQuit());
}
}
现在......将原始代码更改为以下内容:
javax.swing.ImageIcon icon = ...
SpecificPlatform sp = null;
if (System.getProperty("os.name").equals("Mac OS X"))
sp = (SpecificPlatform)(Class.forName("pkg.name.MacOSX").getConstructor().
newInstance());
}
//Check for other OS-specific classes here ...
if(sp != null) sp.initialize(icon);
现在,每个操作系统只有一个丑陋的Reflection调用,而不是为每个特定于操作系统的方法调用做出一堆丑陋的Reflection调用。