예외(Exception)처리
예외와 오류
- 프로그램이 어떤 원인에 의해 비정상 종료되는 상황에서 발생하는 것을 예외 또는 오류라고 한다.
- 예외 처리를 통해 Thread(실행의 흐름)을 잡아 놓을 수 있다.
- 오류(Error) : 메모리가 부족하거나 메서드끼리 호출을 무한 반복하는 것처럼 일단 발생하면 복구나
되돌릴 수 없는 심각한 상황.
- 처리방법 : 오류 발생 원인을 찾아서 없애는 '디버깅 작업'을 해주어야함. - 예외(Exception) : 오류와 비교하면 심각도가 낮으며 프로그램의 정상적인 흐름을 방해하는 상황
예) 파일을 읽으려고 하는데 파일이 없는 경우, 동작 중 네트워크 연결이 끊기는 경우 - 예외처리(Exception Handling) : 예외가 발생했을 때 비정상적인 종료를 막고 계속해서 사용할 수 있도록 하는 것.
예) 파일이 없을때 비정상적으로 프로그램을 종료하는 것이 아니라 파일을 다시 첨부할 수 있도록 알림 창을 띄워주는 것
[Exception의 종류]
1. 실행시에 발생하는 exception : Unchecked exception
RuntimeException을 상속 받은 예외 type이다.
예외 처리 코드를 선택적으로 작성 할 수 있다.
2. 컴파일시에 발생하는 exception : Checked exception
RuntimeException을 상속 받지 않은 예외 type이다.
반드시 예외 처리 코드를 작성해야된다
**Checked exception과 Unchecked exception모두 일단 발생하면 해당 예외에 대한 처리 코드가 있어야 프로그램의 비정상 종료를 막을 수 있다.
예외처리 기법
try~catch 구문
- try 구문 안에서 생성된 참조값은 try 구문 안에서만 사용가능
- catch에서 작성된 변수명은 해당 catch 구문 안에서만 사용가능
try{
// 예외가 발생할 수 있는 코드
}catch(예외type 변수명){
// 예외1 발생시 수행할 코드
}catch(예외type 변수명){
// 예외2 발생시 수행할 코드
}...
[예시 1]
베열의 특정 위치에 있는 자료를 출력 후, '프로그램을 종료합니다.'라는 메시지를 출력 한 뒤 종료하는 프로그램
//exception 발생 코드
public class SimpleException {
public static void main(String[] args) {
int[] intArray= {10};
System.out.println(intArray[2]); // 예외발생 -> 프로그램 종료
System.out.println("프로그램을 종료합니다.");
}
}
exception 발행하여 예외처리
//예외처리 후
public class SimpleException {
public static void main(String[] args) {
int[] intArray= {10};
try {
System.out.println(intArray[2]); // 예외발생 -> 프로그램 종료
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("intArray 배열에는 2번방이 없습니다.");
}
System.out.println("프로그램을 종료합니다.");
}
}
[예시 2]
package test.main;
public class MainClass03 {
public static void main(String[] args) {
/*
* 1~10까지 출력을 하고 싶다.
* 단, 1초에 1번씩
*/
for(int i=0;i<10;i++) {
try {
Thread.sleep(1000); //Thread.sleep(); 을 이용해 thread를 잡아둘 수 있다.
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i+1);
}
}
}
Exception 객체의 정보 활용
- 모든 예외 객체의 상위 객체는 Throwable 이다. Throwable은 다양한 메서드로 예외에 대한 정보를 전달할 수 있다.
[예시]
num1에 담긴 값과 num2에 담긴 값을 나누었을때 나오는 몫을 출력하는 프로그램이다.
코드를 Run해서 어떤 exception이 일어나는지 console 창을 보자
package test.main;
import java.util.InputMismatchException;
import java.util.Scanner;
public class MainClass01 {
public static void main(String[] args) {
Scanner scan=new Scanner(System.in);
//1. Exception이 발생할 수 있는 code 블럭을 try{ }안에 위치 시킨다.
try {
System.out.print("나눌 수 입력 : ");
int num1=scan.nextInt(); // scan.nextInt();는 정수값을 받아온다.
System.out.print("나누어지는 수 입력 : ");
int num2=scan.nextInt();
int result=num2/num1;
System.out.println(result);
}catch(InputMismatchException ime) {//2. catch(예외type 변수명) 작성. 이때, 변수 명은 선언된 catch 구문 안에서만 사용가능하다.
System.out.println("숫자 형태로 입력해라");
ime.printStackTrace(); //.printStackTrace(); - stack에서 일어난 일을 출력해라
}
System.out.println("main 메소드 출력 완료");
}
정수가 아닌 것을 입력하면
.printStackTrace(); 메서드에 의해 메서드 호출 스택이 출력되고, 어느 행에서 오류가 발생했는지 알 수 있다.
try~catch 구문 흐름
- 예외 발생 없을 때
try 구문 모두 실행 후 catch 구문은 건너뛰고 catch 블록 다음 문장을 수행한다. - 예외 발생 했을 때
try 구문 수행중 예외 코드를 만나면 JVM에서 확인 후, 적당한 catch 블록으로 throw(던짐)던져준다.
catch 블록이 실행된 후에는 발행한 예외가 없어진 후이므로 다음에 등장한 일반 코드가 문제 없이 수행된다.
예외 발생 여부에 따른 try~catch 구문의 흐름을 보자.
[예시]
package test.main;
import java.util.InputMismatchException;
import java.util.Scanner;
public class MainClass01 {
public static void main(String[] args) {
Scanner scan=new Scanner(System.in);
//1. Exception이 발생할 수 있는 code 블럭을 try{ }안에 위치 시킨다.
try {
System.out.print("나눌 수 입력 : ");
int num1=scan.nextInt(); // scan.nextInt();는 정수값을 받아온다.
System.out.print("나누어지는 수 입력 : ");
int num2=scan.nextInt();
int result=num2/num1;
System.out.println(result);
}catch(InputMismatchException ime) {//2. catch(예외type 변수명) 작성. 이때, 변수 명은 선언된 catch 구문 안에서만 사용가능하다.
System.out.println("숫자 형태로 입력해라");
ime.printStackTrace(); //.printStackTrace(); - stack에서 일어난 일을 출력해라
}catch(ArithmeticException ae) {
System.out.println("0으로 나눌 수는 없단다~");
ae.printStackTrace();
}
System.out.println("main 메소드 출력 완료");
}
}
다중 예외 처리
- 하나의 try 블록에 여러개의 catch 블록을 연결할 수 있다.
- 각각의 catch 블록에서는 처리하려는 예외 타입을 선언하면 된다.
- 주의1! JVM이 던진 예외를 적절한 catch 블록에게 연결할 때도 다형성이 적용된다.
- 상속 관계가 없는 여러 예외를 처리시, 타입 작성 순서 상관없음
- 상속 관계가 있는 예외 처리시, 자식 예외부터 조상 예외의 순서로 catch 블록을 작성해야한다.
예 ) 가장 첫번째 catch 블록에 exception을 작성하는 경우 'Unreachable catch block for Exception' 컴파일 에러 발생 - 주의2! 상속의 관계가 없는 예외이고, 성격이 비슷해서 처리하는 방식도 비슷한 경우 '|'기호를 사용해서 여러 예외를 연결할 수 있다.
[주의1! 다형성 적용 예시]
import java.util.InputMismatchException;
import java.util.Scanner;
public class MainClass01 {
public static void main(String[] args) {
Scanner scan=new Scanner(System.in);
try {
System.out.print("나눌 수 입력 : ");
int num1=scan.nextInt();
System.out.print("나누어지는 수 입력 : ");
int num2=scan.nextInt();
int result=num2/num1;
System.out.println(result);
}catch(Exception e) { // Exception type을 가장 가장 위에 작성
System.out.println("오류이다");
}catch(InputMismatchException ime) {
System.out.println("숫자 형태로 입력해라");
ime.printStackTrace();
}catch(ArithmeticException ae) {
System.out.println("0으로 나눌 수는 없단다~");
ae.printStackTrace();
}
System.out.println("main 메소드 출력 완료");
}
}
try~catch~finally 구문
- fianlly 블록은 예외 발생 여부와 상관없이 반드시 실행되어야 하는 내용을 작성한다.
- 심지어 중간에 return문을 만날때에도 먼저 finally 블록을 실행 후 메서드가 리턴된다.
[예시]
import java.util.InputMismatchException;
import java.util.Scanner;
public class MainClass01 {
public static void main(String[] args) {
Scanner scan=new Scanner(System.in);
try {
System.out.print("나눌 수 입력 : ");
int num1=scan.nextInt();
System.out.print("나누어지는 수 입력 : ");
int num2=scan.nextInt();
int result=num2/num1;
System.out.println(result);
return;
}catch(InputMismatchException ime) {
ime.printStackTrace();
}catch(ArithmeticException ae) {
System.out.println("0으로 나눌 수는 없단다~");
ae.printStackTrace();
}finally { //finally 구문 작성 : exception 여부에 상관 없이 출력된다.
System.out.println("return 키워드가 있어도 실행할꺼야");
}
System.out.println("main 메소드 출력 완료");
}
}
**참고 (책 p.340 코드 참고)
Finally 블록의 주요 목적은 try 블록에서 사용한 시스템 자원(System resource)의 반납처리에 있다.
시스템자원이란 데이터 베이스 접속을 위한 Connection이나 파일 I/O를 위한 Steam 등을 들 수 있다.
많은 시스템 자원들은 사용시 예외를 발생시킬 수 잇어서 try 블록 내에서 사용하게 된다.
시스템자원은 유한하므로 사용 후 반납되지 않으면 장래 리소스 릭(resource leak)이 발생할 수 있다.
이러한 자원 반납은 애플리케이션이 정상적으로 사용했을 때는 물로 예외가 발생했을 때도 처리되어야한다.
try~with~resources구문
아직 안 배움
throws 키워드를 통한 처리 위임
throws 키워드
- throws 는 메서드에서 처리해야 할 하나 이상의 예외를 메서드를 호출한 곳으로 던져버린다.
- throws 키워드로 예외를 위임하는 것은 단지 호출한 곳으로 예외를 전달할 뿐이다.
(try~catch에서 catch 블록은 예외를 처리해서 없애버린다.) - throw를 통해서 예외를 전달할 때에도 다형성을 적용할 수 있다.
(try~catch에서 '다중 예외 처리' 참고) - 객체 생성시에도 예외가 발생할 수 있다. 이때는 throws 키워드를 생성자에 작성해주어야한다.
[예시]
실행의 흐름이 어떻게 되는지 생각해보자
public class CheckedThrowsTest {
public static void main(String[] args) {
CheckedThrowsTest et=new CheckedThrowsTest();
try {
et.method1();
}catch (ClassNotFoundException e) {
System.out.println("예외처리 : "+e.getMessage());
}
System.out.println("프로그램 종료");
}
//method1
public void method1() throws ClassNotFoundException{
method2();
}
//method2
public void method2() throws ClassNotFoundException{
Class.forName("Some Class");
}
}
예외의 타입에 따른 throws 특징
- unchecked exception인 RuntimeException 계열은 try~catch 문이 없으면 별도의 throws 절이 없어도 예외의 전달이 자동으로 진행된다.
- 하지만 throw는 처리가 아니기 때문에 언젠가는 try~catch로 처리 되어야 함을 명심하자.
[예시]
unchecked exception인 RuntimeException 계열의 예외가 전달되고 처리되는 예
정수를 0으로 나눴을 때 발생하는 ArithmeticException이 어떻게 처리되는지 생각해 보자
public class RuntimeThrowsTest {
public static void main(String[] args) {
RuntimeThrowsTest et=new RuntimeThrowsTest();
try {
et.method1();
}catch (ArithmeticException e) {
System.out.println("예외처리 : "+e.getMessage());
}
System.out.println("프로그램 종료");
}
//method1
public void method1(){
method2();
}
//method2
public void method2(){ // throws ClassNotFoundException 생략
int i=1/0;
}
}
로그 분석과 예외의 추적
- Throwable 클래스가 가지는 printStackTrace()메서드를 사용하면 예외가 바생하고 전파된 메서드 호출 스택에 대한 정보를 확인할 수 있다.
- 어떤 종류의 예외인가? 예외 클래스의 종류를 확인한다.
- 예외의 원인은 무엇인가? 예외 객체의 message에 해당하는 내용을 확인한다.
- 어디서 발생했는가? 디버깅의 출발점을 확인한다.
- at으로 시작하는 정보들은 메서드의 호출 스택을 나타낸다.
- 맨 위의 메서드가 최초로 예외르 발생시킨 메서드이고
- 맨 아래가 그 메서드 호출 스택의 출발점이다.
- 디버깅을 할 수 있는 부분은 일반적으로 우리가 작성한 소스 부분이다.
[예시]
1/0을 했을때 발생하는 예외 사항에 대한 스택 정보를 확인해보자
public class RuntimeThrowsTest {
public static void main(String[] args) {
RuntimeThrowsTest et=new RuntimeThrowsTest();
try {
et.method1();
}catch (ArithmeticException e) {
System.out.println("예외처리 : "+e.getMessage());
e.printStackTrace();
}
System.out.println("프로그램 종료");
}
//method1
public void method1(){
method2();
}
//method2
public void method2(){ // throws ClassNotFoundException 생략
int i=1/0;
}
}
throws의 목적과 API 활용
- 예외는 언제나 try~catch로 처리하는 것이 능사는 아니다.
특히, API나 다른 모듈에서 사용되는 기능을 제공하는 경우 발생한 예외들을 모두 try~catch로 처리해버린다면 API를 사용하는 애플리케이션에는 어떤 문제가 발생했는지 전혀 알 수 없다.
따라서, 예외를 전달해서 그 예외에 대해 적절히 대응할 수 있는 코드를 작성할 수 있게 해줄 필요가 있다. - 예시 : 일반적으로 애플리케이션을 설치할 때 인스톨 툴이 사용된다. 그런데 설치 도중 하드 디스크의 용량이 부족하거나 다른 애플리케이션과 충돌하게 되면 예외가 발생한다.
이 예외 상황을 인스톨 툴을 사용하는 코드에게 전달할 때 throws로 처리해서 설치 과정에 문제가 있음을 알릴 수 있다. - 예외를 발생 시킬때는 throw.
예외를 전파 시킬때는 throws.
[예시]
메서드를 호출한 곳으로 throw를 사용해서 예외를 전달하기
//Thread.sleep()에서 InterruptedException 발생!
public class MyUtil {
//exception을 메서드에서 처리함
public static void draw() {
System.out.println("5초 동안 그림을 그려요");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("그림 그리기 완료");
}
//메서드를 호출한 곳으로 exception을 보낸다.(MainClass05)
public static void send() throws InterruptedException {
System.out.println("5초 동안 전송을 해요");
Thread.sleep(5000);
System.out.println("전송 완료");
}
}
//main 메서드에서 draw()와 send() 호출
public class MainClass05 {
public static void main(String[] args) {
MyUtil.draw();
try {
MyUtil.send();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
메서드 오버라이드와 throws
상속 구조에서 조상 클래스의 메서드가 예외를 throws하고 있고, 자식 클래스에서 그 메서드를 오버라이드 하는 경우,
자식 메서드는 조상 메서드가 던지는 예외보다 부모 예외를 throws 할 수 없다.
[예시]
//부모 클래스
public class Parent {
void methodA() throws IOException{}
void methodB() throws ClassNotFoundException{}
}
//자식 클래스
public class OverrideingTest extends Parent {
//IOException의 자식예외인 FileNotFoundException을 throws 처리
@Override
void methodA() throws FileNotFoundException {}
//ClassNotFoundException의 조상 예외인 Exception throws하므로 제약사항 위반 발생!
//Exception Exception is not compatible with throws clause in Parent.methodB()
@Override
void methodB() throws Exception {}
}
사용자 정의 예외
사용자 정의 예외 작성
- 사용자 정의 예외는 API에 정의된 예외 이외에 필요에 따라 작성한 예외 클래스를 말한다.
- 일반적으로 예외의 특성에 따라 Exception 또는 RuntimeException 을 상속 받아서 작성한다.
[예시]
RuntimeException 상속 받아 사용자 정의 예외를 작성해본다.
package test.mypac;
/*
* [생성자 만들기]
*/
public class GuraException extends RuntimeException{
//생성자
public GuraException(String msg) {
super(msg); //부모생성자에게 String type의 인자 전달
}
}
사용자 정의 예외 활용
[예시]
사용자 정의 예외 클래스가 만들어 졌으면 사용하고자하는 시점에 예외 객체를 생성해서 throw 하면된다.
public class MainClass04 {
public static void main(String[] args) {
Scanner scan=new Scanner(System.in);
try {
System.out.println("클럽입니다.");
System.out.print("이름을 입력하세요 : ");
String name=scan.nextLine();
if(name.equals("김구라")) {
throw new GuraException("김구라 출입금지!");
}
System.out.println(name+"님 클럽에서 신나게 놀아요");
}catch(GuraException ge) {
ge.printStackTrace();
}
System.out.println("main 메서드를 종료합니다.");
}
}
로깅(logging)과 디버깅(debugging)
로깅(logging)
디버깅(debugging)
'JAVA' 카테고리의 다른 글
Multi Thread(멀티스레드) (0) | 2019.12.13 |
---|---|
I/O (Input/Output) (0) | 2019.12.12 |
내부 클래스(Inner Class) / 람다식 (0) | 2019.12.09 |
추상클래스(Abstract Class) / 인터페이스(Interface) (0) | 2019.12.09 |
클래스의 관계-상속/제어자/다형성/패키지/임포트 (0) | 2019.12.08 |