본문 바로가기

Java-Study

Java Study : 10주차 정리 - 예외처리

예외 클래스

오류란?

  • 프로그램에서 오류가 발생하는 상황은 두 가지 이다.
    1. 프로그램 코드 작성 중 실수로 발생하는 컴파일 오류
    2. 실행 중인 프로그램이 의도하지 않은 동작을 하거나 프로그램이 중지되는 실행 오류
      • 실행 오류 중 프로그램을 잘못 구현하여 의도한 바와 다르게 실행되어 생기는 오류를 버그라 한다.

오류와 예외

  • 실행 오류는 크게 두 가지가 있는데, 하나는 자바 가상 머신에서 발생하는 시스템 오류, 하나는 예외 이다.
  • 시스템 오류는 프로그램에서 제어할 수 없지만, 예외는 프로그램에서 제어가 가능하다.

예외 클래스의 종류

  • 프로그램에서 처리하는예외 클래스의 최상위 클래스는  Exception 클래스이다.
  • Exceoption 하위 클래스에중 IOException 클래스는 입출력에 대한 예외 처리를 처리하고,
    RuntimeException은 프로그램 실행 중 발생할 수 있는 오류에 대한 예외를 처리한다.

 

예외 처리하기

try-catch문

  • try 블록에는 예외가 발생할 가능성이 있는 코드를 작성하고, 만약 try 블록 안에서 예외가 발생하면 catch 블록이
    수행된다.
  • catch문의 괄호 안에 쓰는 예외 타입은 예외 상황에 따라 달라진다.
try {
    예외가 발생할 수 있는 코드 부분
} catch(처리할 예외 타입 e) {
    try 블록 안에서 예외가 발생했을 때 예외를 처리하는 부분
}

try-catch문 사용하기

package exception;

public class ArrayExceptionHandling {

    public static void main(String[] args) {

        int[] arr = new int[5];

        try{
            for(int i = 0; i<=5; i++){  //예외가 발생할 수 있으므로 try 블록에 작성
                arr[i] = i;
                System.out.println(arr[i]);
            }
        }catch(ArrayIndexOutOfBoundsException e){   //예외가 발생하면 catch 블록 수행
            System.out.println(e);
            System.out.println("예외 처리 부분");
        }
        System.out.println("프로그램 종료");
    }
}

배열 범위가 유효한 값 4까지는 배열에 저장되어 출력하고 그 다음 값을 배열에 넣으려 할때 예외가 발생하여 catch 블록이

수행되고 정상 종료된다.

 

컴파일러에 의해 예외가 처리되는 경우

package exception;

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

public class ExceptionHandling1 {

    public static void main(String[] args) {
        try {
            FileInputStream fis = new FileInputStream("a.txt");
        } catch (FileNotFoundException e) {
            System.out.println(e);  //예외 클래스의 toString() 메서드 호출
        }
        System.out.println("여기도 수행됩니다.");   //정상 출력
    }
}

 

a.txt 파일이 없기 때문에 관련된 오류를 catch 블록에서 출력하고 예외 처리 후에도 프로그램이 계속 수행되었음을 알 수 있다.

 

try-catch-finally문

  • 끝나지 않고 계속 수행되는 경우 사용한 시스템 리소스는 사용 후 반드시 close() 메서드로 받아 주어야 한다.
  • trty-catch문의 경우에도 close() 메서드를 둘 다 사용하여 닫아주어야 하지만 finally 블록을 추가하여 각 블록마다
    리소스를 해제하지 않고, finally 블록에서 한 번만 해제해 주면 된다.
package exception;

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

public class ExceptionHandling3 {

