为什么这段显示图像的代码在构建到 jar 中时会给出“"error"”?

Why does this code displaying an image give an ""error"" when build into jar?

我想通过在 JLabel 上绘制 BufferedImage 来显示图像。

x/yOffset是在JLabel的中间画一个小图

如果我 运行 我 IDE 中的代码它工作正常并在我的 JFrame 上显示图像。

如果我现在将 Class 构建到 jar 文件中,它将不再起作用。

我在不使用 BufferedImage 的情况下尝试将图像设置为 JLabel 的图标,但这不是我想要做的。

这是我的图片代码Class:

public class ImageHQ extends JLabel {

BufferedImage img;

int xOffset=0;
int yOffset=0;


public ImageHQ(String path, int xOffset, int yOffset) {
    try {
        try {
            img = ImageIO.read(new File(getClass().getResource(path).toURI()));
        } catch (URISyntaxException ex) {
            Logger.getLogger(ImageHQ.class.getName()).log(Level.SEVERE, null, ex);
            errorMsg(ex.getMessage());
        }
    } catch (IOException ex) {
        Logger.getLogger(ImageHQ.class.getName()).log(Level.SEVERE, null, ex);
        errorMsg(ex.getMessage());
    }
    
    this.xOffset = xOffset;
    this.yOffset = yOffset;

}

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);

    Graphics2D g2d = (Graphics2D) g;

    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

    g.drawImage(img, 0+xOffset, 0+yOffset, null);

    repaint();
}

public void errorMsg(String msg) {
    JOptionPane.showMessageDialog(null, msg, "Fehler", JOptionPane.ERROR_MESSAGE);
}

}

PS: errorMsg方法也不给我报错

问题出在这里:

new File(getClass().getResource(path).toURI())

应用程序资源保证是一个单独的文件。 .jar 条目只是压缩存档的一部分。它不是硬盘上的单独文件。这就是为什么您不能使用文件来读取它的原因。

读取资源的正确方法是根本不要尝试将其转换为文件。 getResourcereturn一个URL;您可以将 URL 直接传递给 the ImageIO.read method that takes a URL:

img = ImageIO.read(ImageHQ.class.getResource(path));

请注意使用 class 文字 ImageHQ.class,而不是 getClass()。这保证您的资源是相对于您自己的 class 读取的,而不是可能位于不同包或不同 module.

中的子 class

从 InputStream 读取

一般来说,可能存在 URL 不够用的情况。您还可以使用 getResourceAsStream 获取从资源读取的打开的 InputStream。在你的情况下,你可以这样做:

try (InputStream stream = ImageHQ.class.getResource(path)) {
    img = ImageIO.read(stream);
}

但这将是 sub-optimal,因为 URL 可以提供 InputStream 无法提供的信息,例如文件名、内容类型和图像数据长度的预知信息。

资源路径

您传递给 getResourcegetResourceAsStream 的字符串参数实际上不是文件名。它是相对 URL 的路径部分。这意味着以 C:\ 开头的参数将始终失败。

因为参数是 URL,所以它在所有平台上总是使用正斜杠 (/) 来分隔路径组件。通常是针对调用getResource*方法的class对象的包进行解析;所以,如果 ImageHQcom.example 包中,则此代码:

ImageHQ.class.getResource("logo.png")

会在 .jar 文件中寻找 com/example/logo.png。

您可以选择以斜线开始 String 参数,这将强制它相对于 .jar 文件的根目录。上面可以写成:

ImageHQ.class.getResource("/com/example/logo.png")

ClassLoader 中也有 getResource* 方法,但不应该使用这些方法。始终使用 Class.getResource 或 Class.getResourceAsStream。 ClassLoader 方法在 Java 8 和更早版本中的功能相似,但从 Java 9 开始,Class.getResource 在模块化程序中更安全,因为它不会 运行 与模块封装冲突. (ClassLoader.getResource 不允许在其字符串参数的开头使用 /,并且始终假定该参数相对于 .jar 文件的根。)

空 return 值

所有 getResource* 方法将 return null 如果路径参数没有命名实际存在于 .jar 文件中的资源(或者如果资源在不存在的模块中允许阅读)。 NullPointerException 或 IllegalArgumentException 是这种情况的常见症状。例如,如果没有 logo.png 与 .jar 文件中的 ImageHQ class 在同一个包中,则 getResource 将为 return null,并将该 null 传递给 ImageIO.read 将导致在 IllegalArgumentException 中,如 ImageIO.read 文档中所述。

如果发生这种情况,您可以通过列出其内容来解决 .jar 文件的问题。有多种方法可以做到这一点:

  • 每个 IDE 的文件浏览器或文件树都可以检查 .jar 文件的内容。
  • 如果您的 JDK 在您的 shell 路径中,您可以简单地执行 jar tf /path/to/myapplication.jar.
  • 在 Unix 和 Linux 中,unzip -v /path/to/myapplication.jar 也可以工作,因为 .jar 文件实际上是一个包含几个 Java-specific 条目的 zip 文件。
  • 在 Windows 中,您可以复制 .jar 文件,将副本的扩展名更改为 .zip,然后使用任何压缩工具打开它,包括 Windows 文件资源管理器。

回到示例,如果您的 class 在 com.example 包中并且您的代码正在执行 ImageHQ.class.getResource("logo.png"),您将检查 .jar 文件的内容以获取com/example/logo.png条目。如果不存在,getResource 方法将 return null。

关于打印错误信息

ex.getMessage() 替换为 ex.toString()。通常情况下,异常的消息本身是没有意义的。您还应该将 ex.printStackTrace(); 添加到每个 catch 块(或添加记录堆栈跟踪的日志记录语句),以便您确切知道问题发生的位置。

关于绘画

从不 从 paintComponent 方法调用 repaint()。这将创建一个无限循环,因为 repaint() 将强制 Swing 绘画系统再次调用 paintComponent