问题就出在这里:
new File(getClass().getResource(path).toURI())
应用程序资源是not保证是一个单独的文件。 .jar 条目只是压缩存档的一部分。它不是硬盘驱动器上的单独文件。这就是为什么你不能使用 File 来读取它。
读取资源的正确方法是根本不尝试将其转换为文件。getResource
返回一个 URL;你可以直接将该 URL 传递给采用 URL 的 ImageIO.read 方法:
img = ImageIO.read(ImageHQ.class.getResource(path));
注意类文字的使用,ImageHQ.class
,而不是 getClass()。这保证了您的资源是相对于您自己的类读取的,而不是相对于可能位于不同包或不同包中的子类module.
从输入流读取
一般来说,在某些情况下 URL 可能不够。您还可以使用获取资源作为流获取从资源读取的打开的InputStream。对于你的情况,你可以这样做:
try (InputStream stream = ImageHQ.class.getResource(path)) {
img = ImageIO.read(stream);
}
但这并不是最优的,因为 URL 可以提供 InputStream 无法提供的信息,例如文件名、内容类型和图像数据长度的预先知识。
资源路径
您传递给的 String 参数getResource
and getResourceAsStream
实际上不是文件名。它是相对 URL 的路径部分。这意味着一个论证开始于,比如说,C:\
总会失败。
由于参数是 URL,因此它始终使用正斜杠 (/
) 在所有平台上分隔路径组件。通常,它是针对调用 getResource* 方法的类对象的包来解析的;因此,如果ImageHQ
曾在com.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 不允许/
在其 String 参数的开头,并且始终假设该参数相对于 .jar 文件的根目录。)
返回值为空
所有 getResource* 方法都会返回null
如果路径参数未命名 .jar 文件中实际存在的资源(或者该资源位于不允许读取的模块中)。 NullPointerException 或 IllegalArgumentException 是这种情况的常见症状。例如,如果没有logo.png
与 .jar 文件中的 ImageHQ 类位于同一包中,getResource 将返回 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 特定条目的 zip 文件。
- 在 Windows 中,您可以制作 .jar 文件的副本,将副本的扩展名更改为 .zip,然后使用任何 zip 工具(包括 Windows 文件资源管理器)打开它。
回到这个例子,如果你的班级在com.example
包和你的代码正在做ImageHQ.class.getResource("logo.png")
,您将检查 .jar 文件的内容com/example/logo.png
入口。如果不存在,getResource 方法将返回 null。
关于打印错误消息
Replace ex.getMessage()
with ex.toString()
。通常情况下,异常消息本身是没有意义的。您还应该添加ex.printStackTrace();
每一个catch
块(或添加记录堆栈跟踪的日志语句),这样您就可以准确地知道问题发生的位置。
关于绘画
Never call repaint()
来自paintComponent 方法。这会创建一个无限循环,因为repaint()
将强制 Swing 绘画系统调用paintComponent
again.