본문 바로가기
JAVA/이재환의 자바 프로그래밍 입문

[Java] Ch.16 예외 처리

by ♡˖GYURI˖♡ 2023. 10. 23.

예외와 에러

예외는 개발자가 다음과 같은 목적을 위해 제어·처리함

  1. 프로그램의 정상 종료
  2. 예외 발생 시 무시하고 프로그램 계속 실행

▼자바에서 제공하는 에러 전체 클래스

  • 프로그램에서 처리하는 예외 클래스의 최상위 클래스는 Exception 클래스
  • 모든 예외 타입은 클래스로서 서로 상속 관계

 

 

예외 종류

  • 실행 예외
    • 예외 처리를 하지 않아도 컴파일할 수 있는 비검사형 예외(Unchecked Exception)
    • 실행 단계에서 체크
  • 일반 예외
    • 예외 처리를 하지 않으면 컴파일 오류가 발생하므로 꼭 처리해야 하는 검사형 예외(Checked Exception)
    • 컴파일 단계에서 체크

 

예외를 구분하는 이유 : 프로그램 성능 때문

모든 상황에서 예외 처리를 하면 프로그램의 성능 저하 문제로 이어지기 때문에 일반 예외는 컴파일러가 확실히 확인하고, 실행 예외는 개발자가 판단하여 예외를 처리하든지 처리가 안 되었다면 자바 가상 머신에 처리를 맡기게 됨

 

▼대표적인 실행 예외

실행 예외 발생 이유
ArithmeticException 0으로 나누기와 같은 부적절한 산술 연산 수행 시 발생
IllegalArgumentException 메서드에 부적절한 매개변수를 전달할 때 발생
IndexOutOfBoundException 배열, 벡터 등에서 범위를 벗어난 인덱스 사용 시 발생
NoSuchElementException 요구한 원소가 없을 때 발생
NullPointerException null값을 가진 참조 변수에 접근할 때 발생
NumberFormatException 숫자로 바꿀 수 없는 문자열을 숫자로 변환하려 할 때 발생

 

▼대표적인 일반 예외

일반 예외 발생 이유
ClassNotFoundException 존재하지 않는 클래스를 사용하려고 할 때 발생
NoSuchFieldException 클래스가 명시한 필드를 포함하지 않을 때 발생
NoSuchMethodException 클래스가 명시한 메서드를 포함하지 않을 때 발생
IOException 데이터 읽기 쓰기 같은 입출력 문제가 있을 때 발생

 

import java.util.Scanner;

public class Ex01_ExceptionCase
{
    public static void main(String[] args)
    {
        Scanner sc = new Scanner(System.in);
        int num1 = sc.nextInt();
        int num2 = 10 / num1;
        System.out.println(num2);
        
        //MyBook book1 = new MyBook();
    }
}

 

예외 처리하기

예외 처리의 진행 형식

try
{
 	JAVA 코드
}
catch (예외 타입 1 e)
{
 	예외 1 발생 시 이 부분 실행
}
catch (예외 타입 2 e)
{
	예외 2 발생 시 이 부분 실행
}
finally
{
	이 부분은 마지막에 무조건 실행
}

 

일부 생략 가능

try
{
 	JAVA 코드
}
catch (예외 타입 e)
{
 	예외 발생 시 이 부분 실행
}

try
{
 	JAVA 코드
}
finally
{
	이 부분은 마지막에 무조건 실행
}

 

try ~ catch

앞에서 작성한 코드의 예외는 2개이므로 다음과 같이 코드를 추가하여 예외를 처리해주도록 함

 

import java.util.Scanner;
import java.util.InputMismatchException;

public class Ex02_TryCatch
{
    public static void main(String[] args)
    {
        Scanner sc = new Scanner(System.in);

        try 
        {
            int num1 = sc.nextInt();
            int num2 = 10 / num1;
            System.out.println(num2);
            System.out.println("Good bye~~!");
        }
        catch(ArithmeticException e) 
        {
            String str = e.getMessage();
            System.out.println(str);
            if (str.equals("/ by zero"))
                System.out.println("0으로 나눌 수 없습니다.");
        }
        // 입력값이 정수가 아니어서 데이터 형변환에 대한 예외가 발생하면 실행됨
        catch(InputMismatchException e) 
        {
            System.out.println(e.getMessage());
            //e.printStackTrace(); -> 예외에 대한 자세한 메시지 출력
        }
    }
}

 

Ctrl + Shift + O 를 동시에 누르면 이클립스에서 임포트 자동 추가 가능

 

 

finally

예외 발생 시에도 무조건 실행

 

import java.util.Scanner;
import java.util.InputMismatchException;

public class Ex03_Finally
{
    public static void main(String[] args)
    {
        Scanner sc = new Scanner(System.in);

        try 
        {
            int num1 = sc.nextInt();
            int num2 = 10 / num1;
            System.out.println(num2);
        }
        catch(ArithmeticException e) 
        {
            String str = e.getMessage();
            System.out.println(str);
            if (str.equals("/ by zero"))
                System.out.println("0으로 나눌 수 없습니다.");
        }
        catch(InputMismatchException e) 
        {
            System.out.println(e.getMessage());
            //e.printStackTrace();
        } 
        finally 
        {
            System.out.println("Good bye~~!");
        }
    }
}

 

 

예외 처리 합치기

catch문 하나에서 여러 예외를 한꺼번에 처리할 수도 있음

 

import java.util.Scanner;
import java.util.InputMismatchException;

