sm 기술 블로그
객체지향 (예외) 본문
예외(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)
따라서 기본 문법은 다음과 같다
코드를 다음과 같이 바꿔보자
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
기본 문법은 다음과 같다.
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() 문제가 일어나는 이유
파일을 찾을 수 없거나, 읽을 수 없을 때 발생하는 것. 이것을 해결한다면
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();
}
}
위 코드는 다음과 같은 관계를 가지고 있다.
기본적으로 자신들이 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의 직계 자식이기 때문에 예외처리가 필요/불필요로 나눠진다.
부모 중에 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
https://www.youtube.com/watch?v=Am5meHwYDWE&list=PLuHgQVnccGMCeAy-2-llhw3nWoQKUvQck&index=132
확인하자.
'Java' 카테고리의 다른 글
객체지향) 컬렉션 프레임워크 (0) | 2022.04.21 |
---|---|
객체지향 (Object /enum / 복제,참조 /제네릭) (0) | 2022.04.19 |
객체지향 (API /접근제어자 /abstract /final /static /interface /다형성) (0) | 2022.04.17 |
객체지향 (유효범위[this] /생성자 /상속[super] /오버로딩 /오버라이딩 /클래스패스 /패키지[import]) (0) | 2022.04.15 |
객체지향(클래스,인스턴스,객체 / 클래스맴버와 인스턴스 맴버) (0) | 2022.04.14 |