    public static void main(String[] args) {

        FileInputStream fis = null;
        try {
            fis = new FileInputStream("a.txt");
        } catch (FileNotFoundException e) {
            System.out.println(e);
            return; //예외 처리 후 강제 리턴
        } finally {   //리턴문과 상관 없이 finally 블록 수행
            if (fis != null) {
                try {
                    fis.close();    //파일 입력 스트림 닫기
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            System.out.println("항상 수행 됩니다.");
        }
        System.out.println("여기도 수행됩니다.");
    }
}

a.txt 파일이 없기 때문에 catch 블록을 수행 하고 강제 리턴을 했지만 finally 블록은 상관 없이 수행이 되기 때문에

finally 블록이 수행된 것을 알 수 있다.

 

try-with-resource문

  • try-with-resource문을 사용하면 close() 메서드를 호출하지 않아도 자동을 리소스를 닫도록 만들 수 있다.
  • 이 문법을 사용하려면 해당 리소스가 AutoCloseable  인터페이스를 구현해야 한다.

AuthoCloseable 인터페이스 구현

package exception;

public class AutoCloseObj implements AutoCloseable{

    @Override
    public void close() throws Exception {
        System.out.println("리소스가 close() 되었습니다.");  //close() 메서드 구현
    }
}

 

try-with-resource문 사용하기 - 정상적으로 종료된 경우

package exception;


public class AutoCloseTest {

    public static void main(String[] args) {
        AutoCloseObj obj = new AutoCloseObj();
        try (AutoCloseObj obj2 = obj){  //try블록에서 ()안에 사용할 리소스 선언
            //리소스를 여러개 생성해야 한다면 세미 콜론으로 구분한다.
            throw new Exception();
        }catch(Exception e) {
            System.out.println("예외 부분입니다.");
        }
    }
}

 

try-with-resource문 사용하기 - 예외가 발생하여 종료되는 경우

package exception;

import java.io.FileNotFoundException;

public class AutoCloseTest2 {

    public static void main(String[] args) throws FileNotFoundException {
        AutoCloseObj obj = new AutoCloseObj();
        try (obj){
            throw new Exception();  //강제로 예외 발생
        }catch(Exception e) {
            System.out.println("예외 부분입니다.");
        }
    }
}

 

 

예외 처리 미루기

예외 처리를 미루는 throws 사용하기

  • 예외 처리 방법은 두 가지로 try-catch문이 있고 나머지 하나는 throws를 사용하는 것이다.
  • 예외를 해당 메서드에서 처리하지 않고 미룬 후 메서드를 호출하여 사용하는 부분에서 예외를 처리하는 방법이다.
package exception;

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

public class ThrowsException {

    public Class loadClass(String fileName, String className) throws FileNotFoundException, ClassNotFoundException{
        //throws는 두 예외는 메서드가 호출될 때 처리하도록 미룬다.
        FileInputStream fis = new FileInputStream(fileName); //FileNotFoundException 발생 가능
        Class c = Class.forName(className);  //ClassNotFoundException 발생 가능
        return c;
    }

    public static void main(String[] args) throws FileNotFoundException, ClassNotFoundException {
        ThrowsException test = new ThrowsException();
        test.loadClass("a.txt", "java.lang.String");    //메서드를 호출할 때 예외를 처리함
    }
}

loadClass에서 throws로 미뤄진 예외 처리는 main() 부분에서 이뤄지는데 이 때 처리하는 방법은 세 가지가 있다.

위 코드처럼 함수 선언 부분에 throws를 추가하고 예외 처리를 미루는 것인데 이 경우 대부분의 프로그램이 비정상
종료된다. 그렇기 때문에 두 번째 옵션인 try-catch문을 사용하여 예외를 처리하는 것이 좋다.

package exception;

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

public class ThrowsException {

    public Class loadClass(String fileName, String className) throws FileNotFoundException, ClassNotFoundException{
        //throws는 두 예외는 메서드가 호출될 때 처리하도록 미룬다.
        FileInputStream fis = new FileInputStream(fileName); //FileNotFoundException 발생 가능
        Class c = Class.forName(className);  //ClassNotFoundException 발생 가능
        return c;
    }

    public static void main(String[] args) {

        ThrowsException2 test = new ThrowsException2();

        try {
            test.loadClass("a.txt", "java.lang.String");
        } catch (FileNotFoundException | ClassNotFoundException e) {    //예외처리를 한 문장으로 처리한다.
            e.printStackTrace();
        }
    }
}

 

다중 예외 처리

  • 어떤 예외가 발생할지 미리 알 수 없지만 모든 예외 상황을 처리하고자 할 때 맨 마지막 부분에 Exception 클래스를 
    활용하여 catch 블록을 추가한다.
package exception;

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

public class ThrowsException2 {

    public Class loadClass(String fileName, String className) throws FileNotFoundException, ClassNotFoundException{
        FileInputStream fis = new FileInputStream(fileName); //FileNotFoundException ¹ß»ý
        Class c = Class.forName(className);  //ClassNotFoundException ¹ß»ý
        return c;
    }

    public static void main(String[] args) {

        ThrowsException test = new ThrowsException();

        try {
            test.loadClass("a.txt", "java.lang.String");

        }catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }catch (Exception e) {  //Exception 클래스로 그 외 예외 상황 처리
            e.printStackTrace();
        }
    }
}

 

사용자 정의 예외

