为什么在JDK 11中getResource()方法返回null?

11

简单的Java程序:

public static String loadText(String file) {
    StringBuilder finalString = new StringBuilder();

    InputStream in = null;
    BufferedReader reader = null;
    InputStreamReader isr = null;

    try{
        System.out.println("Text File: " + file);

        // Version 1
        //URL url = Thread.currentThread().getClass().getResource(file); 
        //in = url.openStream();

        // Version 2
        in = Class.class.getResourceAsStream(file);

        isr = new InputStreamReader(in);
        reader = new BufferedReader(isr);
        String line;
        while((line = reader.readLine()) != null) {
            finalString.append(line).append("//\n");
        }
    }
    catch(IOException e) {
        e.printStackTrace();
        System.exit(-1);
    }
    finally {
        try {
            if (isr != null) { isr.close(); }
            if (reader != null) { reader.close(); }
            if (in != null) { in.close(); }
            } catch (IOException e) { e.printStackTrace(); }
        }
    return finalString.toString();
}

getResourcegetResourceAsStream 方法在 JDK 8 (java-8-openjdk-amd64) 中工作正常,但是在 JDK 11 中始终返回 null

问题:为什么?我该如何解决?

  • 操作系统:Linux Mint 19 Tara x64
  • IDE:Eclipse 2018-12(4.10.0)

1
你正在使用的“file”参数的典型值是什么? - DodgyCodeException
请查看这个问题。 - Denis Zavedeev
5
Class.class.getResourceAsStream(...) 将尝试在 java.base 模块中定位资源。请将 "Class.class" 替换为您的类名,以便它可以在您的模块(或类路径)中找到它。 - Alan Bateman
2
此外,值得学习的是 try-with-resources 语句…… - Holger
3
@aAlexey a) try-with-resource已经成为语言的一部分十年了,Java开发人员应该无需进一步解释即可理解。它与任何其他高级语言结构(例如for-each循环或字符串连接,或使用OOP概念)没有区别。冗长并不意味着“自我说明”。b) 向后兼容性是指什么?您正在问有关Java 11的问题,但坚持要向Java 6甚至更早版本兼容?c) 使用try-with-resource会妨碍调试的哪些方面? - Holger
显示剩余2条评论
3个回答

20

我已经在MacOS上尝试了您的应用程序,使用openjdk 8和11,但两者都无法运行。我认为您需要查看[1]和[2],以了解getResourceAsStream如何工作。

TLDR:

  1. 如果路径是绝对路径(即以斜杠 - /开头),则class.getResourceAsStream()将在提供的路径中搜索

  2. 如果路径不是绝对路径(即不以斜杠开头),则class.getResourceAsStream()将在相应于包名称的构建路径中搜索,其中点被替换为斜杠

因此,它是否有效取决于2件事:

  1. 您的路径是否为绝对路径?
  2. file是否位于与类相同的包中?

基本上,在您提供的示例中,如果路径不是绝对路径,则永远无法工作,因为Class.class.getResourceAsStream()将始终将路径解析为java/lang/<file>,因此您的文件必须在系统包中。所以,您必须使用<MyClass>.class.getResourceAsStream()或者使用绝对路径。

[1]https://docs.oracle.com/javase/7/docs/api/java/lang/ClassLoader.html#getResource(java.lang.String)

[2] https://docs.oracle.com/javase/7/docs/api/java/lang/Class.html#getResource%28java.lang.String%29


更新

从Java SE 9开始,在命名模块中调用类上的getResourceXXX仅会在该模块中定位资源,不会像之前的版本一样搜索类路径。因此,当您使用Class.class.getResourceAsStream()时,它将尝试在包含java.lang.Class的模块中定位资源,即java.base模块。显然,您的资源不在该模块中,所以它返回null。

您必须使Java 9+在您的模块中搜索文件,这很可能是一个“未命名模块”。您可以通过将Class更改为在您的模块中定义的任何类来实现这一点,以便Java使用正确的类加载器。


如果我让你感到困惑,我很抱歉。是的,路径是绝对的。TXT文件在Java类文件的包文件夹中,并包含在构建路径中。文件路径:/configuration/options.txt - Alexey
好的,那就是因为Java模块的原因。我会更新我的答案。 - Svetlin Zarev
感谢您的解释。将Class.class.getResourceAsStream(file)更改为TextTools.class.getResourceAsStream(file)解决了错误。 - Alexey
1
哎呀,Java 9及更高版本中的模块行为很奇怪。改用getClass().getResourceXXX对我有用。 - Christopher
更新在此处是正确的答案。我们之前使用了Classloader.class.getResourceAsStream(),但它没有起作用。我们将其更改为我们包中写的类,然后它就能正常工作了。太好了! - markthegrea
1
@markthegrea 两种都是正确的,这取决于你使用的Java版本。 - Svetlin Zarev

7

使用ClassLoader可以有另一种解决方案。

ClassLoader.getSystemResourceAsStream(file)

从所有位置加载。我使用它来从仅包含资源而没有可执行代码的JAR文件中加载资源,因此以前的技巧行不通,因为在JAR中没有 SomeClass 来执行 SomeClass.class.getResourceAsStream()

我只是不理解内部原理,我还必须更改文件的路径。

我有一个JAR文件,在JAR存档的根目录中有文件"my-file.txt"。

对于Java 8,这起作用了

Class.class.getResourceAsStream("/my-file.txt");

对于Java 11, 即使文件位于根路径下,也必须删除前导的/
ClassLoader.getSystemResourceAsStream("my-file.txt");

-1

我也遇到了同样的问题。解决方法是在Java 8之后的版本中使用ClassPathResource

ClassPathResource resource = new ClassPathResource("/path/to/file.txt");
  • 将v1作为文件返回:
URL url = resource.getURL();
  • v2 作为流:
in = resource.getInputStream();

这是一个Spring特定的解决方案,不适用于Java本身。 - Mark Rotteveel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接