[그린컴퓨터] Server/JAVA(자바 JDK)

예외처리 { 예외클래스, 예외처리, 컴파일러의 예외체크, 예외처리미루기 }

Ben의 프로그램 2023. 7. 4. 12:44
728x90

  • 오류는 크게 2가지로 볼 수 있다. 

    컴파일 오류와 런타임 오류가 이에 해당한다. 

  • 컴파일 오류는 컴파일러가 컴파일 하는 과정에서 발견하는 오류이다. 컴파일 오류는 잘못된 문법을 사용했을 때 발생하게 된다. 컴파일 오류가 발생하면 .class 라는 파일이 생성되지 않고 컴파일러가 개발자에게 오류 메시지를 통해 알려주게 된다. 
package main;

public class Ex1 {

	public static void main(String[] args) {
		int value;
		
		int value; // 변수 이름 중복되어 오류가 발생한다. 
		
		value = "aa"; // 변수 타입과 맞지 않는 값을 대입하여 오류가 발생함 
	}

}
  • 예시를 보면 쉽게 이해할 수 있다. 위에는 2 가지 이유로 컴파일 오류가 발생하고 있다. 그리고 오류가 발생한다는 것을 이클립스가 보여주고 있는데, 이렇게 오류가 보이는 이유는 다음과 같다. 

  • project 에 메뉴 중에 build automatically 가 체크되어 있는 것을 확인할 수 있는데, build automatically 는 우리가 코드를 수정할 때마다 이클립스가 컴파일러에게 변경된 코드를 빌드하도록 계속 명령을 내리고 있는 것이다. 

    build automatically 를 해제하고 코드를 수정하면 .class 파일이 수정되지 않는 것을 확인할 수 있다. 실제로 실행 버튼을 눌렀을 때만 .class 파일이 업데이트 되는 것을 알 수 있습니다. 

  • 런타임 오류는 컴파일이 성공적으로 실행된 이후 실제로 코드를 실행시킬 때 발생하는 오류이다. 
  • 런타임 오류는 프로그램이 순서대로 실행되다가 중간에 발생하며, 프로그램이 완전히 수행되지 못하고 오류가 발생한 순간 멈추게 됩니다. 
  • 예를들어 위 그림의 코드를 보면 배열 index 는 0~4까지인데, 5번 인덱스를 사용한 것을 볼 수 있습니다. 이 오류는 컴파일 과정에서는 파악할 수 없으며, 런타임 과정에서 확인하게 됩니다. 런타임 오류가 발생하면 프로그램의 수행이 멈추게 되며 개발자에게 어디서 런타임 오류가 발생했는지 힌트를 주게 됩니다. 

  • 위의 그림처럼 컴파일 과정에서는 오류를 발견하지 못하지만 실행시 런타임 오류가 발생하는 것을 확인할 수 있습니다. 
  • 컴파일 오류는 개발자가 사전에 알 수 있지만 런타임 오류는 실행 중에 발생하므로 개발자 입장에서는 굉장히 부담이 되는 오류입니다. 

  • 런타임 오류에는 2가지가 있습니다. 
  • 오류 Error 는 프로그램에서 처리할 수 없는 심각한 에러를 의미합니다. 

    예외 Exception 은 프로그램에서 처리할 수 있는 간단한 에러를 의미합니다. 
  • 심각한 에러는 처리할 수 없지만, 간단한 에러는 프로그램에서 처리할 수 있습니다. 
  • 예외가 발생하는 것을 미리 대비하면 프로그램이 정상적으로 종료됩니다. 

  • 예외가 발생하면 예외 처리를 위해 예외 클래스를 사용합니다. 
  • 예외 클래스는 계층 구조를 가지면 위와 같습니다. 
  • IOException 은 입출력 오류입니다. 예를 들어 프로그램에서 어떠한 String 을 텍스트 문서로 보내고 싶을 때 사용하는 것이 입출력 기능인데, 이때 발생하는 오류들이 이것에 해당합니다.

    FileNotFoundException 은 내가 어떤 파일을 찾고 싶은데 파일이 없다면 발생하는 오류입니다.
  • ClassNotFoundException 은 컴파일 과정 끝에 생긴 바이트 코드를 우리는 실행하게 되는데, 이 바이트 코드가 컴파일 오류 등으로 생성되지 않았는데 실행시키려고 할 때 생기는 오류입니다. 
  • ArithmeticException 은 수학 오류로 0으로 나누기를 한다든가 하는 등의 오류입니다. 
  • NullPointException 은 참조변수가 비어있는 객체를 가리키고 있는 상황에서 참조변수를 사용해서 객체가 가지고 있는 멤버를 사용하려고할 때 발생하는 오류입니다. 

  • 예외를 처리하는 것은 프로그램에서 예외가 발생하는 것을 대비한 코드를 작성하는 것을 의미합니다. 이렇게 예외를 미리 대비하는 코드를 작성하는 이유는 예외가 발생했을 때 프로그램이 비정상적 종료하는 것을 막기 위해서 활용합니다. 
  • 예외처리를 위해서는 try - catch 문을 활용합니다. 
  • try 블록에는 예외가 발생할 가능성이 있는 코드를 작성합니다. 
  • catch 블록에는 예외클래스를 사용하여 해당 예외가 발생했을 때 실행할 코드를 작성합니다. 

  • 예외가 처리되는 과정을 살펴보면 다음과 같습니다. 
  • 1. 예외가 발생한다. 
  • 2. 해당 예외와 일치하는 catch 블록을 찾는다.
  • 3. catch 블록의 코드를 수행한다. 
  • 4. 이후 문장을 계속해서 수행한다. 
