Seren dev's blog
article thumbnail
이 글은 남궁성 님의 "자바의 정석: 기초편" 책을 읽고 정리한 내용입니다.

프로그램 에러/오류

프로그램이 실행 중 어떤 원인에 의해서 오작동을 하거나 비정상적으로 종료되는 경우

- 컴파일 에러 : 컴파일 시에 발생하는 에러
- 런타임 에러 : 실행 시에 발생하는 에러
- 논리적 에러 : 실행은 되지만, 의도와 다르게 동작하는 것
ex) 창고의 재고가 음수가 된다거나, 게임에서 비행기가 총알을 맞아도 죽지 않는 경우

소스 코드 컴파일 -> 컴파일러가 소스코드(*.java)에 대해 오타나 잘못된 구문, 자료형 체크 등의 기본적인 검사를 수행하여 오류가 있는지를 알려준다. -> 컴파일을 성공적으로 마치면, 클래스 파일(*.class)이 생성되고, 생성된 클래스 파일을 실행할 수 있다.

Java의 런타임 에러

- 에러(error) : 프로그램 코드에 의해서 수습될 수 없는 심각한 오류, 발생하면 복구할 수 없는 심각한 오류
ex) 메모리 부족(OutOfMemoryError), 스택오버플로우(StackOverflowError)
- 예외(exception) : 프로그램 코드에 의해서 수습될 수 있는 미약한 오류

에러는 어쩔 수 없지만 예외는 처리해야 한다.

오류(Exception과 Error)

Object - Throwable - Exception - RuntimeException
- IOException
- Error - OutOfMemoryError
Exception - IOException
- ClassNotFoundException
- RuntimeException
- ArithmeticException, ClassCastException, NullPointerException, IndexOutOfBoundsException

예외 클래스 분류

1. Exception 클래스와 그 자손들 (RuntimeException과 자손들 제외) - 예외처리 선택
- 사용자의 실수와 같은 외부 요인에 의해 발생
- FileNotFoundException : 존재하지 않는 파일의 이름을 입력
- ClassNotFoundException : 실수로 클래스의 이름을 잘못 적음
- DataFormatException : 입력한 데이터 형식이 잘못됨

2. RuntimeException클래스와 그 자손들 - 예외처리 필수
- 프로그래머의 실수로 발생하는 예외
- ArithmeticException : 정수를 0으로 나눔
- ClassCastException : 클래스간의 형변환을 잘못했음
- NullPointerException : 값이 null인 참조변수의 멤버를 호출하려 함
- IndexOutOfBoundsException : 배열의 범위를 벗어남

 

예외 처리(exception handling) : try-catch 문

에러는 어쩔 수 없지만 예외는 처리해야 한다.

정의 - 프로그램 실행 시 발생할 수 있는 예기치 못한 예외의 발생에 대비한 코드를 작성하는 것
목적 - 프로그램의 비정상 종료를 막고, 정상적인 실행상태를 유지하는 것

발생한 예외를 처리하지 못하면, 프로그램은 비정상 종료되고, 처리하지 못한 예외는 JVM의 예외 처리기가 받아서 예외의 원인을 화면에 출력한다.

try - catch문

하나의 try블럭에는 여러 개의 catch 블럭이 올 수 있으며, 이 중 발생한 예외의 종류와 일치하는 단 한 개의 catch 블력만 수행된다.

예외가 발생하면, 발생한 예외에 해당하는 클래스의 인스턴스가 생성된다.
첫번째 catch블럭부터 차례로 내려가면서 catch블럭의 괄호()내에 선언된 참조변수의 종류와 예외 클래스의 인스턴스에 instanceof 연산자를 이용해 검사한다.
검사결과가 true인 catch블럭을 찾게 되면 블럭을 수행하고 try-catch문장을 빠져나간다.

class Ex8_3 {
	public static void main(String args[]) {
		System.out.println(1);			
		System.out.println(2);

		try {
			System.out.println(3);
			System.out.println(0/0);
			System.out.println(4);  // 실행되지 않는다.
		} catch (Exception e){     // ArithmeticException대신 Exception을 사용.
			System.out.println(5);
		}	// try-catch의 끝

		System.out.println(6);
	}	// main메서드의 끝
}

모든 예외 클래스는 Exception 클래스의 자손이므로 catch블럭에 Exception 클래스를 사용하면 어떤 종류의 예외가 발생하더라도 예외를 처리할 수 있다.

