JAR文件在命令行中可以正常运行,但直接双击就无法运行

编程语言 2026-07-09

我有一个桌面应用,需要从文本文件读取数据,并显示JPEG图片。这个在NetBeans中一切正常,打包成的JAR从命令行移动到非NetBeans目录后可以正确打开并运行。然而,当我双击启动应用时,界面会打开,但本应显示数据的字段却是空白的。

我看到其他帖子建议使用IOStreams而不是FileReaders和 FileWriters,因此我也改成这种方式重写了一遍。结果还是一样——在NetBeans中一切正常,在命令行通过 java -jar <myfile.jar> 运行也没问题,但双击 <myfile.jar> 图标时,GUI会显示出来,却没有读取到应有的数据,也没有显示任何图片。

我也看了大量关于类路径的文章,但如果真是这个问题,我的应用从命令行就应该也不能运行,对吧?

这是我正在尝试实现的一个简化版本——读取文本文件并在一个JPanel中显示其内容(我知道在这个简化版本中我引入的导入语句比实际需要的要多):

package com.steve;

import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.charset.StandardCharsets;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;

public class Testing extends JFrame{

   public Testing(){
      super("My Test App");
      EntryPanel panel = new EntryPanel();
      this.getContentPane().add(panel);
      this.setSize(800, 600);
      this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      this.setVisible(true);
   }

   public class EntryPanel extends JPanel{

   String strTextToDisplay;

    public EntryPanel(){
         super();
         try{
           this.strTextToDisplay = getTextFromFile();
         }
         catch(IOException ioe){
            System.out.println(ioe.getMessage());
         }
    }

    private String getTextFromFile() throws IOException{
        String fileName = "testfile.txt";
        var filePath = Paths.get(fileName);
        String myStr = "Here it is:\n";
        try(BufferedReader br = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)){
          String line;
          while((line = br.readLine()) != null){
             myStr = myStr.concat(line).concat("\n");
          }
        }
        System.out.println(myStr);
        return myStr;
    }

    @Override
    public void paintComponent(Graphics g){
      g.setColor(new Color(0, 0, 255));
      g.fillRect(0,0, this.getWidth(), this.getHeight());
      g.setColor(new Color(255, 100, 100));
      g.setFont(new Font("Serif", Font.BOLD, 15));
      g.drawString(strTextToDisplay, 20, 20);
   }
}


   public static void main(String[] args){
      System.out.println("In main");
      Testing myTest = new Testing();
   }

}

我的 manifest.txt 文件如下:

Manifest-Version: 1.0
Class-Path: com.resources/testfile.txt
Main-Class: com.steve.Testing
Created-By: 21.0.10 (Ubuntu)

解决方案

以下演示如何从JAR文件中加载一个“资源”。
(代码后的说明。)

package com.steve;

import java.io.BufferedReader;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;

public class Testing {

    public static void main(String[] args) {
        Testing instance = new Testing();
        Class<?> instanceClass = instance.getClass();
        URL url = instanceClass.getResource("/com/resources/testfile.txt");
        if (url == null) {
            System.err.println("ERROR: File not found.");
        }
        else {
            try {
                URI uri = url.toURI(); // throws java.net.URISyntaxRxception
                try (BufferedReader br = Files.newBufferedReader(Paths.get(uri))) {
                    String myStr = "Here it is:" + System.lineSeparator();
                    String line = br.readLine();
                    while (line != null) {
                        myStr = myStr.concat(line);
                        myStr = myStr.concat(System.lineSeparator());
                        line = br.readLine();
                    }
                    System.out.println(myStr);
                }
            }
            catch (IOException | URISyntaxException x) {
                x.printStackTrace();
            }
        }
    }
}

JAR中的文件路径相对于你调用方法 getResourceClass 来说是相对路径的。因此如果文件 testfile.txt 与Java类 Testing 位于同一个包中,那么传给方法 getResource 的参数就只是 testfile.txt。但由于它位于不同的包中,你需要使用“完整”路径,其中路径分隔符必须是斜杠,即 /。在上面的代码中,前导的 / 表示一个“完整”路径。
请注意,在JAR的 MANIFEST中不需要 [Class-Path] 属性。

备选方案

请查看旧的回答,但这里还有更多问题。

基本要点

“文件”位于jar内部,jar是一种zip文件。

这意味着 File(用于磁盘文件)不可用,但通用的 Path 也可以使用。同样也可以考虑使用 URL/URI

在底层,二进制字节读取可以通过一个 InputStream 来完成。

以文本读取可以通过一个 Reader 来实现,使用InputStream的某种 Charset 编码。

获取资源

在你的 com.steve.Testing 类中,资源 com.resources.testfile.txt 不能通过相对路径进行访问,但如下:

URL url = getClass().getResource("/com/resources/testfile.txt");
Path path = Paths.get(url.toURI());

这使用了 URI,它比 URL 更新且更通用。Path 是一个通用的实现,它维护着它所使用的文件系统,在这里内部是一个Zip文件系统。

private String getTextFromFile() throws IOException {
    URL url = getClass().getResource("/com/resources/testfile.txt");
    Path path = Paths.get(url.toURI());
    String content = Files.readString(path);
    System.out.println(content);
    return content;
}

正如你所见,Files 类具有非常好的功能,例如可以执行以下操作:

Path path2 = Paths.get("...");
Files.copy(path, path2);

通过这种方式,你的只读资源文件即可被复制以便编辑。

使用 Stringconcat/+= 可能会被编译器巧妙地优化,但对于较大的文件而言仍然不是一个好主意。

不过这段代码假设文件使用UTF-8编码。对Ubuntu来说没问题。

你也可以选择其他编码,但UTF-8更优,因为可以提供 Charset

    Charset charset = StandardCharsets.ISO_889_1;
    String content = Files.readString(pat, charset);

System.out.println 同样会把String(内部始终使用Unicode,UTF-8/UTF-16)转换为当前运行平台的编码。例如在Windows机器上,可能会出现希腊文字符的编码问题。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章