package main;

public class Ex3 {

	public static void main(String[] args) {
		int[] arr = new int[5];
		
		try {
			arr[5] = 5;  // 예외가 발생할 가능성이 있는 코드를 작성한다. 
		} catch (IndexOutOfBoundsException e) {  // 예외 클래스를 사용하여 catch 문을 작성한다. 
			System.out.println(e);  // 예외 클래스에 해당하는 에러가 발생했을 때 실행된다. 
		}
		
		System.out.println("프로그램이 정상 종료됩니다.");
	}

}

  • catch 문이 정상적으로 작동한 것을 알 수 있습니다.

  •  위의 예제 코드에서는 프로그램에서 발생하는 오류에 해당하는 오류 클래스를 catch 문으로 작서하지 않았기 때문에 프로그램이 비정상 종료되는 것을 확인할 수 있습니다.  

  • e.printStackTrace 를 활용하면 발생한 위치와 예외 메시지를 출력합니다. 
  • Exception 예외는 모든 예외를 처리하는 예외로 편리하지만 많이 사용하는 것은 출력되지 않습니다. 

오류를 찾는 연습의 필요성

  • 에러 메시지는 지금은 프로그램이 간단해서 찾기 쉬워보이지만 로그가 길어지게 되면 오류가 어디서 어떻게 발생했는지 알아보는 것이 굉장히 오래걸리고 힘들다. 그렇기 때문에 지금부터 오류를 미리미리 확인하고 대처하는 방법을 익혀두는 것이 매우 중요하다고 할 수 있다. 

  • 어떤 종류의 Exception 은 컴파일러가 예외 처리를 했는지 체크해준다. 

    그런데, 이것은 컴파일 오류와는 별개인 점을 생각해야한다. 런타임 오류이지만 미리 오류 체크를 하도록 강제로 컴파일 오류를 만들어 주는 것이다. 예외 처리를 하면 컴파일 오류가 사라지게끔 오류를 미리 예방해주는 아주 고마운 기능이다. 
package main;

import java.io.FileInputStream;

public class Ex6 {

	public static void main(String[] args) {
		FileInputStream fis = new FileInputStream("src/main/a.txt"); // 컴파일러가 예외 처리를 
        // 유도하는 컴파일 오류가 발생한다. 
	}

}
  • 일반적인 컴파일 오류는 문법을 고치라고 알려주는데, 위에서는 예외를 처리하라는 안내사항을 알려준다. 