class Ex8_4 {
	public static void main(String args[]) {
		System.out.println(1);			
		System.out.println(2);
		try {
			System.out.println(3);
			System.out.println(0/0);
			System.out.println(4); 	// 실행되지 않는다.
		} catch (ArithmeticException ae)	{
			if (ae instanceof ArithmeticException) 
				System.out.println("true");	
			System.out.println("ArithmeticException");
		} catch (Exception e){ //출력되지 않는다.
			System.out.println("Exception");
		}	// try-catch의 끝
		System.out.println(6);
	}   // main메서드의 끝
}


예외가 발생되었을 때 생성되는 예외 클래스의 인스턴스에는 발생한 예외에 대한 정보가 담겨져 있으며, getMessage()와 printStackTree()를 통해서 정보를 얻을 수 있다.
printStackTree() : 예외발생 당시의 호출스택(Call Stak)에 있었던 메서드의 정보와 예외 메시지를 화면에 출력한다.
getMessage() : 발생한 예외 클래스의 인스턴스에 저장된 메시지를 얻을 수 있다.

class Ex8_5 {
	public static void main(String args[]) {
		System.out.println(1);			
		System.out.println(2);

		try {
			System.out.println(3);
			System.out.println(0/0); // 예외발생!!!
			System.out.println(4);   // 실행되지 않는다.
		} catch (ArithmeticException ae)	{
			ae.printStackTrace();
			System.out.println("예외메시지 : " + ae.getMessage());
		}	// try-catch의 끝

		System.out.println(6);
	}	// main메서드의 끝
}

 

멀티 캐치블럭

try {
    ...
} catch (ExceptionA | ExceptionB e) {
     e.methodA();  // 에러. ExceptionA에 선언된 methodA()는 호출불가
     
     if (e instanceof ExceptionA) {
          ExceptionA e1  = (ExceptionA)e;
          e1.methodA();  // OK. ExceptionA에 선언된 메서드 호출 가능
      }
}

멀티catch 블럭의 '|' 기호로 연결된 예외 클래스가 조상과 자손의 관계에 있다면 컴파일 에러가 발생한다.

예외 발생시키기

프로그래머가 고의로 예외를 발생시킬 수 있다.
1. 연산자 new를 이용해 발생시키려는 예외 클래스의 객체를 만든 다음
2. 키워드 throw를 이용해서 예외를 발생시킨다.

class Ex8_6 {
	public static void main(String args[]) {
		try {
			Exception e = new Exception("고의로 발생시켰음.");
			throw e;	 // 예외를 발생시킴
		//  throw new Exception("고의로 발생시켰음.");

		} catch (Exception e)	{
			System.out.println("에러 메시지 : " + e.getMessage());
			e.printStackTrace();
		}
		System.out.println("프로그램이 정상 종료되었음.");
	}
}

 

checked예외, uncheckd예외

checkd예외 : Exception 클래스와 그 자손들. 이 예외가 발생할 가능성이 있는 문장들에 대해 예외처리를 해주지 않으면 컴파일 조차 되지 않는다.
uncheckd예외 : RuntimeException 클래스와 그 자손들. 성공적으로 컴파일되지만 실행 후 RuntimeException이 발생하여 비정상적으로 종료된다. 예외 처리를 강제하지 않는다. 이 예외도 예외처리를 강제한다면 참조변수와 배열이 사용되는 모든 곳에 예외처리를 해주어야 한다.

메서드에 예외 선언하기

예외를 처리하는 방법

1. try-catch문
2. 메서드에 키워드 throws를 사용해서 메서드 내에서 발생할 수 있는 예외를 선언
예외를 처리하는 것이 아니라 호출한 메서드로 전달해주는 것
호출한 메서드에서 예외처리를 해야만 할 때 사용

void method() throws Exception1, ... ExceptinoN {
}

메서드 내에서 발생할 가능성이 있는 예외를 메서드의 선언부에 명시하여 이 메서드를 호출하는 쪽에서 이에 대한 처리를 하도록 강요한다.

예제1

class Ex8_9 {
	public static void main(String[] args) throws Exception {
		method1();	 // 같은 클래스내의 static멤버이므로 객체생성없이 직접 호출가능.
  	}	// main메서드의 끝

	static void method1() throws Exception {
		method2();
	}	// method1의 끝

	static void method2() throws Exception {
		throw new Exception();
	}	// method2의 끝
}
실행결과
Exception in thread "main" java.lang.Exception
at Ex8_9.method2()
at Ex8_9.method1()
at Ex8_9.main()

method2()에서 예외를 강제적으로 발생했으나 try-catch문으로 예외처리를 해주지 않았으므로 method1()에게 예외를 넘겨주고, method1()은 main메서드에게 예외를 넘긴다.
그러나 main메서드에서조차 예외처리를 해주지 않았으므로 프로그램이 예외로 인해 비정상적으로 종료된다. 결국 어느 한 곳에서는 반드시 try-catch문으로 예외처리를 해주어야 한다.

