尽管存在拼写错误,但只能猜测实际的“错误”是什么,但此代码在错误处理方面存在问题。特别是在资源的处置方面。
关于资源的讨论
什么是资源?
基本上:任何依赖底层操作系统级别资源的 Java 对象。主要是:IO 资源(输入和输出流、通道)、套接字。但更重要的是:如果你使用的“东西”有close、dispsose、shutdown 或任何类似的,它肯定会在内部保留资源。
有一些例外(特别是 ByteArrayInputStream 不包含任何资源但内存),但这些是实现细节:如果你坚持他们的接口(你应该,这是一个“合同”),每个流都应该关闭。
从 Java 7 开始,Java API 中的这些对象中的大多数都实现了AutoCloseable 接口,但许多第 3 方未必将其移植到他们的代码中(也许有些不能因为其他原因)。
作为我公司的代码审查员之一:一旦我没有看到对资源的close 方法的安全调用,我就会停止阅读并拒绝任何代码。我所说的安全是指在 finally 子句中,保证会被执行。
关于资源的经验法则
您的程序获得的任何资源都应该在finally 子句中释放(有些甚至添加 : 自己的)。
资源的典型生命周期是什么
嗯:
- 你得到它
- 你用它
- 你放开它
在您的代码中,即
ResourceObject myObject = null;
try {
myObject = getResource();
processResource(myObject);
} finally {
if(myObject != null) {
try {
myObject.close();
} catch (Exception e) {
// Usually there is nothing one can do but log
}
}
}
从 Java 7 开始,如果资源对象实现了AutoCloseable你有一种新的写法,它被称为“资源尝试”。
try(ResourceObject myObject = getResource()) {
process(myObject);
}
你看不到 finally,但它就在那里,在这种情况下编译器会为你编写 finally 子句。
多个资源呢?
嗯:多个资源,多个 finally。这个想法是在不同的 finally 子句中分离失败的原因。
假设您要复制文件...
public void myCopy() throws IOException {
InputStream source = null;
try {
source = new FileInputStream("yourInputFile");
// If anything bad happens, I have a finally clause that protects this now
OutputStream destination = null;
try {
destination = new FileOutputStream("yourOurputFile"); // If fails, my Input will be closed thanks to its own finally
performCopy(source, destination); // If this fail, my destination will also be closed thanks to its own finally
} finally {
if(destination!=null) { try { destination.close(); } catch (Exception e) {/* log*/ }}
}
} finally {
if(source!=null) { try { source.close(); } catch (Exception e) {/* log*/ }}
}
}
或者,使用 Java 7 语法,我们有更短的(免责声明:我现在没有 Java7,所以无法真正检查它是否编译):
try(
InputStream input = new FileInputStream("in");
OutputStream output = new FileOutputStream("out")) {
performCopy(input, output);
} catch(IOException e) {
// You still have to deal with it of course.
}
这是太多的样板!
是的。这就是我们有图书馆的原因。有人可能会争辩说你不应该编写这样的代码。使用标准的、行为良好的库,如 commons IO,或使用其中一种实用方法。或者更新的 JDK 方法,如 Files API,看看它是如何工作的。
Commons IO 有一套方便的IOUtils.closeQuietly() 方法用于关闭流。
尝试资源陷阱
“尝试使用资源”调用的后果比这更深一些。其中包括:如果我想对 finally 子句中出现的异常做一些事情怎么办?我如何将其与performCopy 期间发生的异常区分开来?
另一种情况是:这里发生了什么:
try(Reader reader = new InputStreamReader(new FileInputStream("in"), "an encoding that is not supported")) {
// Whatever
}
碰巧UnsupportedEncodingException 被抛出,但之后 FileInputStream 被实例化。但由于FileInputStream 不是try 子句的主题,它不会被关闭。你有一个文件描述符泄漏。尝试一千次,您的 JVM 将无法再打开文件,您的操作系统会告诉您“超出打开文件的最大数量”(ulimit 通常在 UNIX 中这样做)
回到你的插座
那么你的资源是什么?
首先,我们可以注意到您只有一个真正的资源,即您的 Socket 实例,因为 Socket javadoc 说 (javadoc):
* <p> Closing this socket will also close the socket's
* {@link java.io.InputStream InputStream} and
* {@link java.io.OutputStream OutputStream}.
所以你的输入和输出流被绑定到你的 Socket,这就足够了。
你的代码有什么问题
将 cmets 添加到您的原始代码中:
try{
echoSocket = new Socket("server.company.com", 8081);
out = new PrintWriter(echoSocket.getOutputStream(), true); // This can throw IOException
in = new BufferedReader(new InputStreamReader(echoSocket.getInputStream())); // Ditto
}
catch (Exception e) {
// If an exception was thrown getting any of the streams, we get there
System.err.println("Exception has occured");
// And you return without closing the socket. It's bad !
return;
}
// Let's assume everything worked, no exception.
process(out, in, echoSocket); // This may throw an exception (timeout, socket closed by peer, ...)
// that is uncaught (no catch clause). Your socket will be left unclosed as a result.
try {
out.close(); // This can fail
in.close(); // This too
echoSocket.close(); // And this too - although nothing you can do about it
}
catch(IOException e) {
// if out.close fails, we get here, and in.close and socket.close
// never got a chance to be called. You may be leaking resources
System.err.println("IOException has occurred.");
}
安全的实现
Socket echoSocket = null;
try {
// open socket,
echoSocket = new Socket("server.company.com", 8081); // protected by finally
out = new PrintWriter(echoSocket.getOutputStream(), true); // protected
in = new BufferedReader(new InputStreamReader(echoSocket.getInputStream())); // protected
process(out, in, echoSocket); // Still protected
} catch (Exception e) {
// Your current error handling
} finally {
// Anyway, this close will be called if needs be.
if(echoSocket != null) {
try { echoSocket.close(); } catch (Exception e) { /* log */}
// See javadoc, this has closed the in & out streams too.
}
}