티스토리 뷰
자바를 이용해 외부 자원에 접근하는 경우 한가지 주의해야할 점은 외부자원을 사용한 뒤 제대로 자원을 닫아줘야한다는 점이다.
public static void main(String[] args) {
FileInputStream fis = null;
try{
fis = new FileInputStream("");
}catch(IOException e){
}finally {
fis.close();
}
}
이정도로 끝나면 좋겠지만 문제는 저 close() 메서드도 Checked Exception 인 IOException 을 던지므로 이런 코드가 나타난다.
public static void main(String[] args) {
FileInputStream fis = null;
try{
fis = new FileInputStream("");
}catch(IOException e){
}finally {
try {
fis.close();
}catch (IOException ie){
}
}
}
지금 코드에서 fis가 null일 확률은 없지만 try 절에 로직이 더 추가되고 앞단에서 예외가 발생한다면 fis가 null일수도있으므로 그런코드에는 분기문이 하나 더 추가된다.
FileInputStream fis = null;
try{
fis = new FileInputStream("");
}catch(IOException e){
}finally {
try {
if(fis != null) {
fis.close();
}
}catch (IOException ie){
}
}
이런 코드는 장황하고 보기힘들며, 이해하기 어렵고 중첩적이다. 그리고 무엇보다 개발자가 실수하기 쉽다. 그래서 JDK1.7에는 AutoCloseable 인터페이스가 추가됐다.
/**
* @author Josh Bloch
* @since 1.7
*/
public interface AutoCloseable {
void close() throws Exception;
}
(author 를 보니 이펙티브 자바로 유명한 조슈아 블로크가 만들었나보다.)
인터페이스 추가와 함께 소소한 문법적 추가도 이뤄졌는데 try 절에 ()가 들어갈 수 있게됐다.
public static void main(String[] args) {
try(FileInputStream fis = new FileInputStream("")){
}catch(IOException e){
}
}
이런식으로 try(){} 형태로 사용이 가능하며 () 안에 들어올 수 있는건 AutoCloseable 구현체뿐이다. 이런 문법을 try with resources 라고 부른다. 저렇게하면 무슨 이점이 있을까? 매우 직관적인 인터페이스 명칭이나 위 예제를 보고 눈치챌수도있겠지만 try catch 절이 종료되면서 자동으로 close() 메서드를 호출해준다. 이 기능은 참 편리하고 좋은 기능인데 1.7에서 문법적 변화가 없다는 생각과함께 잘 모르는 사람들이 생각보다 많다. 자신의 개발환경이 1.7 이상이라면 유용하게 사용해보자.
나는 이 기능을 유용하게 잘 사용하고있는데 최근 이런 코드를 작성할 일이 생겼다.
try(BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
} catch (IOException e) {
}
try() 안에 중첩적으로 스트림 객체를 생성한것이다. 처음엔 아무생각없이 작성했었는데 보다보니 궁금해졌다. 저럴경우 BufferedReader 외의 자원들은 전부 자동으로 닫힐까?
테스트하기는 어렵지않다. 테스트해보자.
class A implements AutoCloseable{
@Override
public void close() throws Exception {
System.out.println("a call");
}
}
class B implements AutoCloseable{
public B(A a){
}
@Override
public void close() throws Exception {
System.out.println("b call");
}
}
AutoCloseable만 구현해주면 되므로 어려울건 전혀 없다.
public static void main(String[] args) {
try(B b = new B(new A())){
}catch(Exception e){
}
}
저상태 그대로 실행시키면 "b call" 만 실행시키는걸 볼 수 있다. 아.. A는 호출되지않는구나. A도 닫을 수 있게 작성하자. try() 안에는 여러줄의 코드를 삽입할 수 있다.
public static void main(String[] args) {
try(A a = new A();
B b = new B(a)){
}catch(Exception e){
}
}
실행해보면 둘 다 close() 메서드가 실행된걸 볼 수 있다.
try with resources 기능은 아무리 생각해봐도 없는것 보단 있는게 나은 유용한 기능이다. 하지만 변수를 만들지 않은 상태로 사용할 경우 close() 메서드가 호출되지않아 자원이 수거되지않으니 꼭 유념하고 사용하자.
#2017.09.29 추가
포스팅을 유심히 살펴봐주신 한분이 댓글을 달아주셨다. Java io의 Stream 클래스들은 내부변수에 대한 close()를 호출한다는 내용이다. (댓글 달아주신분이 링크걸어주신 내용 https://stackoverflow.com/questions/1388602/do-i-need-to-close-both-filereader-and-bufferedreader)
이글을 보고 생각해본결과 내가 한가지 간과한 구현이 있다는걸 알았다. 위 예제인 A, B 클래스를 보면 알겠지만 try() 문에 들어갈 수 있는 클래스는 특별한 클래스가 아니다. AutoCloseable 인터페이스만 구현해주면 어떤 클래스나 들어갈 수 있다. 그런데 어떻게 스트림은 저런식의 처리를 해줄필요가 없을까? 이 해답은 try() 문에 있는게 아니고 Stream의 구현에 있다. A, B 예제는 B 클래스가 생성자 인자로 A를 받은 후 아무것도 하지않고 있다. 예제코드라 그렇지만 보통 B 클래스같은경우 A를 내부 필드로 보관하게 될것이다.
class B implements AutoCloseable {
private A a;
public B(A a) {
this.a = a;
}
@Override
public void close() throws Exception {
System.out.println("b call");
}
}
그리고 자신의 close()가 호출될때 내부 필드의 대한 close()도 호출하는 식으로 구현한다면 try()가 해주는것과는 별개로 B가 A의 close()를 호출하게된다.
class B implements AutoCloseable {
private A a;
public B(A a) {
this.a = a;
}
@Override
public void close() throws Exception {
this.a.close();
System.out.println("b call");
}
}
결론은 try() 문은 자신의 변수가 아닌 객체의 close()를 호출해주진 않는다. 이것은 위 예제에서 증명할 수 있다. 하지만 io 패키지에서 제공하는 Stream 객체들은 내부 필드에 대한 close()도 호출이 된다. 이는 try() 문이 해주는것은 아니며, 데코레이터 패턴을 활용한 객체 내에서 자신의 필드에 대한 close()도 호출해주기 때문이다. AutoCloseable 의 구현체를 작성할때는 해당부분도 유의해서 작성하자.
'Java' 카테고리의 다른 글
이름 재사용 관련된 용어들 (0) | 2017.11.08 |
---|---|
nio 패키지를 이용한 텍스트 파일 입출력 (0) | 2017.10.09 |
Utils 클래스 리팩토링 (0) | 2017.08.27 |
Test#02. Groovy로 테스트하기 (0) | 2017.08.20 |
Test#01. jUnit으로 테스트하기 (0) | 2017.08.18 |
- Total
- Today
- Yesterday
- OOP
- toby
- frontcode
- generics
- Jackson
- MySQL
- go-core
- java
- javascript
- mariadb
- DesignPattern
- servlet
- programming
- EffectiveJava
- Design Pattern
- Spring
- clean code
- backend개발환경
- JPA
- JavaScript Core
- frontend개발환경
- Kotlin
- 정규표현식
- TEST
- http
- code
- java8
- db
- spring cloud
- Git
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |