1

我正在使用以下代码将我的 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 虚拟机启动器发出“发生 Java 异常”消息。在学校的 Windows PC 上,同样的问题。

这是什么原因造成的?

4

1 回答 1

4

你的块的第二行if是问题(以及第四行)。即使您的代码从未调用过该函数,JVM 仍然必须尝试com.apple.eawt.Application在初始化包含该语句的任何类时加载该类,这if仅仅是因为对它的引用。当您不在 Mac 上时,该类不存在,因此出现错误。

一种解决方法是使用反射来进行该函数调用。这应该可以防止类加载器在实际调用之前尝试加载该类。

例子

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块中第二行的反射等效项。面对良好的开发实践,这有很多问题 - 如果类/方法名称发生更改,您将不会收到编译器警告,但它确实允许您对特定类型进行调用,而无需在源代码中直接引用它们。这可以防止类加载器加载那些特定于操作系统的类,直到您实际在if语句中。

为了稍微清理一下,您可以创建一个处理所有 Mac-OS 特定内容的特殊类,将该功能包装在一个方法中,然后像这个示例一样使用反射调用该方法 - 这将为您节省一个Method m = ...风格的电话。

反射注意事项

除了这段代码丑陋且不易维护之外,Reflection 非常慢。最好在这种情况下使用,在这种情况下,您可能会在程序执行期间初始化一次。通常,出于性能原因(如果不是出于可维护性问题),您希望远离反射。

阅读本文以获取有关反射性能的更多信息:

Java 反射性能

更优雅的解决方案

我不会用反射代码替换该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);

现在每个操作系统只有一个丑陋的反射调用,而不是为您必须进行的每个特定于操作系统的方法调用进行一大堆丑陋的反射调用。

于 2013-06-05T18:45:02.450 回答