[ 4주차 - 0903 ]
금일 커리큘럼
├ 09:00 ~ 12:00 자바 프로그래밍 기초 (예외처리 개념, try-catch, throws)
└ 13:00 ~ 18:00 자바 프로그래밍 기초 (throw new, 사용자 지정 예외, Java IO 개념)
1. 예외처리 개념 (*전일복습)
프로그램 실행 중에 발생할 수 있는 예상치 못한 상황(예외)에 대비하여
프로그램의 정상적인 흐름을 유지하고 예외 상황을 안전하게 처리하는 프로그래밍 기법
1.1 예외처리 문법
try-catch- 해당 로직 안에서 직접 예외를 처리하는 방식
- 예외 발생 가능성이 있는 코드를
try블록에 작성하고, 발생한 예외를catch블록에서 잡아서 처리 catch같은 경우 여러번 가능 (=케이스별 정의)
throws- 호출한 쪽으로 예외를 떠넘기는 방식 (=책임전가)
- 메서드 선언부에
throws예외타입 명시
예외 처리할 때 호출하는 쪽에서 반드시 try-catch로 잡거나, 또다시 throws로 위임해야 함
1.2 예외의 종류
int[] arr = {1, 2, 3};
System.out.println(arr[5]);
// ArrayIndexOutOfBoundsException | 배열 인덱스
String str = null;
System.out.println(str.length());
// NullPointerException | 없음
String num = "abc";
int value = Integer.parseInt(num);
// NumberFormatException | 숫자 타입 변환
int a = 10;
int b = 0;
System.out.println(a / b);
// ArithmeticException | 산수
import java.io.*;
FileReader fr = new FileReader("없는파일.txt");
// FileNotFoundException | 파일없음 (checked exception)
1.3 예외 계층 구조
Throwable
├── Error # 시스템 에러 (개발자가 처리 안 함)
└── Exception
├── Checked Exception # 컴파일러가 처리 강제 (외부자원)
│ (IOException, SQLException 등)
│
└── Unchecked Exception # 컴파일러 강제 X (내부코드)
└── RuntimeException
├── NullPointerException
├── ArithmeticException
├── ArrayIndexOutOfBoundsException
├── IllegalArgumentException
└── ...
- Checked Exception (체크드 예외)
- 컴파일 시점에 반드시 처리(try-catch or throws)해야 하는 예외
- Exception을 상속하지만 RuntimeException을 상속하지 않은 것들
- 파일/네트워크/DB 같이 외부 자원과 연결된 경우 자주 발생 (= 외부)
- 예: IOException, SQLException, FileNotFoundException
// 반드시 try-catch or throws 필요
import java.io.*;
public class ExamChecked {
public static void main(String[] args) {
try {
FileReader fr = new FileReader("test.txt"); // FileNotFoundException
} catch (FileNotFoundException e) {
System.out.println("파일이 존재하지 않습니다.");
}
}
}
- Unchecked Exception (= RuntimeException 계열)
- 컴파일러가 강제하지 않음 → 원하면 처리, 아니면 그냥 프로그램 터짐
- RuntimeException을 상속한 예외
- 주로 개발자 실수/논리 오류 때문에 발생 (= 내부)
- 예: NullPointerException, ArrayIndexOutOfBoundsException, ArithmeticException, NumberFormatException
public class ExamUnchecked {
public static void main(String[] args) {
int[] arr = {1,2,3};
System.out.println(arr[5]); // ArrayIndexOutOfBoundsException
}
}
| 구분 | Checked Exception | Unchecked Exception |
|---|---|---|
| 발생 시점 | 주로 외부 요인 (파일, DB, 네트워크) | 주로 코드 로직/개발자 실수 |
| 처리 강제 여부 | 컴파일러가 강제 (try-catch or throws 필요) | 강제 없음 |
| 상속 계층 | Exception (단, RuntimeException 제외) | RuntimeException 상속 |
| 예시 | IOException, SQLException, ClassNotFoundException | NullPointerException, ArithmeticException, ArrayIndexOutOfBoundsException |
2. try-catch
해당 로직 안에서 직접 예외를 처리하는 방식
try-catch-finally 심플 예시
import java.util.Scanner;
public class Exam {
final static Scanner sc = new Scanner(System.in);
public static void main(String[] args) {
System.out.print("입력: ");
String test = sc.nextLine();
try {
int i = Integer.parseInt(test);
System.out.println("try블럭: " + 10/i);
} catch (ArithmeticException e) {
System.out.println("산수오류 캐치");
System.out.println(e);
} catch (NumberFormatException e) {
System.out.println("넘버포맷오류 캐치");
System.out.println(e);
} finally {
System.out.println("파이널 무조건 실행됨");
}
}
}
# 실행결과 - 1
입력: 0
산수오류 캐치
java.lang.ArithmeticException: / by zero
파이널 무조건 실행됨
# 실행결과 - 2
입력: 반갑습니다
넘버포맷오류 캐치
java.lang.NumberFormatException: For input string: "반갑습니다"
파이널 무조건 실행됨
# 실행결과 - 3
입력: 5
try블럭: 2
파이널 무조건 실행됨
if, try-catch 활용방식
import java.util.Scanner;
public class ExamOrder {
final static Scanner sc = new Scanner(System.in);
public static void order01() {
int price = 0;
long maxMoney = 100_000;
System.out.printf("보유현금액: %,d원%n", maxMoney);
while (true) {
System.out.print("금액입력: ");
try {
price = Integer.parseInt(sc.nextLine());
if (price <= 0) {
System.out.println("0원이하 안됨");
continue; // 다시 입력
}
if (price > maxMoney) {
System.out.println("보유현금보다 높음");
continue; // 다시 입력
}
// 정상 입력이면 break
System.out.println("통과! / 가격: " + price + "원");
break;
} catch (NumberFormatException e) { // 넘버포맷
System.out.println("[NumberFormat!] 숫자로 입력해야 합니다.");
}
}
}
public static void main(String[] args) {
order01();
}
}
# 실행 결과 - catch
보유현금액: 100,000원
금액입력: #@(D
[NumberFormat!] 숫자로 입력해야 합니다.
# 실행 결과 - if
금액입력: 0
0원이하 안됨
# 실행 결과 - if2
금액입력: 10000000
보유현금보다 높음
# 실행 결과 - 통과
금액입력: 23
통과! / 가격: 23원
3. throws ( 예외 떠넘기기 )
호출한 쪽으로 예외를 떠넘기는 방식
- throws는 메서드 내부에서 직접 예외를 처리하지 않고, 호출한 쪽으로 예외 처리를 넘기는 키워드
- 즉 책임 전가 = "이런 예외가 날 수 있으니, 호출자가 책임지고 처리해주세요"
- 선언부에 throws 예외타입을 명시
리턴타입 메서드명(매개변수) throws 예외타입1, 예외타입2 {
// 코드...
}
unchecked 익셉션 방식
runtime 계열
public class ExamThrows {
public static int divide(int a, int b) throws ArithmeticException {
return a / b;
}
public static void main(String[] args) {
// 호출자가 예외 처리
try {
System.out.println(divide(10, 2)); // 정상 출력: 5
System.out.println(divide(10, 0)); // 예외 발생
System.out.println("이거 안나옴");
} catch (ArithmeticException e) {
System.out.println("예외 메시지: " + e.getMessage());
}
// 캐치 이후에도 실행됨
System.out.println("이후 실행됨");
}
}
# 실행 결과
5
예외 메시지: / by zero
이후 실햄됨
checked 익셉션 방식
import java.io.FileReader;
import java.io.IOException;
public class ExamCheckedThrows {
// FileReader는 파일이 없으면 IOException 발생 (Checked Exception)
public static void readFile() throws IOException {
FileReader fr2 = new FileReader("README.md");
System.out.println("README.md 파일 열기 완료");
FileReader fr1 = new FileReader("null.txt");
System.out.println("null.txt 파일 열기 완료");
}
public static void main(String[] args) {
// 호출자가 예외 처리
try {
readFile();
} catch (IOException e) {
System.out.println("없는파일: " + e.getMessage());
}
}
}
# 실행 결과
README.md 파일 열기 완료
없는파일: null.txt (지정된 파일을 찾을 수 없습니다)
throw new 직접 예외 사용
- 자바에서 예외 객체를 직접 만들어서 강제로 발생시키는 구문
public class ExamException00 {
public static void setAge(int age) {
if (age < 0) {
// 직접 익셉션 던짐
throw new IllegalArgumentException("음수야: " + age);
}
System.out.println("나이: " + age);
}
public static void main(String[] args) {
setAge(-5); // 잘못된 인자로 예외 발생
}
}
# 실행 결과
Exception in thread "main" java.lang.IllegalArgumentException: 음수야: -5
at week_04._0903.ExamException00.setAge(ExamException00.java:7)
at week_04._0903.ExamException00.main(ExamException00.java:13)
throw와 throws의 차이
- throw: 예외를 강제로 발생시킴 (throw new Exception())
- throws: 메소드가 예외를 던질 수 있음을 선언 (method() throws Exception)
4. 사용자 지정 예외
- Checked Exception 방식
class MyException extends Exception { /* ... */ }
- Exception을 상속
- 여전히 반드시 throws 선언 필요, 호출자는 try-catch 강제됨
- Unchecked Exception 방식
class MyException extends RuntimeException { /* ... */ }
- RuntimeException 상속 → Unchecked
- throws 선언 강제 없음, try-catch도 선택 사항
Checked 지정 예외 예시
// 사용자 지정 예외 (Checked)
class MyException extends Exception {
public MyException(String msg) {
super(msg);
}
public MyException(Exception e) {
super(e);
}
}
public class ExamChecked {
public static void checkNumber(int n) throws MyException {
if (n < 1 || n > 100) {
throw new MyException("범위를 벗어난 값: " + n);
}
System.out.println("정상 입력: " + n);
}
public static void main(String[] args) {
try {
checkNumber(-1); // 예외 발생
} catch (MyException e) {
System.out.println("[Checked Exception 예외 잡힘] " + e.getMessage());
}
}
}
[Checked Exception 예외 잡힘] 범위를 벗어난 값: -1
Unchecked 지정 예외 예시
// 사용자 지정 예외 (Unchecked)
class MyException extends RuntimeException {
public MyException(String msg) {
super(msg);
}
public MyException(Exception e) {
super(e);
}
}
public class ExamUnchecked {
public static void checkNumber(int n) {
if (n < 1 || n > 100) {
throw new MyException("범위를 벗어난 값: " + n);
}
System.out.println("정상 입력: " + n);
}
public static void main(String[] args) {
checkNumber(150); // try-catch 안 해도 컴파일 OK
System.out.println("이 코드는 실행되지 않음");
}
}
Exception in thread "main" week_04._0903.MyException: 범위를 벗어난 값: 150
at week_04._0903.ExamUnchecked.checkNumber(ExamUnchecked.java:15)
at week_04._0903.ExamUnchecked.main(ExamUnchecked.java:21)
5. 예외 주요 메서드 정리
| 메서드 | 설명 | 출력 예시 |
|---|---|---|
| getMessage() | 예외 생성 시 전달한 메시지 반환 | For input string: "abc" |
| toString() | 예외 클래스명 + 메시지 반환 | ...: For input string: "abc" |
| printStackTrace() | 예외 발생 경로(스택 트레이스)를 콘솔에 출력 | at Exam.main(Exam.java:5) |
| getClass() | 예외 객체의 클래스 타입 반환 | class java.lang.NumberFormatException |
| getStackTrace() | 스택 추적 정보를 배열(StackTraceElement[])로 반환 | [StackTraceElement, ...] |
| getCause() | 원인이 된 다른 예외를 반환 (체인 예외) | null 또는 java.io.FileNotFoundException |
| initCause(Throwable cause) | 원인 예외를 수동으로 연결 | e.initCause(new IOException()) |
public class ExamExceptionInfo {
public static void main(String[] args) {
try {
int num = Integer.parseInt("abc"); // NumberFormatException 발생
} catch (NumberFormatException e) {
System.out.println("getMessage(): " + e.getMessage());
System.out.println("toString(): " + e.toString());
System.out.println("getClass(): " + e.getClass());
System.out.println("getStackTrace()[0]: " + e.getStackTrace()[0]);
System.out.println("printStackTrace(): ");
e.printStackTrace();
}
}
}
# 실행 결과
getMessage(): For input string: "abc"
toString(): java.lang.NumberFormatException: For input string: "abc"
getClass(): class java.lang.NumberFormatException
getStackTrace()[0]: ExamExceptionInfo.main(ExamExceptionInfo.java:5)
printStackTrace():
java.lang.NumberFormatException: For input string: "abc"
at ExamExceptionInfo.main(ExamExceptionInfo.java:5)
6. Java IO (입출력) 개념
- 입출력
- Input (입력) : 키보드, 파일, 네트워크 등 외부에서 데이터를 읽어오는 것
- Output (출력) : 모니터, 파일, 네트워크 등 외부로 데이터를 내보내는 것
- 자바에서는 java.io 패키지를 통해 입출력 스트림(Stream) 개념으로 처리
- 스트림
- 데이터가 흐르는 통로 (단방향, 파이프처럼 생각하면 됨)
- 입력 스트림(InputStream/Reader) → 데이터를 프로그램 안으로
- 출력 스트림(OutputStream/Writer) → 데이터를 프로그램 밖으로
[데이터 소스] ──→ [입력 스트림] ──→ [프로그램] ──→ [출력 스트림] ──→ [데이터 대상]
- IO 분류
- 데이터 방향 : input, output
- 데이터 단위
- 바이트 단위 : InputStream, OutputStream (이미지, 영상, 바이너리 처리)
- 문자 단위 : Reader, Writer (텍스트 처리)
- 처리 방식
- 기반 스트림 : 실제 데이터와 연결되는 통로 (예: FileInputStream, FileReader)
- 주인공임
- 보조 스트림 : 기반 스트림을 감싸 기능 추가 (예: BufferedReader, BufferedWriter)
- 커넥터 역할 (보조, 장식)
- 기반 스트림 : 실제 데이터와 연결되는 통로 (예: FileInputStream, FileReader)
| 구분 | 바이트 스트림 | 문자 스트림 |
|---|---|---|
| 입력 | FileInputStream |
FileReader |
| 출력 | FileOutputStream |
FileWriter |
| 보조 | BufferedInputStream |
BufferedReader |
바이트 단위 입출력 (InputStream/OutputStream)
모든 데이터(이미지, 영상 등) 가능
- 이진 데이터 처리에 적합 (이미지, 오디오, 비디오 등)
- 1바이트씩 처리
- 모든 데이터를 바이트로 표현 가능
- InputStream 계열
- FileInputStream : 파일에서 바이트 단위로 읽기
- ByteArrayInputStream : 바이트 배열에서 읽기
- BufferedInputStream : 버퍼를 이용한 효율적인 읽기
- DataInputStream : 기본 데이터 타입 읽기
- OutputStream 계열
- FileOutputStream : 파일에 바이트 단위로 쓰기
- ByteArrayOutputStream : 바이트 배열에 쓰기
- BufferedOutputStream : 버퍼를 이용한 효율적인 쓰기
- DataOutputStream : 기본 데이터 타입 쓰기
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteExam {
public static void main(String[] args) {
String srcFile = "src/etc/IOTEST.md";
String srcCopy = "src/etc/IOTEST_COPY.md";
// try-with-resources를 사용한 자동 리소스 관리
try (FileInputStream in = new FileInputStream(srcFile);
FileOutputStream out = new FileOutputStream(srcCopy)) {
int byteData;
// 파일 끝(-1)까지 한 바이트씩 읽기
while ((byteData = in.read()) != -1) {
out.write(byteData);
}
System.out.println("파일 복사 완료!");
} catch (IOException e) {
System.err.println("파일 처리 중 오류: " + e.getMessage());
}
// 복사 완료 후 확인
System.out.println("[파일 읽기 - 바이트]");
System.out.println("─".repeat(10));
try (FileInputStream fis = new FileInputStream(srcCopy)) {
int byteData;
// 파일에서 한 글자씩 읽어서 int 값으로 반환
// 더 이상 읽을 문자가 없으면 -1 반환됨
while ((byteData = fis.read()) != -1) {
System.out.print((char) byteData);
}
} catch (IOException e) {
System.out.println("오류: " + e.getMessage());
}
System.out.println();
System.out.println("─".repeat(10));
}
}
파일 복사 완료!
[파일 읽기 - 바이트]
──────────
# IO TEST
──────────
문자 단위 입출력 (Reader/Writer)
텍스트 전용 (인코딩 지원, 다국어 처리 강점)
- 텍스트 파일 처리에 적합
- 문자 인코딩 자동 처리
- 유니코드 지원으로 다국어 처리 가능
- Reader 계열
- FileReader : 파일에서 문자 단위로 읽기
- StringReader : 문자열에서 읽기
- BufferedReader : 버퍼를 이용한 효율적인 읽기 (readLine() 제공)
- InputStreamReader : 바이트 스트림을 문자 스트림으로 변환
- Writer 계열
- FileWriter : 파일에 문자 단위로 쓰기
- StringWriter : 문자열로 쓰기
- BufferedWriter : 버퍼를 이용한 효율적인 쓰기
- OutputStreamWriter : 문자 스트림을 바이트 스트림으로 변환
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class FileExam {
public static void main(String[] args) {
String srcFile = "src/etc/IOTEST.md";
System.out.println("[파일 읽기 - 원본]");
System.out.println("─".repeat(10));
// 파일 읽기
try (FileReader fr = new FileReader(srcFile)) {
int ch;
// 파일에서 한 글자씩 읽어서 int 값으로 반환
// 더 이상 읽을 문자가 없으면 -1 반환됨
while ((ch = fr.read()) != -1) {
System.out.print((char) ch);
}
} catch (IOException e) {
System.out.println("오류: " + e.getMessage());
}
System.out.println();
System.out.println("─".repeat(10));
// 파일 쓰기 (apeend)
try (FileWriter fw = new FileWriter(srcFile, true)) {
fw.write("\n## java로 추가. 반갑습니다");
} catch (IOException e) {
System.out.println("오류: " + e.getMessage());
}
// 다시 읽기
System.out.println("\n[파일 쓰고 읽기 - 수정 후]");
System.out.println("─".repeat(10));
try (FileReader fr = new FileReader(srcFile)) {
int ch;
while ((ch = fr.read()) != -1) {
System.out.print((char) ch);
}
} catch (IOException e) {
System.out.println("오류: " + e.getMessage());
}
System.out.println();
System.out.println("─".repeat(10));
}
}
[파일 읽기 - 원본]
──────────
# IO TEST
──────────
[파일 쓰고 읽기 - 수정 후]
──────────
# IO TEST
## java로 추가. 반갑습니다
──────────