Error와 Exception

프로그램이 비정상적으로 종료되게 하는 원인

 

Error

수습할 수 없는 심각한 오류

  1. 컴파일 에러: 컴파일시 발생하는 에러
  2. 런타임 에러: 실행시 발생하는 에러

Exception (예외)

예외 처리를 통해 수습할 수 있는 덜 심각한 오류

 


예외 처리란?

package bubu;

public class Exception1 {
    public static void main(String[] args) {
        ExceptionObj1 exobj = new ExceptionObj1();
        int value = exobj.divide(10, 0);
        System.out.println(value);
    }
}
class ExceptionObj1{
    public int divide(int i, int k){
        int value = 0;
        value = i / k;
        return value;
    }
}

ArithmeticException 발생

 


예외 처리하기 (try~catch)

try {
    코드1
    코드2
    .................
} catch (Exception클래스명1 변수명1) {
            Exception을 처리하는 코드
} catch (Exception클래스명2 변수명2) {
            Exception을 처리하는 코드
}

 

package bubu;

public class Exception1 {
    public static void main(String[] args) {
        ExceptionObj1 exobj = new ExceptionObj1();
        int value = exobj.divide(10, 0);
        System.out.println(value);
    }
}
class ExceptionObj1{
    public int divide(int i, int k){
        int value = 0;
        try{
            value = i / k;
        } catch (ArithmeticException e){
            System.out.println("0으로 나눌 수 없어요.");
            System.out.println(e.toString());
        }
        return value;
    }
}

실행 결과

 

위 코드의 문제점

  1. 0으로 나눴을 때 값이 안 나오는 게 맞는데, 엉뚱하게 0이 출력된다.
  2. 사용자가 원하지 않는 메서드를 출력한다.(divide 메서드)

 


예외 떠넘기기(throws)

리턴타입 method명 (아규먼트 리스트) throws Exception클래스명1, Exception클래스명2 ....{
            코드1
            코드2
            .............
}

 

package bubu;

public class Exception1 {
    public static void main(String[] args) {
        ExceptionObj2 exobj = new ExceptionObj2();
        //exception이 이쪽으로 넘어오면 try~catch문으로 처리해야 한다.
        try {
            int value = exobj.divide(10, 0);
            System.out.println(value);
        } catch (ArithmeticException e){
            System.out.println("0으로 나눌 수 없어요.");
        }

    }
}
class ExceptionObj2{
    public int divide(int i, int k) throws ArithmeticException{ //메서드를 호출한 쪽으로 exception을 떠넘김
        int value = 0;
        value = i / k;
        return value;
    }
}

실행 결과

 


RuntimeException과 Checked Exception

 

Exception의 직계자손 두 가지

  1. RuntimeException = UncheckedException
    예외 처리를 하지 않아도 컴파일 됨 (실행 시 발생하는 예외)
    ex) ArithmeticException, ArrayIndexOutOfBoundsException
  2. IOException = CheckedException
    예외 처리를 했는지 컴파일러가 체크해서 예외 처리를 하지 않았으면 컴파일 오류를 발생시킴
    → 예외 처리를 강제함
    ex) FileNotFoundException
package studyException;

import java.io.IOException;

public class E {
    //UncheckedException (예외 처리 안해도 됨)
    void throwArithmeticException(){
        throw new ArithmeticException();
    }

    //CheckedException (예외 처리 필수)
    void throwIOException1(){
        try {
            throw new IOException();
        } catch(IOException e){
            e.printStackTrace();
        }
    }
    void throwIOException2() throws IOException{
        throw new IOException();
    }
}

 

 

IOException 예시

package bubu;

import java.io.FileInputStream;

public class Exception4 {
    public static void main(String[] args) {
            //컴파일 오류 발생
            FileInputStream fis = new FileInputStream("Exception4.java");
    }
}

실행 결과: FileNotFoundException 발생 - 반드시 예외처리 해줘야 한다는 컴파일 오류

 

try ~ catch로 예외처리를 해주자.

package bubu;

