Java 입출력과 예외 처리: 콘솔부터 UTF-8 파일까지

Alchemist·3일 전

KT AIVLE

목록 보기
102/130

자바 프로그램이 조금만 커져도 외부 데이터와 상호작용해야 한다. 사용자가 입력한 숫자를 읽고, 파일에 결과를 저장하고, 잘못된 입력이 들어왔을 때 안전하게 실패해야 한다. 이 과정을 이해하려면 입출력과 예외 처리를 함께 보는 것이 좋다.

패키지와 접근 제어자는 왜 필요할까?

패키지는 관련 클래스를 묶는 이름 공간이다. 파일 상단의 package 선언은 클래스가 속한 패키지를 정한다.

package com.example.orders;

public class OrderService {
    public String getStatus() {
        return "READY";
    }
}

위 파일은 일반적인 소스 폴더 구조에서 com/example/orders/OrderService.java에 위치한다. 다른 패키지에서 사용하려면 import com.example.orders.OrderService;처럼 불러올 수 있다.

접근 제어자는 클래스의 내부 구현을 보호하는 장치다.

접근 범위publicprotected기본(package-private)private
같은 클래스가능가능가능가능
같은 패키지의 다른 클래스가능가능가능불가
다른 패키지의 하위 클래스가능조건부 가능불가불가
무관한 다른 패키지가능불가불가불가

protected의 다른 패키지 접근에는 상속을 통한 접근이라는 조건이 있다. 단순히 하위 클래스라는 이유로 모든 다른 인스턴스의 멤버에 접근할 수 있는 것은 아니다.

콘솔에서 입력받기

간단한 사용자 입력은 Scanner로 받을 수 있다.

// ConsoleExample.java
import java.util.Scanner;

public class ConsoleExample {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("주문 수량을 입력하세요: ");
        String raw = scanner.nextLine();

        try {
            int quantity = Integer.parseInt(raw.trim());
            if (quantity <= 0) {
                throw new IllegalArgumentException("수량은 1 이상이어야 합니다.");
            }
            System.out.println("선택한 수량: " + quantity);
        } catch (NumberFormatException e) {
            System.out.println("정수를 입력해야 합니다.");
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage());
        }
    }
}

NumberFormatException은 IllegalArgumentException의 하위 타입이므로 더 구체적인 예외를 먼저 처리해야 컴파일 오류가 나지 않는다. 입력을 문자열로 받은 뒤 변환하면 숫자가 아닌 입력을 검사하기 편하다.

파일 입출력에는 표준 문자 인코딩을 명시하자

텍스트 파일을 다룰 때는 Path, Files, StandardCharsets가 편리하다.

// FileExample.java
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;

public class FileExample {
    public static void main(String[] args) throws IOException {
        Path file = Path.of("orders-demo.txt");

        Files.write(file, List.of("order-11,PAID", "order-12,CANCELLED"),
                StandardCharsets.UTF_8);

        Files.writeString(file, "order-13,PAID\n", StandardCharsets.UTF_8,
                StandardOpenOption.APPEND);

        List<String> lines = Files.readAllLines(file, StandardCharsets.UTF_8);
        for (String line : lines) {
            System.out.println(line);
        }
    }
}

실행하면 작업 디렉터리에 orders-demo.txt가 생성되고 세 줄이 출력된다. Files.write(...)는 기본적으로 기존 파일을 덮어쓸 수 있으므로 실제 사용자 파일에 실행하지 않도록 주의하자.

Path.of(...)는 OS별 경로 구분자 처리에 도움이 된다. 한글 데이터가 포함된 파일을 다른 환경과 공유할 예정이라면 UTF-8을 명시하는 습관이 좋다.

스트림을 닫는 try-with-resources

BufferedReader, BufferedWriter, InputStream 등 일부 자원은 사용 후 닫아야 한다. try-with-resources를 쓰면 정상 종료와 예외 상황 모두에서 자원을 정리하기 쉽다.

// ReaderExample.java
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public class ReaderExample {
    public static void main(String[] args) throws IOException {
        Path path = Path.of("orders-demo.txt");

        try (BufferedReader reader = Files.newBufferedReader(path,
                StandardCharsets.UTF_8)) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }
    }
}

이 코드는 앞의 예제에서 파일을 생성한 뒤 실행해야 한다. 작은 파일은 readAllLines도 편리하지만 큰 파일은 한 줄씩 스트리밍 방식으로 처리하는 편이 메모리 부담을 줄인다.

checked와 unchecked 예외

  • Checked exception: 예를 들어 IOException. 호출하는 코드가 catch로 처리하거나 throws로 선언해야 한다.
  • Unchecked exception: 예를 들어 IllegalArgumentException, NullPointerException. 컴파일러가 처리 여부를 강제하지 않는다.

throw는 예외 객체를 실제로 발생시키고, throws는 메서드 선언에 예외가 전파될 가능성을 표시한다.

static int requirePositive(int value) {
    if (value <= 0) {
        throw new IllegalArgumentException("양수를 입력하세요.");
    }
    return value;
}

예외를 무조건 빈 catch로 숨기면 문제를 찾아내기 어려워진다. 복구할 수 있는 예외는 처리하고, 복구할 수 없다면 문맥을 추가해 전달하는 쪽이 낫다.

실무에서 주의할 부분

  1. 파일이 존재하지 않거나 권한이 없으면 IOException 계열 예외가 발생할 수 있다.
  2. 문자 인코딩이 다르면 한글이 깨질 수 있다.
  3. 사용자 입력을 검증 없이 파일 경로로 쓰면 경로 탐색 취약점이 생길 수 있다.
  4. 큰 파일 전체를 한 번에 읽으면 메모리를 과도하게 사용할 수 있다.
  5. 읽은 데이터를 split(",")만으로 CSV로 해석하면 따옴표 안의 쉼표를 제대로 처리하지 못한다. 실제 CSV 데이터는 검증된 파서를 고려하자.

핵심 정리

  • 패키지는 관련 클래스를 조직하고 이름 충돌을 줄여 준다.
  • 접근 제어자는 외부에 공개할 API 범위를 관리한다.
  • 콘솔 입력은 문자열로 받은 뒤 유효성 검사·변환하는 방식이 편리하다.
  • 텍스트 파일을 읽고 쓸 때는 Path·Files와 UTF-8을 활용할 수 있다.
  • 자원을 사용하는 API는 try-with-resources를 우선 검토한다.
  • checked 예외는 처리하거나 throws로 선언해야 한다.
  • 예외를 숨기기보다 원인과 복구 가능성을 기준으로 처리한다.
profile
html_programming_language

0개의 댓글