sm 기술 블로그

객체지향 (예외) 본문

Java

객체지향 (예외)

sm_hope 2022. 4. 18. 21:56
예외(Exception)
예상하지 못한, 예기치 못한 오류를 어떻게 처리하는가
package org.opentutorials.javatutorials.exception;
class Calculator{
    int left, right;
    public void setOprands(int left, int right){
        this.left = left;
        this.right = right;
    }
    public void divide(){
        System.out.print("계산결과는 ");
        System.out.print(this.left/this.right); // 이 부분의 문제로
        System.out.print(" 입니다.");
    }
} 
public class CalculatorDemo {
    public static void main(String[] args) {
        Calculator c1 = new Calculator();
        c1.setOprands(10, 0);
        c1.divide();  // 여기를 호출할 때 에러가 발생한다.
    }
}

이 프로그램은

// 출력결과
계산결과는 Exception in thread "main" java.lang.ArithmeticException: / by zero
    at org.opentutorials.javatutorials.exception.Calculator.divide(CalculatorDemo.java:10)
    // 10번째 코드가 메인으로 에러가 발생함
    
    at org.opentutorials.javatutorials.exception.CalculatorDemo.main(CalculatorDemo.java:18)

다음과 같이 에러를 나타낼 것이다. [0으로 나누면 에러를 발생시킨다.]

 

 

이것을 고치기 위해

package org.opentutorials.javatutorials.exception;
class Calculator{
    int left, right;
    public void setOprands(int left, int right){
        this.left = left;
        this.right = right;
    }
    public void divide(){
    
    try{
        System.out.print("계산결과는 ");
        System.out.print(this.left/this.right); // 이 부분의 문제로
        System.out.print(" 입니다.");
        }
    catch(Exception e){
    	System.out.println("오류가 발생했습니다 : "+ e.getMessage());
    }
    }
} 
public class CalculatorDemo {
    public static void main(String[] args) {
        Calculator c1 = new Calculator();
        c1.setOprands(10, 0);
        c1.divide();  // 여기를 호출할 때 에러가 발생한다.
    }
}
// 출력결과
계산결과는 오류가 발생했습니다 : / by zero

try -catch는 오류를 처리하기 위해 꼭 필요한 존재이다.

try는 오류가 날 수 있는 부분을 묶어주고, 만약 오류가 발생한다면 그 즉시 중단되며 바로 catch로 넘어간다.

 

catch 부분에 e.getMessage()를 통해 에러가 발생한 이유가 retrun 되는 것이다. (/ by zero)

 

따라서 기본 문법은 다음과 같다

try-catch 문법

 코드를 다음과 같이 바꿔보자

package org.opentutorials.javatutorials.exception;
class Calculator{
    int left, right;
    public void setOprands(int left, int right){
        this.left = left;
        this.right = right;
    }
    public void divide(){
        try {
            System.out.print("계산결과는 ");
            System.out.print(this.left/this.right);
            System.out.print(" 입니다.");
        } catch(Exception e){
            System.out.println("\n\ne.getMessage()\n"+e.getMessage());
            System.out.println("\n\ne.toString()\n"+e.toString());
            System.out.println("\n\ne.printStackTrace()");
            e.printStackTrace();
        }
    }
} 
public class CalculatorDemo {
    public static void main(String[] args) {
        Calculator c1 = new Calculator();
        c1.setOprands(10, 0);
        c1.divide();
    }
}
//출력 결과
계산결과는 
 
e.getMessage()
/ by zero
 
 
e.toString()
java.lang.ArithmeticException: / by zero
 
 
e.printStackTrace()
java.lang.ArithmeticException: / by zero
    at org.opentutorials.javatutorials.exception.Calculator.divide(CalculatorDemo.java:11)
    at org.opentutorials.javatutorials.exception.CalculatorDemo.main(CalculatorDemo.java:25)