public class Ex04_CatchConcat
{
    public static void main(String[] args)
    {
        Scanner sc = new Scanner(System.in);

        try 
        {
            int num1 = sc.nextInt();
            int num2 = 10 / num1;
            System.out.println(num2);
        }
        // 예외 처리 | 사용 (or 연산자)
        catch(ArithmeticException | InputMismatchException e) 
        {
            //System.out.println(e.getMessage());
            //e.printStackTrace();
            System.out.println("예외 발생");
        }
	// try~catch문에 의해 비정상적인 종료는 방지되었으므로, finally를 사용하지 않고
        // 그 뒤에 이렇게 사용하면 이 내용을 예외가 발생해도 출력할 수 있음
        System.out.println("Good bye~~!");
    }
}

 

 

모든 예외 한 번에 처리하기

어떤 예외가 발생할지 모를 때 모든 예외의 최상위 클래스를 이용해서 예외 처리 가능

'자식 클래스의 객체는 부모 클래스형의 변수에 대입할 수 있다'

 

import java.util.Scanner;

public class Ex05_Exception
{
    public static void main(String[] args)
    {
        Scanner sc = new Scanner(System.in);

        try 
        {
            int num1 = sc.nextInt();
            int num2 = 10 / num1;
            System.out.println(num2);
        }
        catch(Exception e) 
        {
            //System.out.println(e.getMessage());
            //e.printStackTrace();
            System.out.println("예외 발생");
        }

        System.out.println("Good bye~~!");
    }
}

예외를 한 번에 다 처리하려면 Exception 클래스나 Throwable 클래스 사용 가능

 

 

예외 처리 미루기(던지기)

결과적으로 JVM은 프로그램을 종료시킴

 

 

예외 처리 미루기

public class Ex06_ExceptionThrow
{
    public static void myMethod1(int n) 
    {
        myMethod2(n, 0);
    }
    
    public static void myMethod2(int n1, int n2) 
    {
        int r = n1 / n2;    // 예외 발생 지점
    }

    public static void main(String[] args)
    {
        myMethod1(3);
        System.out.println("Exception Throw !!!");
    }
}

 

 

Throwable로 잡기

던져진 예외를 처리하려면 어떤 예외가 올지 모르므로 catch문에서 앞서의 Exception을 활용할 수도 있겠지만, 던져진 것을 처리한다는 의미로 Exception의 상위 객체인 Throwable을 사용할 수도 있음

 

import java.util.Scanner;

public class Ex07_CatchThrowable
{
    public static void myMethod1()
    {
        myMethod2();
    }
    
    public static void myMethod2()
    {
        Scanner sc = new Scanner(System.in);

        int num1 = sc.nextInt(); // 에러 발생 지점
        int num2 = 10 / num1;    // 에러 발생 지점
        System.out.println(num2);
    }

    public static void main(String[] args)
    {
        try 
        {
            myMethod1();  // 여기로 myMethod1으로부터 예외가 넘어옴
        }
        catch(Throwable e) 
        {
            e.printStackTrace();
            //System.out.println(e.getMessage());
        }
    }
}

 

 

예외 처리를 미루는 이유

예외가 발생하는 지점의 메서드를 많은 곳에서 호출하는 경우 예외 처리가 다양할 수 있기 때문

 

예를 들어, 클래스에서 스태틱으로 지정된 유틸 메서드인 경우는 어떤 클래스의 메서드가 자신을 호출할지 모르는 상태이기 때문에, 호출하는 쪽에서 예외 처리를 해야 더 적절한 처리를 할 수 있음

 

import java.util.Scanner;

public class Ex08_WhyThrow
{
    public static void myMethod1()
    {
        try 
        {
            myMethodA();  // 여기로 myMethod1으로부터 예외가 넘어옴
        }
        catch(Throwable e) 
        {
            System.out.println("에러 !!!");
        }
    }
    
    public static void myMethod2()
    {
        try 
        {
            myMethodA();  // 여기로 myMethod1으로부터 예외가 넘어옴
        }
        catch(Throwable e) 
        {
            System.out.println("Error !!!");
        }
    }

    public static void myMethodA()
    {
        Scanner sc = new Scanner(System.in);

        int num1 = sc.nextInt(); // 에러 발생 지점
        int num2 = 10 / num1;    // 에러 발생 지점
        System.out.println(num2);
    }

    public static void main(String[] args)
    {
        myMethod1();	// 한글 메시지 출력
        myMethod2();	// 영문 메시지 출력
    }
}

 

 

메서드에 예외 선언

public static void 메서드명()
	throws 예외, 예외, 예외
{
	// 본문
}
  • 메서드를 사용하는 사람이 메서드의 선언부만 보아도 이 메서드를 사용하려면 어떤 예외를 처리하면 되는지 쉽게 알 수 있음
  • 일반적으로 RuntimeException 클래스들은 적지 않음

 

import java.util.InputMismatchException;
import java.util.Scanner;

public class Ex09_ThrowsInMethod
{
    public static void myMethod1()
    {
        myMethod2();
    }

    public static void myMethod2()
            throws ArithmeticException, InputMismatchException
    {
        Scanner sc = new Scanner(System.in);

        int num1 = sc.nextInt(); // 에러 발생 지점
        int num2 = 10 / num1;    // 에러 발생 지점
        System.out.println(num2);
    }

    public static void main(String[] args)
    {
        try
        {
            myMethod1();
        }
        catch (ArithmeticException | InputMismatchException e) 
        {
            e.printStackTrace();
        }
        System.out.println("-------");
    }
}