예외 처리의 이해와 활용
프로그램 실행 중 발생할 수 있는 오류 상황을 효과적으로 관리하기 위해 자바는 예외(Exception) 기반의 에러 처리 메커니즘을 제공한다. 이를 적절히 다루지 않으면 애플리케이션이 비정상 종료되며 사용자 경험을 크게 해칠 수 있다.
예외의 종류와 발생 원리
자바에서는 두 가지 주요 유형의 예외를 구분한다:
- 컴파일 시 예외 (Checked Exception): 코드 작성 단계에서 강제로 처리를 요구하는 예외. 대표적으로
ParseException,IOException등이 있다. - 런타임 예외 (Unchecked Exception): 프로그램 실행 도중 발생하며, 반드시 처리하지 않아도 되는 예외. 예:
ArrayIndexOutOfBoundsException,NullPointerException.
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
Date date = sdf.parse("2028-13-45 25:70:99"); // 형식 불일치 → ParseException
System.out.println(date);
} catch (ParseException e) {
System.err.println("날짜 형식이 올바르지 않습니다.");
}
사용자 정의 예외 클래스 설계
표준 라이브러리에 포함되지 않은 특정 비즈니스 로직 오류를 표현하고자 할 때는 직접 예외 클래스를 정의할 수 있다. 이때 다음 규칙을 따른다:
Exception클래스를 상속하면 컴파일 시 예외가 된다.RuntimeException을 상속하면 런타임 예외가 된다.
public class InvalidAgeException extends RuntimeException {
public InvalidAgeException(String message) {
super(message);
}
}
public class Person {
private int age;
public void setAge(int inputAge) {
if (inputAge < 0 || inputAge > 150) {
throw new InvalidAgeException("유효하지 않은 나이: " + inputAge);
}
this.age = inputAge;
}
}
예외 처리 전략
예외는 호출 스택을 따라 위로 전달되며, 최종적으로 JVM이 잡게 되면 프로그램이 종료된다. 따라서 가장 바람직한 접근은 핵심 사용자 인터페이스 근처에서 예외를 포착해 의미 있는 피드백을 제공하는 것이다.
public static double readValidPrice() {
Scanner scanner = new Scanner(System.in);
while (true) {
try {
System.out.print("가격을 입력하세요: ");
double price = scanner.nextDouble();
if (price >= 0) return price;
else System.out.println("가격은 음수가 될 수 없습니다.");
} catch (InputMismatchException e) {
System.out.println("숫자만 입력 가능합니다.");
scanner.next(); // 잘못된 입력 소비
}
}
}
컬렉션 프레임워크 개요
자바는 다양한 데이터 구조를 제공하는 java.util.Collection 프레임워크를 통해 동적 배열, 집합, 리스트 등을 지원한다. 이들은 다음과 같이 분류된다:
- 단일 요소 컬렉션 (Single Collection):
List,Set - 쌍형 컬렉션 (Map-based):
HashMap,TreeMap
Collection 인터페이스의 공통 메서드
모든 컬렉션 구현체는 아래 메서드들을 공유한다:
Collection<String> items = new ArrayList<>();
items.add("item1");
items.add("item2");
System.out.println(items.size()); // 2
System.out.println(items.contains("item1")); // true
Object[] array = items.toArray();
Collection<String> another = new HashSet<>(Arrays.asList("item3", "item4"));
items.addAll(another); // 병합
items.clear(); // 전체 제거
컬렉션 반복 접근 방식
다양한 타입의 컬렉션을 일관되게 순회하기 위한 여러 방법이 존재한다.
Iterator를 이용한 반복
모든 컬렉션에서 사용 가능한 표준 방식으로, 내부 상태를 유지하며 요소를 하나씩 접근한다.
Iterator<String> iterator = items.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
System.out.println(item);
}
향상된 for 문 (Enhanced For Loop)
간결한 문법으로 컬렉션과 배열 모두에 적용 가능하다.
for (String item : items) {
System.out.println(item);
}
// 배열에도 동일하게 적용 가능
int[] numbers = {1, 2, 3};
for (int num : numbers) {
System.out.println(num);
}
Lambda 기반 forEach
JDK 8부터 도입된 함수형 스타일의 반복 처리. 가독성이 뛰어나고 간결하다.
items.forEach(System.out::println);
// 또는 람다 표현식
items.forEach(item -> System.out.println("항목: " + item));
List 계열 컬렉션 심층 분석
List는 요소의 순서를 보장하고 중복 허용, 인덱스 기반 접근이 가능한 특징을 갖는다.
List 전용 메서드 예시
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add(1, "Charlie"); // 인덱스 1 위치에 삽입
String removed = names.remove(2); // 인덱스 2 삭제
String first = names.get(0); // 인덱스 0 조회
String replaced = names.set(1, "David"); // 값 수정
ArrayList 내부 작동 원리
ArrayList는 내부적으로 배열을 사용하며, 용량 초과 시 자동으로 더 큰 배열을 생성하고 기존 데이터를 복사한다. 이 과정은 O(n) 시간이 소요되지만 평균적으로 상수 시간 추가 성능을 제공한다.
LinkedList의 구조와 활용
LinkedList는 양방향 연결 리스트로 구현되어 앞뒤 노드에 대한 참조를 모두 가진다. 이 덕분에 양 끝단에서의 삽입/삭제가 매우 효율적이다.
// 큐(Queue) 구현: FIFO (선입선출)
LinkedList<String> queue = new LinkedList<>();
queue.offer("첫 번째");
queue.offer("두 번째");
System.out.println(queue.poll()); // 첫 번째 출력
// 스택(Stack) 구현: LIFO (후입선출)
LinkedList<String> stack = new LinkedList<>();
stack.push("위쪽");
stack.push("아래쪽");
System.out.println(stack.pop()); // 위쪽 출력
실제 객체를 포함한 컬렉션 처리
사용자 정의 객체도 컬렉션에 저장하고 반복 처리할 수 있다.
class Movie {
private String title;
private double rating;
private String cast;
public Movie(String title, double rating, String cast) {
this.title = title;
this.rating = rating;
this.cast = cast;
}
// getter 메서드 생략
public String getTitle() { return title; }
public double getRating() { return rating; }
public String getCast() { return cast; }
}
List<Movie> movies = new ArrayList<>();
movies.add(new Movie("기생충", 8.6, "송강호"));
movies.add(new Movie("인터스텔라", 9.0, "매튜 맥커너히"));
movies.forEach(m -> {
System.out.println("제목: " + m.getTitle());
System.out.println("평점: " + m.getRating());
System.out.println("출연: " + m.getCast());
});