import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class Exception4 {
    public static void main(String[] args) {
        try {
            FileInputStream fis = new FileInputStream("Exception4.java");
        } catch (FileNotFoundException e){
            System.out.println("파일을 찾을 수 없어요.");
        }
    }
}

실행 결과

 


다중 Exception 처리

package bubu;

public class Exception6 {
    public static void main(String[] args) {
        int[] array = {4, 0};
        int[] value = null;
        try {
            value[0] = array[0] / array[1];
        } catch (ArrayIndexOutOfBoundsException e){
            System.out.println("정해진 배열의 범위를 벗어났어요." +e.getMessage());
        } catch (ArithmeticException e){ //여기서 에러 발생
            System.out.print("0으로 나눌 수 없어요. ");
            e.printStackTrace();
        } catch (Exception e){
            System.out.println(e.toString()); //Exception e가 가지고 있는 문자열 값 출력
        }
        //getMessage(), printSTackTrace(): catch가 어떤 맥락에서 발생했는지 파악해서 알려주는 메시지 출력
    }
}

두 번째 catch에서 에러가 발생한다. (ArithmeticException 에러)

실행 결과

 

package bubu;

public class Exception6 {
    public static void main(String[] args) {
        int[] array = {4, 2};
        int[] value = null;
        try {
            value[0] = array[0] / array[1];
        } catch (ArrayIndexOutOfBoundsException e){
            System.out.println("정해진 배열의 범위를 벗어났어요." +e.getMessage());
        } catch (ArithmeticException e){
            System.out.print("0으로 나눌 수 없어요. ");
            e.printStackTrace();
        } catch (Exception e){ //여기서 에러 발생
            System.out.println(e.toString()); //Exception e가 가지고 있는 문자열 값 출력
        }
        //getMessage(), printSTackTrace(): catch가 어떤 맥락에서 발생했는지 파악해서 알려주는 메시지 출력
    }
}
  • 세 번째 catch에서 에러가 발생한다. (Exception 에러)
  • Exception은 모든 Exception의 조상이다.

실행 결과

  • int[ ] value = new int[1]; 로 바꿔서 배열에 저장 공간을 만들어주면 에러가 발생하지 않는다.

 

한편, int[ ] array = {4, 2} → {4}로 바꾸면 어떻게 될까?

package bubu;

public class Exception6 {
    public static void main(String[] args) {
        int[] array = {4};
        int[] value = new int[1];
        try {
            value[0] = array[0] / array[1];
        } catch (ArrayIndexOutOfBoundsException e){ //여기서 에러 발생
            System.out.println("정해진 배열의 범위를 벗어났어요. " +e.getMessage());
        } catch (ArithmeticException e){
            System.out.print("0으로 나눌 수 없어요. ");
            e.printStackTrace();
        } catch (Exception e){
            System.out.println(e.toString()); //Exception e가 가지고 있는 문자열 값 출력
        }
        //getMessage(), printSTackTrace(): catch가 어떤 맥락에서 발생했는지 파악해서 알려주는 메시지 출력
    }
}

첫 번째 catch에서 에러가 발생한다. (ArrayIndexOutOfBoundsException 에러)

array[1]이 존재하지 않기 때문이다.

 

try 블럭 하나에 catch 블럭을 여러 개 사용할 수 있다.
이때, 자식 예외를 먼저 catch하고 부모 예외를 나중에 catch해야 한다.
자식 ArrayIndexOutOfBoundsException, ArithmeticException
부모 Exception

 

try {
    value[0] = array[0] / array[1];
} catch (ArrayIndexOutOfBoundsException e){
    System.out.println("정해진 배열의 범위를 벗어났어요. " +e.getMessage());
} catch (ArithmeticException e){
    System.out.print("0으로 나눌 수 없어요. ");
    e.printStackTrace();
} catch (Exception e){
    System.out.println(e.toString());
}

 


try catch finally

try
    코드1
    코드2 ...
} catch (Exception클래스명1 변수명1) {
        Exception을 처리하는 코드
} finally {
        예외 여부와 관계없이 반드시 한 번은 실행되는 코드
}

