빠른 흐름
Path path = Path.of("data", "users.txt");
if (Files.exists(path)) {
List<String> lines = Files.readAllLines(path);
}기본 흐름
어떤 파일 시스템 API를 먼저 떠올리면 되나
| 상황 | 먼저 떠올릴 선택 |
|---|---|
| 경로를 값 객체처럼 다루기 | Path |
| 파일 읽기/쓰기/이동 | Files |
| 디렉터리 생성 | Files.createDirectories(...) |
| 디렉터리 순회 | Files.list(...) 또는 DirectoryStream |
| 문자열 경로를 안전하게 조합 | Path.of(...) / resolve(...) |
Path: 경로를 값 객체로 다루기
예전 java.io.File과 달리, java.nio.file.Path는 경로를 값처럼 다루는 객체입니다. OS별 구분자를 신경 쓰지 않아도 되고, resolve로 경로를 조합하고, getParent, getFileName, normalize로 경로 구성요소를 다룰 수 있습니다.
Path base = Path.of("/app/data");
Path file = base.resolve("users.txt"); // /app/data/users.txt
Path relative = base.relativize(file); // users.txt
Path parent = file.getParent(); // /app/data
Path name = file.getFileName(); // users.txt
// 경로 조합 — 문자열 덧셈 없이
Path config = Path.of("config", "app", "settings.json");Files: 경로에 대해 실제 작업 수행
Files는 경로를 받아 실제 파일 I/O 작업을 수행하는 유틸리티 클래스입니다. 읽기, 쓰기, 복사, 이동, 삭제, 디렉터리 생성, 존재 확인 등을 제공합니다. Path가 "어디"를 표현하고, Files가 "무엇을 할지"를 실행하는 구조입니다.
Path path = Path.of("output", "report.txt");
// 파일 읽기
String content = Files.readString(path);
List<String> lines = Files.readAllLines(path);
// 파일 쓰기
Files.writeString(path, "결과 데이터\n", StandardOpenOption.APPEND);
// 복사 / 이동
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
Files.move(oldPath, newPath);
// 파일 삭제
Files.deleteIfExists(path);디렉터리 생성 및 탐색
디렉터리를 만들거나 디렉터리 안의 파일을 탐색할 때도 Files를 씁니다. createDirectories는 중간 경로까지 한 번에 생성해 줍니다.
// 디렉터리 생성 (중간 경로 포함)
Files.createDirectories(Path.of("output", "2026", "march"));
// 디렉터리 내 파일 목록
try (Stream<Path> entries = Files.list(Path.of("logs"))) {
entries.filter(p -> p.toString().endsWith(".log"))
.forEach(System.out::println);
}
// 재귀 탐색
try (Stream<Path> all = Files.walk(Path.of("src"))) {
all.filter(Files::isRegularFile)
.forEach(System.out::println);
}Path vs Files
Path는 경로 자체를 표현하고, Files는 그 경로에 대해 실제 작업을 수행합니다. 문자열을 계속 넘겨 다니기보다 이 역할을 분리해서 보면 코드가 훨씬 덜 꼬입니다.
Path report = Path.of("output").resolve("report.txt");
// 경로 계산은 Path
Path parent = report.getParent();
// 실제 읽기/쓰기/존재 확인은 Files
if (Files.notExists(parent)) {
Files.createDirectories(parent);
}
Files.writeString(report, "done");선택 기준
| 상황 | 적합한 선택 |
|---|---|
| 경로 생성 및 조합 | Path.of(...), path.resolve(...) |
| 파일 읽기/쓰기 | Files.readString, Files.writeString |
| 파일 존재 확인 | Files.exists(path) |
| 디렉터리 생성 | Files.createDirectories(path) |
| 디렉터리 탐색 | Files.list(dir), Files.walk(dir) |
주의할 점
경로를 문자열 덧셈으로 직접 만들면 OS별 구분자 문제와 상대 경로 오류가 발생합니다. 경로는 항상 Path로 조합하세요.
// ❌ 문자열 덧셈으로 경로 조합 — OS마다 구분자(\, /) 다름
String filePath = baseDir + "/" + subDir + "/" + fileName;
File file = new File(filePath); // Windows에서 오류 가능
// ✅ Path.resolve()로 OS 독립적인 경로 조합
Path filePath = Path.of(baseDir)
.resolve(subDir)
.resolve(fileName);Files.list(...)와 Files.walk(...)가 반환하는 Stream은 닫아야 합니다. try-with-resources 없이 열어 두면 파일 핸들이 남을 수 있습니다.
// ❌ 닫지 않음
Stream<Path> paths = Files.list(Path.of("logs"));
// ✅ try-with-resources
try (Stream<Path> paths = Files.list(Path.of("logs"))) {
paths.forEach(System.out::println);
}참고 링크
3 sources