메서드에 예외를 선언하면 checked예외를 try-catch문으로 처리하지 않아도 컴파일 에러가 발생하지 않는다.

예외2

import java.io.*;

class Ex8_10 {
	public static void main(String[] args) {
		try {
			File f = createFile(args[0]);
			System.out.println( f.getName()+"파일이 성공적으로 생성되었습니다.");
		} catch (Exception e) {
			System.out.println(e.getMessage()+" 다시 입력해 주시기 바랍니다.");
		}
	}	// main메서드의 끝

	static File createFile(String fileName) throws Exception {
		if (fileName==null || fileName.equals(""))
			throw new Exception("파일이름이 유효하지 않습니다.");
		File f = new File(fileName);		//  File클래스의 객체를 만든다.
     	// File객체의 createNewFile메서드를 이용해서 실제 파일을 생성한다.
		f.createNewFile();
		return f;		// 생성된 객체의 참조를 반환한다.
	}	// createFile메서드의 끝
}	// 클래스의 끝

throws Exception을 추가하지 않으면 다음과 같이 컴파일 에러가 발생한다.

-> throws Exception을 선언하여 예외를 전달하거나, 예외가 발생한 메서드 안에서 try-catch문을 통해 예외를 처리해주어야 함

사용자 정의 예외

프로그래머가 새로운 예외 클래스를 정의하여 사용할 수 있다. 보통 Exception 클래스 또는 RuntimeException 클래스로부터 상속받는 클래스를 만든다.

class MyException extends Exception {
    
    //에러코드 값을 저장하기 위한 필드
    private final int ERR_CODE;  //생성자를 통해 초기화한다.
    
    MyException(String msg, int errCode) {
        super(msg);
        ERR_CODE = errCode;
    }
    
    MyException(String msg) {
        this(msg, 100);
    }

    public int getERR_CODE() {
        return ERR_CODE;
    }
}

기존의 예외 클래스는 주로 Exception을 상속받아서 'checked예외'로 작성하는 경우가 많았지만, 요즘은 예외처리를 선택적으로 할 수 있도록 RuntimeException을 상속받아서 작성하는 쪽으로 바뀌어가고 있다.
checked 예외는 반드시 예외처리를 해주어야 하기 때문에 예외처리가 불필요한 경우에도 try-catch문을 넣어서 코드가 복잡해지기 때문이다.

예외 되던지기(Exception re-throwing)

한 메서드에서 발생할 수 있는 예외가 여럿인 경우, 몇 개는 try-catch문, 나머지는 선언부에 지정하여 호출한 메서드에서 처리하도록 할 수 있다.
예외 되던지기는 예외를 처리한 후에 다시 예외를 생성해서 호출한 메서드로 전달하는 것이다.
하나의 예외에 대해서 예외가 발생한 메서드와 이를 호출한 메서드 양쪽 모두에서 처리해줘야 할 작업이 있을 때 사용된다.
주의할 점은 예외가 발생할 메서드에서 try-catch문을 사용해서 예외처리를 해줌과 동시에 선언부에 throws를 사용해 예외를 지정해야 한다는 것이다.

class Ex8_12 {
	public static void main(String[] args) {
		try  {
			method1();		
		} catch (Exception e)	{
			System.out.println("main메서드에서 예외가 처리되었습니다.");
		}
	}	// main메서드의 끝

	static void method1() throws Exception {
		try {
			throw new Exception();
		} catch (Exception e) {
			System.out.println("method1메서드에서 예외가 처리되었습니다.");
			throw e;			// 다시 예외를 발생시킨다.
		}
	}	// method1메서드의 끝
}

반환값이 있는 메서드의 경우 catch블럭에도 return 문이 있어야 한다.

연결된 예외(chained exception)

try {
    startInstall();  // SpaceException 발생
    copyFiles();
} catch (SpaceException e) {
    InstallException ie = new InstallException("설치중 예외발생");  // 예외 생성
    ie.initCause(e);  //InstallException의 원인 예외를 SpaceException으로 지정
    throw ie;  //InstallException 발생시킨다.
} ...

initCause는 Throwable 클래스에 정의되어있다.

원인 예외 사용 이유

1. 여러가지 예외를 하나의 큰 분류의 예외로 묶어서 다루기 위해서다.
- 상속관계가 아니어도 사용가능
2. checked 예외를 unchecked예외로 변경 가능
- new RuntimeException(new MemoryException());
- RuntimeException(Throwable cause) // 원인 예외를 등록하는 생성자

Reference

- 소스코드 링크

728x90
profile

Seren dev's blog

@Seren dev

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!