finally 블록으로 묶인 코드는 반드시 한 번은 실행되는 코드이다.

  • 위에 return문이 있더라도 반드시 finally는 수행된다.
  • 단, 위에 System.exit() 코드가 있으면 finally가 수행되지 않고 바로 프로그램이 종료된다.

 

package studyException;

import javax.swing.*;

public class ExceptionTest4 {
    public static void main(String[] args) {
        try {
            String num1 = JOptionPane.showInputDialog("[정수1]을 입력하세요");
            String num2 = JOptionPane.showInputDialog("[정수2]을 입력하세요");

            int n1 = Integer.parseInt(num1);
            int n2 = Integer.parseInt(num2);
            System.out.println("result: " + n1 / n2);
            //NumberFormatException: 숫자가 아닌 다른 값을 입력했을 때 발생
            //ArithmeticException: n2가 0일 때 발생
        } catch (NumberFormatException e){
            System.out.println("입력 오류! 숫자를 입력하세요");
            System.exit(-1);
        } catch (ArithmeticException e){
            System.out.println("분모가 0이 될 수 없어요. [정수2]는 0이 아닌 값을 입력하세요!");
            return;
        } catch (Exception e){
            System.out.println(e.toString());
            System.out.println(e.getMessage());
        } finally {
            System.out.println("###반드시 실행되어야 하는 코드###"); //return문에도 실행 O
        }
        System.out.println("~~~The End~~~"); //return문으로 실행 X
    }
}

 

1) NumberForMatException: 숫자가 아닌 다른 값을 입력했을 때

실행 결과

System.exit(-1)으로 시스템이 종료되고, finally의 코드가 실행되지 않는다. 당연히 맨 아래의 "~~~The End~~~" 부분도 출력되지 않는다.

 

2) ArithmeticException: 분모를 0으로 입력했을 때

실행결과

앞의 catch문에 return;이 있어도 finally의 코드는 실행된다. 그러나 맨 아래의 "~~~The End~~~" 부분은 출력되지 않는다.

 


Quiz

 

정답

1번>

AException 발생

반드시 한번 수행코드

 

2번>

BException 발생

반드시 한번 수행코드

 

3번>

CException 발생

 

4번>

암튼...예외 발생

반드시 한번 수행코드

 


Throw

코드를 작성하는 개발자가 예외를 강제로 발생시키는 것

throw new 발생시킬 예외; (강제로 Exception 객체를 생성하여 예외를 강제로 만듦)
package studyException;
class Calculator{
    int left, right;
    public void setOprands(int left, int right){
        this.left = left;
        this.right = right;
    }
    public void divide(){
        if(right == 0){
            throw new ArithmeticException("0으로 나눌 수 없습니다.");
            //강제로 ArithmeticException 객체를 생성하여 예외를 강제로 내주었음
        }
        try {
            System.out.print("계산결과는 ");
            System.out.print(this.left / this.right);
            System.out.print(" 입니다.");
        } catch (Exception e){
            System.out.println(e.getMessage());
            System.out.println(e.toString());
            e.printStackTrace();
        }
        System.out.println("Divide End");
    }

}

public class CalculatorDemo {
    public static void main(String[] args) {
        Calculator c1 = new Calculator();
        c1.setOprands(10, 0);
        try {
            c1.divide();
        } catch(ArithmeticException e){ //강제로 발생한 Exception으로 인해 catch문이 실행됨
            System.out.println(e.getMessage());
            //e 안에는 new ArithmeticException("0으로 나눌 수 없습니다."); 인스턴스가 들어있음
        }
    }
}

 


사용자 정의 Exception과 예외 발생시키기 (throw throws)

사용자 정의 예외 클래스 만들기
[1] Exception을 상속받는다.
[2] 생성자를 구성하고 그 안에서 super("예외메시지");를 호출한다.
public class MyException extends Exception{
    public MyException (String msg){
        super(msg);
    }
    public MyException (Exception e){
        super(e);
    }
}

 