\n 은 줄바꿈. e.getMessage()는 에러에 대한 힌트가 담김.

e.toString() : 예외 상황에 대한 자세한 정보를 얻을 수 있음.

e.printStackTrace() : 어떤 Exception이 발생했고, 이유는 무엇이고, 어디서 발생했는지 알려줌.

 

e.getMessage() < e.toString() < e.printStackTrace()

상황에 따라서, 기호에 알맞게 사용하면 된다.

 

---> catch 프로그램까지 다 실행 되었다고 해도 java는 끝내지 않고 catch 이후의 동작도 실행된다.

 

다양한 상황

package org.opentutorials.javatutorials.exception;

class A{
    private int[] arr = new int[3];
    A(){
        arr[0]=0;
        arr[1]=10;
        arr[2]=20;
    }
    public void z(int first, int second){
        System.out.println(arr[first] / arr[second]);
    }
}
 
public class ExceptionDemo1 {
    public static void main(String[] args) {
        A a = new A();
        a.z(10, 1);
    }
}
//출력 결과
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 10
    at org.opentutorials.javatutorials.exception.A.z(ExceptionDemo1.java:11)
    at org.opentutorials.javatutorials.exception.ExceptionDemo1.main(ExceptionDemo1.java:18)

배열의 인덱스 밖의 값을 가지고 오려고 하여 에러가 발생했다는 뜻이다.

 

 

catch는 분기로 로직을 실행할 수 있다.

package org.opentutorials.javatutorials.exception;
 
class A{
    private int[] arr = new int[3];
    A(){
        arr[0]=0;
        arr[1]=10;
        arr[2]=20;
    }
    public void z(int first, int second){
        try {
            System.out.println(arr[first] / arr[second]);
        } 
        catch(ArrayIndexOutOfBoundsException e){
            System.out.println("ArrayIndexOutOfBoundsException");
        } 
        // 예외가 ArrayIndexOutOfBoundsException e 일때 알려준다
        
        catch(ArithmeticException e){
            System.out.println("ArithmeticException");      
        } 
         // 예외가 ArithmeticException e 일때 알려준다.
        
        catch(Exception e){
            System.out.println("Exception");
        }
         // 예외가 Exception e 일때 알려준다
    }
}
 
public class ExceptionDemo1 {
    public static void main(String[] args) {
        A a = new A();
        a.z(10, 0);
        a.z(1, 0);
        a.z(2, 1);
    }
}

※ Exception e는 모든 예외를 다 포함한다. 따라서 분기에 따라 실행하고자 할 때 맨 앞에 둘 수 없다.

-> 어떤 예외가 되든 Exception e만 실행됨.

 

finally 

기본 문법은 다음과 같다.

try - catch -finally

finally는 예외 관계없이 무조건 실행된다.

 

package org.opentutorials.javatutorials.exception;
 
class A{
    private int[] arr = new int[3];
    A(){
        arr[0]=0;
        arr[1]=10;
        arr[2]=20;
    }
    public void z(int first, int second){
        try {
            System.out.println(arr[first] / arr[second]);
        } 
        catch(ArrayIndexOutOfBoundsException e){
            System.out.println("ArrayIndexOutOfBoundsException");
        } 
        // 예외가 ArrayIndexOutOfBoundsException e 일때 알려준다
        
        catch(ArithmeticException e){
            System.out.println("ArithmeticException");      
        } 
         // 예외가 ArithmeticException e 일때 알려준다.
        
        catch(Exception e){
            System.out.println("Exception");
        }
         // 예외가 Exception e 일때 알려준다
        
        finally{
        	System.out.println("finally");
        }
    }
}
 
public class ExceptionDemo1 {
    public static void main(String[] args) {
        A a = new A();
        a.z(10, 0); // 예외 발생
        a.z(1, 0); // 예외 발생
        a.z(2, 1); // 정상
    }
}
// 출력결과

