국비교육(22-23)

19일차(3)/java(24) : Exception

서리/Seori 2022. 11. 1. 18:37

Step13_Exception

- java에서 어떤 상황에서 예외(오류)가 나타나는지 관찰한다.

 

- 자바의정석 3판 p414

 

<MainClass01>

package test.main;

import java.util.Scanner;

public class MainClass01 {
	public static void main(String[] args) {
		Scanner scan=new Scanner(System.in);
		System.out.println("숫자입력:");
		//숫자 형식의 문자열을 입력받는다. "10" "20" "10.1" 등등
		String inputNum=scan.nextLine();
		try {
			//입력한 숫자를(문자열) 실제 숫자로 바꾼다.
			double num=Double.parseDouble(inputNum);		
			//입력한 숫자에 100을 더한다.
			double result=num+100;
			System.out.println("입력한 숫자+100: "+result);
		}catch(NumberFormatException nfe) {
			/*
			 * 실행스택에서 일어난 일을 콘솔창에 출력하기
			 * (자세한 예외정보를 예외객체가 콘솔창에 출력하게 하기)
			 */
			nfe.printStackTrace();
		}
		
		System.out.println("무언가 중요한 마무리 작업을 하고 main 메소드가 종료됩니다.");
	}
}

- try~catch 절의 사용

 

- 예외처리(Exception Handling)란, 프로그램 실행 시 발생할 수 있는 예외의 발생에 대비한 코드를 작성하는 것.

- 예외처리의 목적은 예외의 발생으로 인한 프로그램의 갑작스런 비정상 종료를 막고, 정상적인 실행상태를 유지할 수 있도록 하는 것이다.

 

- 예외가 발생하는 경우: 숫자가 아닌 문자를 집어넣었을 때, 문자를 넣지 않았을 때 등등 (NumberFormatException)

- 저 오류 문장이 출력되면 마지막 syso '마무리작업' 부분의 메소드가 수행되지 않고 끝난다.

 

double num=Double.parseDouble(inputNum);

- 이 부분에서 오류가 발생한 것이다. 숫자로 변환할 수 없기 때문에!

 

- 오류가 나더라도 마무리 작업은 진행될 수 있도록 하고싶다면?

→ 이런 일이 발생할 가능성을 대비해 다른 코딩을 해두는것.

try { } catch( ) { } 문을 사용한다.

 

 

 

- try { 예외가 발생할 가능성이 있는 문장 } catch ( 타입 지역변수 ) { 예외상황에서 처리를 위한 문장 };

 

- NumberFormatException 이라는 클래스가 생성되어있다.

- NumberFormatException을 타입으로 넣고, 지역변수 nfe 로 설정

 

- 위와 같이 작성해두면, NumberFormatException 타입의 exception이 발견되면

 객체의 참조값이 자동으로 nfe로 전달되고 catch{}가 실행된다.

 

- java 에서는 경미한 오류는 전부 예외로 처리한다.

 

- 초록: 예외가 발생하지 않을 경우의 경로 (try → 마무리작업)

- 빨강: 예외가 발생할 경우의 경로 (try → catch → 마무리작업)

 

- 오류와 관계없이 맨 아래의 syso 는 실행한다!!

 (오류가 나지만 무시하고 끝까지 실행하는 것)

 

- 예외객체에도 메소드가 있다!

printStackTrace(); : 오류 메시지가 콘솔창에 출력되는 메소드

 

- 메소드의 인자가 전달되듯이, nfe 변수로 예외객체의 참조값이 전달되어 예외객체의 메소드를 사용할 수 있다.

 

 

** NumbeFormatException 설명 : 링크

 

- java의 대부분의 클래스는 Serializable이라는 인터페이스를 구현했다.

 

- 모든 예외타입의 부모타입은 Exception이다.

- 원한다면 Exception을 상속받아 커스텀 Exception도 만들 수 있다. 

 

- 실행(runtime)중에 발생하는 예외, 컴파일(compile)중에 발생하는 예외가 있다.

- Compile Exception 의 경우 이클립스가 바로 오류 표시를 해준다.

Runtime Exception 은 다양한 Exception들의 부모타입에 포함되어 있다.

 


 

<MainClass02>

package test.main;

import java.util.Scanner;