package main;

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

public class Ex6 {

	public static void main(String[] args) {
		try {
			FileInputStream fis = new FileInputStream("src/main/a.txt");
		} catch (FileNotFoundException e) {
			System.out.println(e);
		}
		
		System.out.println("프로그램이 정상 종료됩니다.");
	}
}
  • 위와 같이 예외 처리를 하면 컴파일 오류가 더 이상 발생하지 않게 된다. 

  • 대신 현재 해당 파일이 없으므로 FileNotFoundException 이 발생하는 것을 확인할 수 있다. 

  • 단, 컴파일러가 모든 예외를 검사하고 있지는 않다는 것을 알아야 한다. 

  • 예외 처리도 if 문 처럼 진화를 했는데, finally 문을 갖게 되었다. 
  • 특징이 있는데 catch 는 여러번 연결되어 사용할 수 있지만 finally 블록은 맨 끝에 와야 하고 한 번만 사용할 수 있다. finally 블록은 선택사항이다. 즉, 반드시 실행되어야 하는 코드가 있는 경우 사용하게 된다. 
  • 스트림 클래스를 사용할 때 주의해야할 점이 있다. 텍스트 파일을 여는 코드인데, 파일 스트림을 열면 프로그램이 끝날 때까지 메모리를 차지하고 있다. 그래서 사용이 끝나면 .close 를 활용하여 꼭 닫아주어야 한다. 

    즉, .close 와 같이 반드시 실행되어야 하는 코드들을 finally 블록에 담아서 실행되도록 한다는 것이다. 

  • finally 블록에서 fis.close( ) 부분도 예외처리를 하라는 메시지가 보인다. 
  • 또한 fis.close( ) 는 fis stream 이 생성되었을 때만 실행되어야 하므로 다음과 같이 코드를 수정해주어야 한다. 

package main;

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

public class Ex7 {

	public static void main(String[] args) {
		FileInputStream fis = null;
		
		try {
			fis = new FileInputStream("src/main/a.txt");
		} catch (FileNotFoundException e) {
			System.out.println(e);
		} finally {
			if (fis !=  null) {
				try {
					fis.close();
				} catch (IOException e) {
					System.out.println(e);
				}
				System.out.println("사용한 리소스를 닫았습니다. ");
			}
		}
	}
}

연습문제 1

  • Q. 위 프로그램을 실행시키면 발생하는 오류를 예외 처리 하여 프로그램이 정상 종료되도록 하라.  

  • try catch 문을 활용하여 예외처리를 해준 것을 알 수 있다. 

연습문제 2

  • Q. 정상적으로 실행되도록 예외처리를 수행하라.

package quiz;

public class Quiz2 {
	
	public static void main(String[] args) {
		Object x = new Integer(0);
		try {
			System.out.println((String)x); // Integer 클래스를 String 클래스로 형변환			
		} catch (ClassCastException e) {
			System.out.println(e);
		}
		System.out.println("정상적으로 프로그램이 종료됩니다.");
	}
}

프로젝트 단계

  • 예외 처리는 실제로 하게 되면 생각보다 굉장히 까다롭다고 한다. 
  • 설계 -> 구형 -> 테스트 
  • 테스트 단계에서 프로그램을 여러 방면으로 사용한 이후 서비스를 제공하게 된다. 이때 예외 처리를 같이 해주게 된다.

  • 예외를 처리하는 방법에는 2가지가 있다. 
  • 첫 번째는 에러가 발생하는 그 시점에 exception 을 처리하는 것이다.
  • 두 번째는 에러가 발생하는 그 시점에 exception 을 처리하는 것이 아니라 코드를 호출하는 쪽에서 처리하게끔 예외를 던지는(throw) 방법이 있다. 

연습문제 3

  •  위 코드를 예외를 던져서 예외처리해보자. 

Ex8.openTextFile 말고 openTextFile 로 사용할 수 있다.

  • throw 문으로 예외를 메소드를 사용하는 쪽에서 처리하도록 던지고(throw) main 함수에서 예외처리를 구현할 수 있다.