나만의 예외를 만들기 전에 해야 할 것: 자신의 예외를 unchecked로 할 것인가? checked로 할 것인가를 정해야 한다.

 

API 쪽에서 예외를 던졌을 때 API 사용자 쪽에서 예외 상황을 복구할 수 있다면 checked 예외를 사용한다. checked 예외는 사용자에게 문제를 해결할 기회를 주는 것이면서 예외처리를 강제하는 것이다. 하지만 checked 예외를 너무 자주 사용하면 API 사용자를 몹시 힘들게 할 수 있기 때문에 적정선을 찾는 것이 중요하다.

 

사용자가 API의 사용법을 어겨서 발생하는 문제거나 예외 상황이 이미 발생한 시점에서 그냥 프로그램을 종료하는 것이 덜 위험할 때 unchekced를 사용한다.

 

[사용자 정의 Exception 예제1]

package bubu;

public class MyException extends RuntimeException{
    //오류 메시지나 발생한 Exception을 감싼 결과로 내가 만든 Exception을 사용하고 싶을 때가 많다.

    //message를 받는 인자생성자
    public MyException(String message) {
        super(message);
    }
    //Throwable: 예외처리의 최상위 클래스 - RumtimeException 이외의 예외를 처리해줌
    public MyException(Throwable cause) {
        super(cause);
    }
}
package bubu;

public class Exception8 {
    public static void main(String[] args) {
        try {
            ExceptionObj8 exobj = new ExceptionObj8();
            int value = exobj.divide(10, 0);
            System.out.println(value);
        } catch(MyException e){
            System.out.println(e.getMessage());
            //e 안에는 new MyException("0으로 나눌 수 없어요."); 인스턴스가 들어있음
        }
    }
}
class ExceptionObj8{
    public int divide(int i, int k) throws MyException{
        int value = 0;
        try{
            value = i / k;
        } catch (ArithmeticException e){
            throw new MyException("0으로 나눌 수 없어요.");
            //MyException 객체를 생성하여 강제로 예외를 만듦
        }
        return value;
    }
}

실행 결과

 

[사용자 정의 Exception 예제2]

package day09;
/*
* 사용자 정의 예외 클래스 만들기
[1] Exception을 상속받는다.
[2] 생성자를 구성하고 그 안에서 super("예외메시지");를 호출한다.
* */
public class NotSupportedNameException extends Exception{ //checkedException
    public NotSupportedNameException(){
        super("지원되지 않는 성씨입니다."); //예외 메시지로 등록됨
    }

    public NotSupportedNameException(String msg){
        super(msg);
    }
}
package day09;

import java.util.Scanner;

public class PongSite {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("이름을 입력하세요=> ");
        String name = scanner.next();

        //join() 호출
        PongSite ps = new PongSite();
        try{
            ps.join(name);
        } catch(NotSupportedNameException e){
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
    }

    public void join(String name) throws NotSupportedNameException{
        if(name.charAt(0) == '퐁'){
            System.out.println(name + "님 환영합니다^^");
        } else if(name.charAt(0) == '콩'){
            throw new NotSupportedNameException();
            //throw를 통해 예외 강제 발생
        } else {
            throw new NotSupportedNameException("가입은 가능하지만 제약이 있어요~");
        }
    }
}

"퐁진호" 입력시 실행결과
"콩진호" 입력시 실행결과
"김진호" 입력시 실행결과

 

throw를 통해 NotSupportedNameException을 강제로 발생시킨다. 발생시키면서 메시지를 한번 출력한다.

발생된 NotSupportedNameException의 예외는 main에서 처리해준다. (try ~ catch)

실행 결과를 보면 출력이 2번 (text 부분 / 빨간 글씨 부분)된 것을 확인할 수 있다. 결론은 예외가 2번 실행되는 것이다.

메서드에서 예외를 처리하고, 실행한 메인쪽에서도 예외를 추가적으로 처리할 수 있게 된 것이다.

=> 예외를 분산처리할 수 있게 되었다!

+ Recent posts