public class MainClass02 {
	public static void main(String[] args) {
		Scanner scan=new Scanner(System.in);

		System.out.println("나눌 수 입력:");
		String inputNum1=scan.nextLine();
		System.out.println("나누어지는 수 입력:");
		String inputNum2=scan.nextLine();
		try {
			int num1=Integer.parseInt(inputNum1);
			int num2=Integer.parseInt(inputNum2);
			
			int result=num2/num1; //몫
			int result2=num2%num1; //나머지
			System.out.println(num2+" 를 "+num1+" 으로 나눈 몫 :"+result);
			System.out.println(num2+" 를 "+num1+" 으로 나눈 나머지 :"+result2);
		}catch(NumberFormatException nfe) {
			System.out.println("숫자 형식으로 입력해 주세요.");
		}catch(ArithmeticException ae) {			
			System.out.println("어떤 수를 0으로 나눌 수는 없어요.");
		}catch(Exception e) {
			System.out.println("예외가 발생했습니다.");
		}finally {//위의 예외 발생과 상관없이 반드시 실행이 보장되는 블럭
			System.out.println("무언가 중요한 마무리 작업을 해요!");
		}
		System.out.println("main 메소드가 정상 종료 됩니다.");
	}
}

try {예외 발생 가능성이 있는 문장}

 catch {예외처리를 위한 문장} 

 → finally {예외와 관계없이 반드시 실행되어야 할 문장}

 

 

NumberFormatException : 숫자가 들어가야 하는 자리에 숫자가 아닌 값을 입력하면 발생하는 exception
ArithmeticException : 어떤 숫자를 0으로 나누면 발생하는 exception

 

- catch 2개 사용하면 오류가 발생했을 때 더 다양하게 대비할 수 있다.

 

catch(Exception e) {
	System.out.println("예외가 발생했습니다.");
}

- 예상할 수 없는 exception이 발생할 경우를 대비해서,

모든 예외(오류)상황의 부모타입인 exception으로 받으면 된다.

 

초록: 정상적인 경로 (try → finally)

빨강: 오류 발생시의 경로 (try → catch → finally)

- 어느 쪽이든 마무리작업 부분은 잘 실행된다.

 

- finally 예약어 : 예외가 발생하건 하지 않건 마무리작업을 실행하도록 한다.

 → try~catch 구문의 결과에 관계없이 무조건 실행한다.

 


 

<MainClass03>

package test.main;

public class MainClass03 {
	public static void main(String[] args) {
		System.out.println("main메소드가 시작됩니다.");
		
		try {
			/*
			 * 실행의 흐름을 스레드라고 하는데 스레드를 임의로 5초동안 잡아두기
			 * 컴파일시에 발생하는 Exception이 발생하기 때문에 반드시 try~catch 블럭으로
			 * 예외처리를 해야 한다.
			 */
			Thread.sleep(5000);
		} catch (InterruptedException e) {			
			e.printStackTrace();
		}
		
		System.out.println("main메소드가 종료됩니다.");
	}
}

 

- 실행의 흐름을 '스레드' 라고 한다.

 

sleep : 1/1000초 단위로 실행을 잠깐 멈출수있다.

 ex) 5초라면 5000 입력

 

- 근데 오류가 발생한다....

 Unhandled exception type InterruptedException : 컴파일 시에 발생하는 예외

 

- try-catch 쪽을 클릭하면 try구문으로 묶어주는 구문이 자동완성된다.

 

** InterruptedException 설명 : 링크

- Runtime Exception이 아니다.(부모타입에 Runtime Exception이 없다)

- 그래서 실행중에 넣을 수 없기 때문에 수정이 필요하다.

 


 

<MainClass04>

package test.main;

import java.io.File;
import java.io.IOException;

public class MainClass04 {
	public static void main(String[] args) {
		/*
		 * 현재 존재하거나 혹은 존재하지 않는 파일이나 폴더를 제어할 수 있는 File 객체를 생성해서
		 * 참조값을 f라는 지역변수에 담기
		 */
		File f=new File("c:/acorn202210/myFolder/memo.txt");
		
		//실제 memo.txt파일이 존재하지 않으면 파일을 만들고
		//존재하면 memo.txt파일을 삭제하도록 프로그래밍해 보세요.
		try {
			if(f.exists()) {
				f.delete();
			}else {
			f.createNewFile();
			}
		} catch (IOException e) {			
			e.printStackTrace();
		}
		
	}
}

 

 

- 파일 시스템에 관련된 작업을 하는 객체

- 파일객체의 기능을 이용하면 없던 파일을 만들거나 있던 파일을 제거하는 것도 가능하다.

 

- 생성자가 4개. 이중 첫번째 사용! (문자열로 access할 파일의 경로를 적어준다.)

 

new File("c:/경로/memo.txt");

