우리 모두 알고 있듯이 스트림, 파일 또는 소켓 연결과 같은 열려 있는 모든 시스템 리소스는 개발자가 수동으로 닫아야 합니다. 그렇지 않으면 프로그램이 계속 실행되면서 리소스 누출이 누적됩니다. .대규모 생산사고.
자바의 세계에는 무술을 연습하다 미쳐버렸을 때에도 자생 작전을 할 수 있게 해주는 마침내라는 쿵푸가 있습니다. 고대에는 리소스 닫기를 처리하는 코드가 일반적으로 finally 블록으로 작성되었습니다. 그러나 동시에 여러 리소스를 열면 다음과 같은 악몽 같은 시나리오가 발생합니다.
public class Demo { public static void main(String[] args) { BufferedInputStream bin = null; BufferedOutputStream bout = null; try { bin = new BufferedInputStream(new FileInputStream(new File("test.txt"))); bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt"))); int b; while ((b = bin.read()) != -1) { bout.write(b); } } catch (IOException e) { e.printStackTrace(); } finally { if (bin != null) { try { bin.close(); } catch (IOException e) { e.printStackTrace(); } finally { if (bout != null) { try { bout.close(); } catch (IOException e) { e.printStackTrace(); } } } } } } }
맙소사! ! ! 비즈니스 코드보다 자원 폐쇄 코드가 더 많습니다! ! ! BufferedInputStream
을 닫아야 할 뿐만 아니라, BufferedInputStream
을 닫을 때 예외가 발생하면 BufferedOutputStream
도 올바르게 닫혀야 하기 때문입니다. 그래서 우리는 finally 중첩 방법을 사용해야 합니다. 더 많은 리소스가 열릴수록 최종적으로 중첩이 더 깊어질 것이라고 상상할 수 있습니다! ! !
더 역겨운 것은 Python프로그래머가 이 문제에 직면하고 실제로 웃으면서 애교 있게 말했다는 것입니다. “이건 전혀 생각할 필요가 없어요~ ”:
하지만 당황하지 마세요, 형제님! 프로그래머가 코드 자체를 닫기 위해 리소스를 작성할 필요 없이 Java 1.7의 새로운 리소스 사용 시도 구문 설탕을 사용하여 리소스를 열 수 있습니다. 엄마는 더 이상 내 글씨가 깨질까봐 걱정하지 않으셔도 돼요! try-with-resource를 사용하여 이전 예제를 다시 작성했습니다.
public class TryWithResource { public static void main(String[] args) { try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt"))); BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) { int b; while ((b = bin.read()) != -1) { bout.write(b); } } catch (IOException e) { e.printStackTrace(); } } }
아주 간단하지 않나요? 흥미롭지 않나요? 더 이상 Python 프로그래머가 무시하지 않습니다! 자, 구현 원리와 내부 메커니즘은 아래에서 자세히 설명하겠습니다.
try-with-resource와 협력하려면 리소스가 AutoClosable
인터페이스를 구현해야 합니다. 이 인터페이스의 구현 클래스는 close
메서드를 재정의해야 합니다:
public class Connection implements AutoCloseable { public void sendData() { System.out.println("正在发送数据"); } @Override public void close() throws Exception { System.out.println("正在关闭连接"); } }
호출 클래스:
public class TryWithResource { public static void main(String[] args) { try (Connection conn = new Connection()) { conn.sendData(); } catch (Exception e) { e.printStackTrace(); } } }
실행 후 출력 결과:
正在发送数据 正在关闭连接
결과를 통해 다음을 수행할 수 있습니다. see, close 메소드가 자동으로 호출됩니다.
그럼 어떻게 하는 걸까요? 똑똑한 여러분은 사실 이 모든 것이 컴파일러 마스터에 의해 발생한다는 것을 짐작했을 것입니다. 지금 바로 예제의 클래스 파일을 디컴파일해 보겠습니다.
public class TryWithResource { public TryWithResource() { } public static void main(String[] args) { try { Connection e = new Connection(); Throwable var2 = null; try { e.sendData(); } catch (Throwable var12) { var2 = var12; throw var12; } finally { if(e != null) { if(var2 != null) { try { e.close(); } catch (Throwable var11) { var2.addSuppressed(var11); } } else { e.close(); } } } } catch (Exception var14) { var14.printStackTrace(); } } }
15~27행에서 컴파일러가 자동으로 finally 블록을 생성하고 그 안에 있는 리소스의 close 메서드를 호출하는 것을 보셨나요? 예제 close 메소드는 런타임에 실행됩니다.
방금 디컴파일된 코드(21행)에 고대에 작성된 코드보다 addSuppressed
가 하나 더 있다는 것을 주의깊게 살펴보신 분들은 아실 거라 믿습니다. 이 코드의 목적을 이해하기 위해 지금 예제를 약간 수정해 보겠습니다. 이제 코드를 고대에 수동으로 예외를 닫는 방식으로 다시 변경하고 sendData
및 close
및 메서드 >:
public class Connection implements AutoCloseable { public void sendData() throws Exception { throw new Exception("send data"); } @Override public void close() throws Exception { throw new MyException("close"); } }
public class TryWithResource { public static void main(String[] args) { try { test(); } catch (Exception e) { e.printStackTrace(); } } private static void test() throws Exception { Connection conn = null; try { conn = new Connection(); conn.sendData(); } finally { if (conn != null) { conn.close(); } } } }
basic.exception.MyException: close at basic.exception.Connection.close(Connection.java:10) at basic.exception.TryWithResource.test(TryWithResource.java:82) at basic.exception.TryWithResource.main(TryWithResource.java:7) ......
메서드에서 발생한 close
은 무시되고 MyException
에서 발생한 sendData
은 무시됩니다. 이를 예외 보호라고 합니다. 예외 정보가 손실되므로 예외 마스킹으로 인해 특정 버그를 찾기가 극도로 어려워질 수 있습니다. 프로그래머는 이러한 버그를 찾기 위해 초과 근무를 해야 합니다. 다행스럽게도 이 문제를 해결하기 위해 Java 1.7부터 업계에서는 예외 마스킹을 피하기 위해 하나의 예외를 다른 예외에 연결하는 것을 지원하는 Exception
클래스에 새로운 Throwable
메서드를 추가했습니다. 그러면 차단된 예외 정보는 어떤 형식으로 출력되나요? 방금 try-with-resource로 래핑된 기본 메소드를 다시 실행해 보겠습니다. addSuppressed
java.lang.Exception: send data at basic.exception.Connection.sendData(Connection.java:5) at basic.exception.TryWithResource.main(TryWithResource.java:14) ...... Suppressed: basic.exception.MyException: close at basic.exception.Connection.close(Connection.java:10) at basic.exception.TryWithResource.main(TryWithResource.java:15) ... 5 more
프롬프트가 있어 이 예외가 실제로 두 개의 예외로 구성되어 있음을 알려줍니다. Suppressed
은 억제된 예외입니다. 축하해요! MyException
메소드의 내부 구현 논리를 이해해야 합니다. 그렇지 않으면 리소스가 계속 누출될 수 있습니다. close
데코레이터 패턴이 사용됩니다. 데코레이터의 메서드가 호출되면 본질적으로 데코레이터 내부에 래핑된 스트림의 close
메서드를 호출하는 것입니다. 예: close
public class TryWithResource { public static void main(String[] args) { try (FileInputStream fin = new FileInputStream(new File("input.txt")); GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(new File("out.txt")))) { byte[] buffer = new byte[4096]; int read; while ((read = fin.read(buffer)) != -1) { out.write(buffer, 0, read); } } catch (IOException e) { e.printStackTrace(); } } }
在上述代码中,我们从FileInputStream
中读取字节,并且写入到GZIPOutputStream
中。GZIPOutputStream
实际上是FileOutputStream
的装饰器。由于try-with-resource的特性,实际编译之后的代码会在后面带上finally代码块,并且在里面调用fin.close()方法和out.close()方法。我们再来看GZIPOutputStream
类的close方法:
public void close() throws IOException { if (!closed) { finish(); if (usesDefaultDeflater) def.end(); out.close(); closed = true; } }
我们可以看到,out变量实际上代表的是被装饰的FileOutputStream
类。在调用out变量的close
方法之前,GZIPOutputStream
还做了finish
操作,该操作还会继续往FileOutputStream
中写压缩信息,此时如果出现异常,则会out.close()
方法被略过,然而这个才是最底层的资源关闭方法。正确的做法是应该在try-with-resource中单独声明最底层的资源,保证对应的close
方法一定能够被调用。在刚才的例子中,我们需要单独声明每个FileInputStream
以及FileOutputStream
:
public class TryWithResource { public static void main(String[] args) { try (FileInputStream fin = new FileInputStream(new File("input.txt")); FileOutputStream fout = new FileOutputStream(new File("out.txt")); GZIPOutputStream out = new GZIPOutputStream(fout)) { byte[] buffer = new byte[4096]; int read; while ((read = fin.read(buffer)) != -1) { out.write(buffer, 0, read); } } catch (IOException e) { e.printStackTrace(); } } }
由于编译器会自动生成fout.close()
的代码,这样肯定能够保证真正的流被关闭。
怎么样,是不是很简单呢,如果学会了话
위 내용은 Java의 리소스 사용 샘플 코드에 대한 자세한 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!