응용프로그램에서 문제가 발생하면 보통 에러 혹은 예외를 던진다.
자바의 에러 구조는 다음과 같다.
가장 상위 인터페이스로 Throwable 이 있고
여기서 Throwable을 상속받는 두가지로 예외는 Exception, 에러는 Error로 나뉜다.
그리고 Exception을 상속받는 클래스 중 RuntimeException은 런타임 도중 발생하는 예외를 잡는다.
예외는 던지거나 잡을 수 있다.
던지고싶다면 메소드 선언부에서 throws 키워드로 던지고싶은 Exception을 나열하면된다.
잡으려면 try-catch~finally가 있다.
try ~ catch ~ finally에서 중요한 점은
Exception이 catch 되더라도 finally는 발동한다.
public int test(){
try{
Class.forName("java.lang.String");
return 12;
}catch(ClassNotFoundException e){
e.printStackTrace();
}finally{
// finally 구문은 Exception이 발생해도 반드시 실행한다.
return 10;
}
}
Try with Resources
https://mangkyu.tistory.com/217
다음으로는 try with resources 이다.
이는 주로 FileInputStream같은 열어주면? 닫아줘야하는 객체가 있을 때,
Auto로 finally없이 작업이 끝나면 닫아주는 역할을 수행한다.
사용법
try with resources 를 사용하려면 AutoCloseable 인터페이스의 close() 메서드를 구현해야 한다.
아마 try절에 소괄호 속에 들어가는 객체에 close메서드를 호출해서 그런게 아닐까 싶다.
참고) 자바 9에서 부터는 아래와 같이 객체 생성부와 try속 객체를 넣는곳을 분리할 수 있다.
참고2) 아래와 같이 사용하려면 AutoCloseable을 구현한 객체가 final이거나 effectively final이어야 한다.
void test(){
MyResource myRes1 = new MyResource();
MyResource myRes2 = new MyResource();
try(myRes1;myRes2){ // Java 9부터 가능
// 실행되어야 하는 구문
}catch(Exception e){
// catch절 구문
}
}
이러한 try with resources를 사용하는데 있어서 크게 두가지 이유를 위 블로그에서 참조할 수 있었다.
스택 트레이스 누락
원래라면 다음과 같은 코드가 일반적일 것 이다.
class MyResource implements AutoCloseable{
// AutoCloseable을 구현해서 close 함수를 구현한다.
public void open(){
System.out.println("열어요");
throw new IllegalArgumentException();
}
@Override
public void close(){
System.out.println("닫아요");
throw new IllegalArgumentException();
}
}
public class Main {
MyResource myRes1;
MyResource myRes2;
public void temp(){
try{
myRes1 = new MyResource();
myRes1.open();
}finally{
if(myRes1 != null){
myRes1.close();
}
}
}
public static void main(String[] args){
Main main = new Main();
main.temp();
}
}
이렇게 작성하고 실행하면 open에 발생하는 Exception이 스택 트레이스에 잡히지 않는다.
그런데 아래와 같이 바꾸면 결과에 두가지 Exception이 모두 잡히는것 처럼 보인다.
class MyResource implements AutoCloseable{
public void open(){
System.out.println("열림");
throw new IllegalArgumentException();
}
@Override
public void close() {
System.out.println("닫힘");
throw new IllegalArgumentException();
}
}
public class Main {
MyResource myRes1;
MyResource myRes2;
public void test(){
myRes1 = new MyResource();
try{
myRes1.open();
} catch(Exception e){
e.printStackTrace();
} finally {
if(myRes1 != null) myRes1.close();
}
}
public static void main(String[] args) {
Main main = new Main();
main.test();
}
}
열림
닫힘
java.lang.IllegalArgumentException
at MyResource.open(Main.java:8)
at Main.test(Main.java:25)
at Main.main(Main.java:37)
Exception in thread "main" java.lang.IllegalArgumentException
at MyResource.close(Main.java:13)
at Main.test(Main.java:30)
at Main.main(Main.java:37)
두가지 Exception이 모두 잡힌것 처럼 보이지만
main 메서드를 실행하는 쓰레드에서 Exception이 발생하였다고 나온다.
즉, finally에서 던져진 예외가 catch에서 처리되지 못하고, JVM에 던지게 된 것이다.
이걸 막기위해서 try with resources를 사용한다면
public void temp(){
MyResource myRes1 = new MyResource();
try(myRes1){
myRes1.open();
}catch(Exception e){
e.printStackTrace();
}
}
open과 close에 발생하는 모든 Exception이 catch절에 의해 잡히게 된다.
열어요
닫아요
java.lang.IllegalArgumentException
at MyResource.open(Main.java:11)
at Main.temp(Main.java:27)
at Main.main(Main.java:34)
Suppressed: java.lang.IllegalArgumentException
at MyResource.close(Main.java:17)
at Main.temp(Main.java:26)
... 1 more
자원 반납 누락
이제 위의 코드에서 open에 있는 throw를 없애고, 두개의 MyResource객체를 만들어서 finally구문에서 close하자.
class MyResource implements AutoCloseable{
public void open(){
System.out.println("열림");
}
@Override
public void close() {
System.out.println("닫힘");
throw new IllegalArgumentException();
}
}
public class Main {
MyResource myRes1;
MyResource myRes2;
public void test(){
myRes1 = new MyResource();
myRes2 = new MyResource();
try{
myRes1.open();
myRes2.open();
} catch(Exception e){
e.printStackTrace();
} finally {
if(myRes1 != null) myRes1.close();
if(myRes2 != null) myRes1.close();
}
}
public static void main(String[] args) {
Main main = new Main();
main.test();
}
}
스택 트레이스는 다음과 같이 나온다.
열림
열림
닫힘
Exception in thread "main" java.lang.IllegalArgumentException
at MyResource.close(Main.java:12)
at Main.test(Main.java:29)
at Main.main(Main.java:36)
finally 에 있는 myRes1의 close에서 발생한 Exception이 처리되지 못한채로 JVM까지 처리 위임이 되었기에
비정상종료되어 myRes2의 닫힘이 발생할 수 없게 되었다.
이제 try with resources로 변경해보자.
public void test(){
MyResource myRes1 = new MyResource();
MyResource myRes2 = new MyResource();
try(myRes1;myRes2){
myRes1.open();
myRes2.open();
}catch(Exception e){
e.printStackTrace();
}
}
스택 트레이스는 다음과 같이 달라진다.
열림
열림
닫힘
닫힘
java.lang.IllegalArgumentException
at MyResource.close(Main.java:12)
at Main.test(Main.java:23)
at Main.main(Main.java:30)
Suppressed: java.lang.IllegalArgumentException
at MyResource.close(Main.java:12)
at Main.test(Main.java:20)
... 1 more
이제 myRes1이 Exception을 던지더라도 myRes2까지 실행하는데 영향을 주지않았고
각 close() 메서드 호출동안에 발생하는 Exception또한 catch절에 잘 잡히게 되었다.
그 원리는 bytecode를 까보면 알 수 있다.
내부적으로 작성자가 작성한 catch절을 제외하고도 exception이 발생할 부분들에 대하여 컴파일러가 try-catch로 감싸놓고
Throwable에 있는 addSuppressed를 통해 누락되지 않도록 안쪽부터 발생한 Exception을 전부 누적해놓는다.
public void temp3() {
// try with resources 사용 (JAVA 9에서는 아래와 같이 AutoCloseable을 구현한 객체를 바로 넣을 수 있음)
// suppressed를 통해 누락없이 예외를 잡을 수 있음
// myRes1 = new MyResource(); 이렇게 작성하면 effectively final이어야 한다는 에러발생
MyResource myRes1 = new MyResource();
try(myRes1) {
myRes1.open();
} catch (Exception e) {
e.printStackTrace();
}
}
// 위의 코드를 bytecode로 보면 (인텔리제이 기준이다.)'
public void temp3() {
MyResource myRes1 = new MyResource();
// 순서상으로 누적되어야 하는건 open() 메소드 이후 close()메소드 이기 때문에 내부적으로 감싸준듯 하다.
try {
MyResource var2 = myRes1;
try {
myRes1.open();
} catch (Throwable var6) {
if (myRes1 != null) {
try {
var2.close();
} catch (Throwable var5) {
var6.addSuppressed(var5);
}
}
throw var6;
}
if (myRes1 != null) {
myRes1.close();
}
} catch (Exception var7) {
var7.printStackTrace();
}
}
'Java' 카테고리의 다른 글
함수형 인터페이스 (1) | 2024.04.18 |
---|---|
가비지 컬렉션 - 가비지 컬렉션의 개념와 작동 방식 (1) | 2024.04.13 |