글을 작성하기 전에 망나니 개발자님에게 감사의 인사를 전합니다.
완벽한 설명이라고 생각이 됩니다. 출처에 망나니 개발자님의 설명 링크를 남겨놨습니다.
기존에는 ObjectOutputStream, ObjectInputStream, FileInputStream, BufferedInputStream 등과 같이 Java 프로그래밍에서 파일 입출력과 직렬화(serialization)에 관련된 클래스는 객체를 생성해서 마지막에 close() 메서드를 사용해서 객체를 삭제했습니다.
예시가 있는 글입니다.
https://pabeba.tistory.com/127
public class TestObjectDeSerialization2 {
public static void main(String[] args) {
//객체 역직렬화 : person.obj 에 저장된 정보를 역직렬화하여
// 자바 메모리 상의 객체로 복원
String personPath=DirInfo.OUTPUT_DIR2+"person.obj";
try {
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(personPath));
Person p=(Person)ois.readObject();//Object 타입으로 리턴되므로 Person 타입으로 객체 캐스팅이 필요
// password는 transient 명시되어 직렬화 되지 않았음, 그러므로 null 출력
System.out.println(p.getName()+" "+p.getMoney()+" "+p.getPassword());
ois.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
이런 식으로 따로 닫아줘야 하는 번거로움이 있고 또 이렇게 코드를 작성하면 동작이 되지 않는 부분도 있다고 합니다.
이러한 단점외에도 단점이 더 있답니다.
- 자원 반납에 의해 코드가 복잡해짐
- 작업이 번거로움
- 실수로 자원을 반납하지 못하는 경우 발생
- 에러로 자원을 반납하지 못하는 경우 발생
- 에러 스택 트레이스가 누락되어 디버깅이 어려움
이러한 문제점을 해결하기 위해서 Java7부터 try-with-resources 문법이 추가되었다고 합니다.
try-with-resources 사용 코드 예시
public class TestObjectDeSerialization2 {
public static void main(String[] args) {
String personPath = "person.obj";
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(personPath));) {
Person p = (Person) ois.readObject();
System.out.println(p.getName() + " " + p.getMoney() + " " + p.getPassword());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
위의 코드와 다르게 close() 메서드를 사용하지 않고 있습니다.
이렇게 코드를 작성하면 코드가 유연해지고, 누락되는 에러 없이 모든 에러를 잡을 수 있게 됩니다.
AutoCloseable 인터페이스
이렇게 코드를 작성하게 될 수 있게 된 이유는 AutoCloseable 인터페이스를 구현하여 기존의 Closeable에 부모 인터페이스로 AutoCloseable을 추가했다고 합니다.
먼저 만들어진 Closeable 인터페이스에 부모 인터페이스인 AutoCloseable을 추가함으로써 하위 호환성을 100% 달성함과 동시에 변경 작업에 대한 수고를 덜었습니다. 만일 Closeable아래 AutoCloseable이 생기면 코드를 변경해야 하는 이슈가 발생하여 Java 업그레이드를 진행하지 않겠죠?
위의 작성해 놓은 문제들의 예시를 직접 보면서 try-with-resources 를 사용하는 이유를 보겠습니다.
try-catch-finally를 이용하여 close() 메서드 이용 문제 예시
public class TestTryWithResources implements AutoCloseable {
@Override
public void close() throws Exception {
System.out.println("close 메서드 실행");
throw new IllegalStateException();
}
public void hosose() {
System.out.println("hosose 화이팅");
throw new IllegalStateException();
}
}
이렇게 AutoCloseable 인터페이스를 상속받는 Class를 생성해주고 시작하겠습니다.
close() 메서드랑 hosoe() 메서드를 만들고 그 안에는 IllegalStateException() 에러를 발생하게 합니다.
실행 코드 예시(hosose 메서드에 작성한 에러 발생 안함)
public static void main(String[] args) throws Exception {
TestTryWithResources testTryWithResources = null;
try {
testTryWithResources = new TestTryWithResources();
testTryWithResources.hosose();
} finally {
if (testTryWithResources != null)
testTryWithResources.close();
}
}
이상하게도 hosose 메서드에 작성한 에러는 발생하지 않는 기이한 현상이 일어나게 됩니다.
만약 이러한 문제가 소프트웨어 운영 중에 발생했다면 원인을 파악하는데 상당히 많은 시간을 들여야 할 수도 있습니다....
따라서, try-with-resources를 사용해야 합니다.
try-with-resources 사용 예시코드(hosose 메서드 에러 발생)
public static void main(String[] args) throws Exception {
try (TestTryWithResources testTryWithResources = new TestTryWithResources();) {
testTryWithResources.hosose();
}
}
이번에는 try-with-resources를 이용해서 실행했는데 hosose 메서드에 있는 에러와 close 메서드에 있는 에러 둘 다 발생했습니다! 정말 신기하고 잘 만들었네요.
이렇게 사용하면 소프트웨어 운영 중에도 어디서 에러가 발생했는지 알 수 있겠습니다.
다음은 여러 개의 자원을 사용했을 때 에러 발생이 하나만 되는 경우를 보겠습니다.
여러개 자원 사용(close 메서드 에러 여러개 발생 안 함)
public class TestTryWithResources implements AutoCloseable {
@Override
public void close() throws Exception {
System.out.println("close 메서드 실행");
throw new IllegalStateException();
}
public void hosose() {
System.out.println("hosose 화이팅");
}
}
이번에는 close() 메서드에만 에러를 발생해 보겠습니다.
public static void main(String[] args) throws Exception {
TestTryWithResources resource1 = null;
TestTryWithResources resource2 = null;
try {
resource1 = new TestTryWithResources();
resource2 = new TestTryWithResources();
resource1.hosose();
resource2.hosose();
} finally {
if (resource1 != null) {
resource1.close();
}
if (resource2 != null) {
resource2.close();
}
}
}
위의 try-catch-finally 문장을 실행해 보면...
이렇게 에러가 하나밖에 나오지 않습니다.
이게 에러가 두 개 다 나오게하려면 close 메서드를 실행하는 코드에도 try-catch 문장을 사용해야합니다. 그래야 에러가 발생해도 다음 문장을 실행하기 때문이죠. 현재는 에러가 첫번째 자원에서 나오기 때문에 두번째 자원의 close는 실행을 할 수 없는 것입니다.
finally라고 해서 무조건 메서드가 실행되지 않음을 확인할 수 있습니다.
try-with-resources 사용하여 에러 모두 발생시키기
public static void main(String[] args) throws Exception {
try (TestTryWithResources resource1 = new TestTryWithResources();
TestTryWithResources resource2 = new TestTryWithResources();) {
resource1.hosose();
resource2.hosose();
}
}
일단 보시면 코드부터 엄청 간단해졌죠?
실행 결과를 보겠습니다.
hosose 메서드도 잘 실행되고 close 메서드도 두개 다 실행되고 에러도 여려 개가 잘 나온 것 같습니다.
Java에서 여러 예외가 발생할 때, 예외가 처리되지 않으면 예외 스택 트레이스에서 여러 예외가 발생한 위치를 보여줄 수 있습니다. 이때, 첫 번째 예외가 메인 예외이고, 다른 예외는 그 예외를 발생시키거나 연관된 예외를 나타내며, Suppressed로 표시된다고 합니다.
이렇게 왜 try-with-resources를 사용하는지 알아보았습니다.
망나니개발자님의 설명 덕분에 더욱 쉽게 예시코드들을 만들어 볼 수 있었고, 다시는 try catch finally를 이용하여 자원관리를 하지 않아야겠습니다. (AutoCloseable 인터페이스 상속 클래스 한정)
소감
하면 할수록 모르는 것이 있고 배워야 할 것이 너무나 많다고 생각됩니다. 여기서 성장하지 않고 가만히 있는다면 다시 돌아오지 않을 시간을 버리는 것이 됩니다.
소중한 시간시간을 다시 돌아오지 않을 시간이라고 생각하고 열심히 살아가는 사람이 되어야겠습니다.
출처 : https://mangkyu.tistory.com/217