- 실제 존재하지 않아도 된다. 존재하지 않으면 새로 만들고 존재하면 access한다.

 

- 만들기: make, create / 삭제하기: remove, delete

 

createNewFile : 새 파일 만들기

- 만드는데 성공하면 true, 실패하면 false를 내보낸다. 궁금하면 리턴값을 받아도 된다.

 

canRead : 읽을 수 있는지 여부 (read 권한이있는지)

 

- createNewFile 메소드를 사용하면 Exception이 발생한다.

- try~catch 문으로 작성하는 것 클릭!(자동완성됨)

 

- run하면 실제로 파일이 만들어진다.

 

.exists : 파일이 존재하는지 여부를 확인하는 메소드

- if문 안에 exists 를 넣는다!

 

- if 안에 있으면 삭제하는 구문을 넣고, else안에 없으면 생성하는 구문을 넣는다.

- run 하면 삭제와 파일 생성이 반복된다!

 


 

새 패키지(mypac)에 새 클래스 만들기

 

<WowException>

package test.mypac;

public class WowException extends RuntimeException{

	//생성자
	public WowException(String msg) {
		super(msg);
	}
}

- RuntimeException을 상속하는(부모 클래스로 갖는) WowException이라는 새 예외를 생성

- 생성자의 인자로 전달받은 문자열(msg)을 부모 생성자의 인자로 전달!

 

 

<MainClass05>

package test.main;

import java.util.Random;

import test.mypac.WowException;

public class MainClass05 {
	public static void main(String[] args) throws WowException {
		Random ran=new Random();
		
		//0~4 사이의 랜덤한 정수 얻어내기
		int ranNum=ran.nextInt(5);
		
		//우연히 가장 큰 수가 나오면 WowException을 발생시키기
		if(ranNum==4) {
			//throw 예약어와 함께 예외 객체를 생성하면 예외가 발생한다. 
			throw new WowException("놀랍네 이거");
		}
		
		System.out.println("main메소드가 종료됩니다.");
	}
}

 

- WowException이라고 하는 새 예외를 만들어본다!

- 새로운 예외를 만들려면 RuntimeException / Exception 둘 중 하나의 클래스는 상속해야 한다.

 

- Throw 예약어를 사용해서 프로그래머가 고의로 Exception을 발생시킬 수 있다.

 

 

- 생성자의 인자로 전달한 값이 가장 큰 값(4)이 나오면 이렇게 exception이 발생한다.

- 아래를 try~catch 문으로 묶으면 예외 처리가 가능하다.

 

- RuntimeException이 아니라 그냥 Exception 을 상속하면 throw 문에 추가 설명이 필요하다.

 

- Throw 예약어와 함께 예외 객체를 생성하면 예외가 발생한다.

- 예외를 의도적으로 발생시켜야 할 일이 종종 있다.

 


 

<MyUtil>

package test.mypac;

public class MyUtil {

	public static void draw() {
		System.out.println("5초 동안 그림을 그려요.");
		
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {			
			e.printStackTrace();
		}
		
		System.out.println("그림 완성!");
	}
	
	public static void send() throws InterruptedException {
		System.out.println("5초 동안 전송을 해요.");
		
		Thread.sleep(5000);		
		
		System.out.println("전송 완료!");
	}
}

 

 

<MainClass06>

package test.main;

import test.mypac.MyUtil;

public class MainClass06 {
	public static void main(String[] args) {
		
		try {
			MyUtil.send();
		} catch (InterruptedException e) {			
			e.printStackTrace();
		}
		
		MyUtil.draw();
	}
}

 

 

Thread.sleep(); 의 오류 해결방식이 둘이 다른데,

1 : try~catch 로 묶어서 직접 메소드안에서 처리하기

2 : 메소드를 사용하는 입장에서 처리(컨트롤)하는 것이 낫다고 판단하면 throw하기

 

- 2가 간편해 보이지만, 사용하는 쪽에서는 불편하다.

 (해당 예외를 호출하는 쪽으로 책임을 돌린 것!)

 

- 메인메소드에서 static 자원은 new할 필요없이 클래스명에 . 점 찍어서 사용한다.

 

- draw는 문제없이 사용할 수 있다.

- send는 수정이 필요하다. 사용하는 시점에서 처리해야한다. 결국 try~catch를 해줘야 한다.

 

- 처리해야 할 예외가 있을 때, throw하는 것만이 능사는 아니다. 어디에선가는 예외처리를 해줘야한다!

 

 

- try-catch / finally절 / throw 잘 알아두기!