Java 7 이상은 java.nio.file.Files 를 쓴다. 스트림을 직접 돌리는 옛 예제는 이제 쓸 이유가 없다.
import java.io.IOException;
import java.nio.file.*;
public class CopyExample {
public static void main(String[] args) throws IOException {
Path src = Path.of("/data/in/report.csv");
Path dst = Path.of("/data/out/report.csv");
Files.createDirectories(dst.getParent());
Files.copy(src, dst,
StandardCopyOption.REPLACE_EXISTING,
StandardCopyOption.COPY_ATTRIBUTES);
}
}
옵션을 주지 않으면 대상이 이미 있을 때 FileAlreadyExistsException 이 난다. REPLACE_EXISTING 은 대상이 비어 있지 않은 디렉터리면 여전히 실패한다.
디렉터리 전체를 복사하는 기능은 표준 라이브러리에 없다. 직접 순회한다.
try (var stream = Files.walk(srcDir)) {
stream.forEach(p -> {
try {
Path target = dstDir.resolve(srcDir.relativize(p));
if (Files.isDirectory(p)) {
Files.createDirectories(target);
} else {
Files.copy(p, target, StandardCopyOption.REPLACE_EXISTING);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
Files.copy 는 원자적이지 않다. 복사 중 실패하면 반쪽짜리 파일이 남는다. 중요한 파일은 임시 이름으로 복사한 뒤 같은 파일 시스템 안에서 이름을 바꾼다.
Path tmp = dst.resolveSibling(dst.getFileName() + ".tmp");
Files.copy(src, tmp, StandardCopyOption.REPLACE_EXISTING);
Files.move(tmp, dst, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
ATOMIC_MOVE 는 같은 파일 시스템 안에서만 보장된다. 다른 마운트로 옮기면 AtomicMoveNotSupportedException 이 난다.
Throwable 아래에 Exception 과 Error 가 나란히 있다. Error 는 애플리케이션이 복구할 것으로 기대하지 않는 상황을 나타낸다. OutOfMemoryError · StackOverflowError · NoClassDefFoundError 가 여기 속한다.
catch (Exception e) 로는 잡히지 않고, catch (Throwable t) 로 잡을 수는 있지만 대개 잡으면 안 된다. 상태가 이미 망가진 뒤이므로 계속 실행하면 더 알기 어려운 오류로 이어진다.
java.lang.Error: Unresolved compilation problems:
The method foo() is undefined for the type Bar
이 예외는 Eclipse 가 컴파일 오류가 있는 상태로 만들어 낸 클래스 파일을 실행했을 때 난다. Eclipse 의 증분 컴파일러는 오류가 있는 메서드 자리에 이 예외를 던지는 코드를 넣고 .class 를 생성한다. 그래서 IDE 에서는 실행이 되고, 그 자리에 도달하는 순간 터진다.
원인은 컴파일 오류 그 자체다. 예외 메시지에 실제 컴파일 오류 내용이 들어 있으므로 그것을 고친다. javac 로 빌드했다면 애초에 클래스 파일이 만들어지지 않으므로 이 예외를 볼 일이 없다.
확인 순서는 이렇다. Eclipse 의 Problems 뷰에서 오류를 모두 없애고, Project > Clean 으로 다시 빌드한다. 그래도 남으면 옛 .class 가 배포본에 섞여 있는 것이므로 빌드 산출물 디렉터리를 비우고 다시 만든다. CI 에서는 IDE 컴파일러가 아니라 javac 또는 Maven·Gradle 로 빌드해 이 상태가 산출물에 들어가지 않게 한다.