  • 자바 프로그램은 예외 클래스를 직접 만들어서 예외를 발생시키고 예외 처리 코드를 구현할 수 있다.

사용자 정의 예외 클래스 구현하기

  • 사용자 정의 예외 클래스를 구현할 때는 기존 JDK에서 제공하는 예외 클래스 중 가장 유사한 클래스를 상속받는
    것이 좋다.
  • 예시로 아이디가 null 값이거나 지정 범위를 벗어나는 경우의 예외 클래스를 만들어 보자

사용자 정의 예외 구현하기

package exception;

public class IDFormatException extends Exception{

    public IDFormatException(String message){   //String message - 생성자의 매게변수로 예외 상황 메세지를 받음
        super(message);
    }
}
  • Exception 클래스에서 상속을 받아서 구현하였다.
  • Exception 클래스에서 메시지 생성자, 멤버 변수와 메서드를 이미 제공하고 있기 때문에 super(message)를 사용하여
    예외 메시지를 설정한다. getMessage() 메서드를 호출하여 내용을 볼 수 있다.

사용자 정의 예외 테스트하기

package exception;

public class IDFormatTest {

    private String userID;

    public String getUserID(){
        return userID;
    }
    
    //아이디에 대한 제약 조건 구현
    public void setUserID(String userID) throws IDFormatException{
        ////IDFormatException 예외를 setUserID() 메서드가 호출될 때 처리하도록 미룸

        if(userID == null){
            throw new IDFormatException("아이디는 null 일 수 없습니다");  //강제로 예외 발생시킴
        }
        else if( userID.length() < 8 || userID.length() > 20){
            throw new IDFormatException("아이디는 8자 이상 20자 이하로 쓰세요");  //강제로 예외 발생시킴
        }

        this.userID = userID;
    }

    public static void main(String[] args) {

        IDFormatTest test = new IDFormatTest();
        
        String userID = null;   //아디디 값이 null인 경우
        try {
            test.setUserID(userID);
        } catch (IDFormatException e) {
            System.out.println(e.getMessage());
        }

        userID = "1234567"; //아이디 값이 8자 이하인 경우
        try {
            test.setUserID(userID);
        } catch (IDFormatException e) {
            System.out.println(e.getMessage());
        }
    }
}

 

예외 처리를 할 때는 로그를 잘 남기자

  • 시스템을 개발하여 구축을 했을 때 오류가 발생한다면 어떤 상황에서 오류가 났는지, 시스템에서 어떤 메서드를
    호출하고 어떻게 매개변수를 전달했는지 오류 현상만 보고는 알 수 없기 때문에 로그를 남기는 것이 중요하다.