ArrayIndexOutOfBoundsException
finally
ArithmeticException
finally
2
finally

finally라고 하는 것은 예외가 발생하더라도 반드시 실행되는 것으로 데이터베이스 처리에 많이 사용된다.

 

 

예외 던지기

 

package org.opentutorials.javatutorials.exception;
import java.io.*;  //FileReader 로직을 사용하기 위해 필요
public class CheckedExceptionDemo {
    public static void main(String[] args) {
        BufferedReader bReader = new BufferedReader(new FileReader("out.txt")); //문제 발생
        String input = bReader.readLine(); //문제 발생
        System.out.println(input); 
    }
}

FileReader() 문제가 일어나는 이유 

Throws

 파일을 찾을 수 없거나, 읽을 수 없을 때 발생하는 것. 이것을 해결한다면

package org.opentutorials.javatutorials.exception;
import java.io.*;
public class CheckedExceptionDemo {
    public static void main(String[] args) {
        BufferedReader bReader = null;
        String input = null;
        // 유효범위 때문에 전역변수로 선언함
       
        
		try {
			bReader = new BufferedReader(new FileReader("out.txt"));
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        
		try {
			input = bReader.readLine();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        System.out.println(input); 
    }
}

다음과 같이 할 수 있다.

 

예외의 강제

package org.opentutorials.javatutorials.exception;
 
class B{
    void run(){
    }
}
class C{
    void run(){
        B b = new B();
        b.run();
    }
}
public class ThrowExceptionDemo {
    public static void main(String[] args) {
         C c = new C();
         c.run();
    }   
}

위 코드는 다음과 같은 관계를 가지고 있다.

B의 사용자는 C , C의 사용자는 Throw, Throw 사용자는 일반 사용자

기본적으로 자신들이 Try~catch를 통해 예외를 처리할 수 있지만, B는 C에게 C는 Throw에게 Throw는 일반 사용자에게 예외를 넘길 수 있다.

 

B가 C에게 Throws를 통해 예외를 던진 구문이다.

package org.opentutorials.javatutorials.exception;
import java.io.*;
class B{
    void run() throws IOException, FileNotFoundException{
        BufferedReader bReader = null;
        String input = null;
        bReader = new BufferedReader(new FileReader("out.txt"));
        input = bReader.readLine();
        System.out.println(input);
    }
}
class C{
    void run(){
        B b = new B();
        try {
            b.run();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class ThrowExceptionDemo {
    public static void main(String[] args) {
         C c = new C();
         c.run();
    }   
}

 

C가 main에게 예외를 Throw를 통해 예외를 던지면

package org.opentutorials.javatutorials.exception;
import java.io.*;
class B{
    void run() throws IOException, FileNotFoundException{
        BufferedReader bReader = null;
        String input = null;
        bReader = new BufferedReader(new FileReader("out.txt"));
        input = bReader.readLine();
        System.out.println(input);
    }
}
class C{
    void run() throws IOException, FileNotFoundException{
        B b = new B();
        b.run();
    }
}
public class ThrowExceptionDemo {
    public static void main(String[] args) {
         C c = new C();
         try {
            c.run();
        } catch (FileNotFoundException e) {
            System.out.println("out.txt 파일은 설정 파일 입니다. 이 파일이 프로잭트 루트 디렉토리에 존재해야 합니다.");
            // 예외 상황을 사용자에게 알 수 있는 문자로서 알려주는 것.
            // 이 구문을 쓰지 않는다면 java.io.FileNotFoundException : out.txt (지정된 파일을 찾을 수 없습니다) ~~~ 가 출력 될 것 이다.
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }   
}

이런 식으로 예외를 던질 수 있다.

[이 코드는 예외만을 이해하기 위한 코드임. 다른 의미로는 좋은 코드는 아님]

 

예외 만들기

 

package org.opentutorials.javatutorials.exception;
class Calculator{
    int left, right;
    public void setOprands(int left, int right){
    	// (1) illegalArgumentException
    
        this.left = left;
        this.right = right;
    }
    public void divide(){
    	// (2) ArithmeticException
    
        try {
            System.out.print("계산결과는 ");
            System.out.print(this.left/this.right);
            System.out.print(" 입니다.");
        } catch(Exception e){
            System.out.println("\n\ne.getMessage()\n"+e.getMessage());
            System.out.println("\n\ne.toString()\n"+e.toString());
            System.out.println("\n\ne.printStackTrace()");
            e.printStackTrace();
        }
    }
} 
public class CalculatorDemo {
    public static void main(String[] args) {
        Calculator c1 = new Calculator();
        c1.setOprands(10, 0);
        c1.divide();
    }
}

 

이 부분에서 예외를 (1) 혹은 (2)에서 처리할 수 있다.

 

그래서

 

(1)로 처리한다면,

package org.opentutorials.javatutorials.exception;
class Calculator{
    int left, right;
    public void setOprands(int left, int right){
        if(right == 0){
            throw new IllegalArgumentException("두번째 인자의 값은 0이 될 수 없습니다.");
        }
        this.left = left;
        this.right = right;
    }
    public void divide(){
        try {
            System.out.print("계산결과는 ");
            System.out.print(this.left/this.right);
            System.out.print(" 입니다.");
        } catch(Exception e){
            System.out.println("\n\ne.getMessage()\n"+e.getMessage());
            System.out.println("\n\ne.toString()\n"+e.toString());
            System.out.println("\n\ne.printStackTrace()");
            e.printStackTrace();
        }
    }
} 
public class CalculatorDemo {
    public static void main(String[] args) {
        Calculator c1 = new Calculator();
        c1.setOprands(10, 0);
        c1.divide();
    }
}

(2)로 처리한다면

package org.opentutorials.javatutorials.exception;
class Calculator{
    int left, right;
    public void setOprands(int left, int right){        
        this.left = left;
        this.right = right;
    }
    public void divide(){
        if(this.right == 0){
            throw new ArithmeticException("0으로 나누는 것은 허용되지 않습니다.");
        }
        try {
            System.out.print("계산결과는 ");
            System.out.print(this.left/this.right);
            System.out.print(" 입니다.");
        } catch(Exception e){
            System.out.println("\n\ne.getMessage()\n"+e.getMessage());
            System.out.println("\n\ne.toString()\n"+e.toString());
            System.out.println("\n\ne.printStackTrace()");
            e.printStackTrace();
        }
    }
} 
public class CalculatorDemo {
    public static void main(String[] args) {
        Calculator c1 = new Calculator();
        c1.setOprands(10, 0);
        c1.divide();
    }
}

(1)은 right가 0이면 예외를 발생시키는 것, (2)는 0으로 나눈다면 예외를 발생시키는 것으로 (2)의 예외 상황이 더 적합할 것이다. (덧셈, 뺄셈은 문제없이 가능하기 때문.)

 

자주 사용하는 예외들은 아래와 같다.

예외 사용해야 할 상황
IllegalArgumentException 매개변수가 의도하지 않은 상황을 유발시킬 때
IllegalStateException 메소드를 호출하기 위한 상태가 아닐 때
NullPointerException 매개 변수 값이 null 일 때
IndexOutOfBoundsException 인덱스 매개 변수 값이 범위를 벗어날 때
ArithmeticException 산술적인 연산에 오류가 있을 때

사실 위에 있는 예외보다 더 많다.

 

예외의 여러 가지 상황들

package org.opentutorials.javatutorials.exception;
import java.io.IOException;
class E{
    void throwArithmeticException(){
        throw new ArithmeticException();
    }
    void throwIOException1(){
        try {
            throw new IOException();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    void throwIOException2() throws IOException{
        throw new IOException();
    }
}

[1은 예외를 직접 처리, 2는 예외를 넘김]

 

throwArithmeticException()는 오류가 발생하지 않는 반면, throwIOException()는 오류가 발생한다

왜 throwIOException는 꼭 예외처리가 필요할까?

 

 

Exception의 상속관계는 아래와 같다

예외들의 상속 관계

Throwable에 정의되어 있는 메소드는 다음 세 개다. [e.getMessage() < e.toString() < e.printStackTrace()]

따라서 이들만이 예외를 인스턴스화 해서 가지고 있기 때문에 예외처리에 문제가 없다.

 

 

Throwable은 모든 예외의 부모이다. (실질적으로 많이 쓰이지는 않는다.)

 

Error는 여러 가지 복합적인 문제로 자바 VM 실행이 불가능할 때 발생하는 오류 (우리가 할 수 있는 일은 없다.)

 

Exception은 IOException과 RuntimeException의 부모이다.

 

ArithmeticException은 RuntimeException의 직계 자식이기 때문에 예외처리가 필요/불필요로 나눠진다.

 

 

unchecked VS checked

부모 중에 RuntimeException이 있으면 unchecked를, 없으면 checked라고 한다.

unchecked는 예외처리가 필요 없고, checked는 필요하다.

 

checked : 상황을 개선할 수 있는 여지가 있는 경우

unchecked : 상황을 개선할 수 있는 여지가 없거나, APP을 종료시키는 게 가장 좋은 방법일 때

 

파일을 찾아달라는 것은 복구가 가능하다 -> checked

API의 사용방법을 어겨서 발생하는 경우 [배열이 3까지 정의되어 있고 값이 있는데 10을 호출할 때] -> unchecked

 

 

직접 Exception class 만들기

(1) unchecked로 예외 만들기

package org.opentutorials.javatutorials.exception;
class DivideException extends RuntimeException {
    
    DivideException(){
        super();
    } // 기본 생성자
    
    DivideException(String message){
        super(message);
    } // 기본 생성자가 아닌것은 우리가 만들어 줘야함.
}
class Calculator{
    int left, right;
    public void setOprands(int left, int right){        
        this.left = left;
        this.right = right;
    }
    public void divide(){
        if(this.right == 0){
            throw new DivideException("0으로 나누는 것은 허용되지 않습니다.");
    
        
    	}
        System.out.print(this.left/this.right);
    }
}
public class CalculatorDemo {
    public static void main(String[] args) {
        Calculator c1 = new Calculator();
        c1.setOprands(10, 0);
        c1.divide();
    }
}

 

(2) checked로 예외 만들기

package org.opentutorials.javatutorials.exception;
class DivideException extends Exception {
    DivideException(){
        super();
    }
    DivideException(String message){
        super(message);
    }
}
class Calculator{
    int left, right;
    public void setOprands(int left, int right){        
        this.left = left;
        this.right = right;
    }
    public void divide(){
        if(this.right == 0){
            try {
                throw new DivideException("0으로 나누는 것은 허용되지 않습니다.");
            } catch (DivideException e) {
                e.printStackTrace();
            }
        }
        System.out.print(this.left/this.right);
    }
}
public class CalculatorDemo {
    public static void main(String[] args) {
        Calculator c1 = new Calculator();
        c1.setOprands(10, 0);
        c1.divide();
    }
}

 

자세한 내용은 

https://opentutorials.org/module/516/6228

 

예외 3 - 만들기 - Java

소비자에서 생산자로 지금까지 API의 소비자로서 API 측에서 던지는 예외를 처리하는 방법을 알아봤다. 이번에는 API의 생산자로서 소비자들에게 API를 제공하는 입장에 서보자. 전 시간에 사용했

opentutorials.org

https://www.youtube.com/watch?v=Am5meHwYDWE&list=PLuHgQVnccGMCeAy-2-llhw3nWoQKUvQck&index=132 

확인하자.

Comments