【问题标题】:Is it possible to write a correct and portable console-output "hello world" in JAVA?是否可以在 JAVA 中编写正确且可移植的控制台输出“hello world”?
【发布时间】:2019-07-25 18:44:13
【问题描述】:

我是 JAVA 新手,还没有找到一种方法来编写一个“hello world”应用程序,该应用程序在所有平台上都可以正常工作而无需任何更改,还可以检测和处理输出错误。

特别是,我想将以下可移植的 C 程序移植到 JAVA:

“生成文件”:

.POSIX:

TARGETS = hello

.PHONY: all clean

all: $(TARGETS)

clean:
    -rm $(TARGETS)

“你好.c”:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
   if (puts("Hello, world!") >= 0 && fflush(0) == 0) return EXIT_SUCCESS;
   (void)fputs("An output error occurred.\n", stderr);
   return EXIT_FAILURE;
}

这个程序的工作原理是这样的:

$ make
c99 -O    hello.c   -o hello

$ ./hello
Hello, world!

$ ./hello > /dev/full
An output error occurred.

不幸的是,我还不能在 JAVA 中实现相同的功能。

System.out.println() 和 System.out.flush() 似乎都没有检查 I/O 错误,或者我只是无法捕捉到它们。

我已经尝试捕获 IOException 类型的异常,但编译器告诉我

error: exception IOException is never thrown in body of corresponding try statement

所以这显然不是正确的做法。

但是即使我能找到一种方法来捕获输出错误,我仍然不知道要使用什么退出代码可移植,因为 JAVA 似乎没有定义与 C 的 EXIT_FAILURE 相对应的东西。

考虑到 JAVA 一直被誉为应用程序开发的高度可移植系统,我觉得这难以置信。

如果一个简单的“hello world”的正确和可移植的实现已经超过了这个“一次编写,到处运行”平台的能力,那将是相当令人震惊的。

所以请帮助我创建一个行为与 C 版本完全相同并且仍然可移植的 JAVA 实现。

感谢 khelwood 和其他人,我设法创建了以下迄今为止效果最好的 JAVA 实现:

public class HelloWorld {
   private static class OutputError extends java.io.IOException { }

   private static void println(String s) throws OutputError {
      System.out.println(s);
      if (System.out.checkError()) throw new OutputError();
   }

   private static void flush() throws OutputError {
      System.out.flush();
      if (System.out.checkError()) throw new OutputError();
   }

   private static int getExitFailureCode() {
      try {
         return (new java.lang.ProcessBuilder("/bin/false")).start().waitFor();
      } catch (Exception dummy) {
         return 99;
      }
   }

   public static void main(String[] args) {
      try {
         println("Hello, world!");
         flush();
      } catch (OutputError dummy) {
         System.err.println("An output error occurred.");
         System.exit(getExitFailureCode());
      }
   }
}

但是,这并不是真正可移植的,因为它只能在提供“假”实用程序的 POSIX 系统上可靠地工作。

否则它将恢复为 99 的任意返回码,这显然是危险的,因为例如,某些平台可以将所有负值定义为失败代码,将所有其他值定义为成功代码。

【问题讨论】:

  • 你试过System.out.checkError()吗?
  • @khelwood 还没有。但正如文档所说“当底层输出流抛出 IOException 时,内部错误状态设置为 true”,并且 JAVA 编译器坚持不抛出此类异常,这似乎值得怀疑。不过我会试试的。
  • 不,编译器对象,因为 IOException 永远不会被抛出 您正在调用的方法。这并不意味着它不能发生在方法内部(直接或间接)并在内部处理。

标签: java runtime-error portability system.out correctness


【解决方案1】:

System.outPrintStream

PrintStream 的文档说:

与其他输出流不同,PrintStream 永远不会抛出 IOException;相反,异常情况只是设置一个内部标志,可以通过checkError 方法进行测试。

您可以检查System.out.checkError(),看看是否发生了错误。

如果您希望程序以非零状态退出,您可以使用System.exit

例如

if (System.out.checkError()) {
    System.err.println("An output error occurred.");
    System.exit(-1);
}

另见Is there a replacement for EXIT_SUCCESS and EXIT_FAILURE in Java?

【讨论】:

  • 这意味着 PrintStream 是记录方式 checkError() 应该工作的一个例外,因此可以解决我问题的前半部分。感谢那!但是我仍然需要一种可移植的方式来获取 System.exit() 的返回码。仅使用“1”作为常量没有帮助,因为并非所有平台都将返回码 1 视为错误。我需要一些象征性的东西。
  • @GuentherBrunthaler 我不认为 PrintStream 是一个例外,记录方式 checkError() 应该工作。我认为您误解了 checkError() 文档所说的内容。
  • 你可能是对的。它说“当底层输出流抛出 IOException [...] 并且调用 setError 方法时,[...] 错误状态设置为 true”。因此,PrintStream.println() 可能会显式调用 setError() 而不会引发异常。但是,println() 的文档没有提到这一点。
  • @GuentherBrunthaler 它说当 underlying 流抛出异常时,该异常被捕获,setError 被调用,并且该异常被(否则)抑制。所以 PrintStream 方法不会抛出 IOException,即使 underlying 流确实如此。
  • 接受您的回答。总而言之,我的问题的答案是“不”。当前的 JAVA 框架中没有可移植的方式来获取 EXIT_FAILURE。检查输出错误是可能的,但需要做很多工作,因为基本上大多数 PrintStream 方法都需要包装在使用 checkError() 并抛出异常(或以某种方式直接处理错误)的替代函数中。结合这一点,没有简单的方法可以在 JAVA 中编写一个正确且可移植的“Hello world”。
【解决方案2】:

对象System.outjava.io.PrintStream 的一个实例。您可以使用checkError() 来检查可能的错误。

【讨论】:

  • 这意味着我需要围绕 System.out.println() 和所有类似函数编写一个包装器,除非我想在每次调用这些函数后调用 checkError()。这很烦人!如果 JAVA 没有在最广泛使用的控制台输出功能中使用它们,那么它有什么例外?此外,我仍然需要知道在哪里可以得到 System.exit() 的特定于平台的正确错误退出代码。