CodeGym /행동 /JAVA 25 SELF /IOException 및 FileNotFoundException: 오류 처리

IOException 및 FileNotFoundException: 오류 처리

JAVA 25 SELF
레벨 38 , 레슨 0
사용 가능

1. 입출력 예외의 계층 구조

Java에서 파일 및 다른 외부 리소스를 다룰 때는 거의 항상 이른바 입출력 예외를 마주합니다. 이는 읽기나 쓰기 도중 문제가 생기면 던져지는(throw) 객체입니다. 예를 들어 파일을 찾을 수 없거나, 접근 권한이 없거나, 디스크가 갑자기 “지쳤다” 같은 상황입니다.

이번 강의의 주인공

Java에는 이러한 예외의 전체 계층 구조가 있습니다. 그중 핵심은 다음과 같습니다:

  • IOException — 모든 입출력 오류의 기본 클래스. 파일, 스트림 또는 네트워크에서 문제가 생기면 거의 항상 이것(혹은 그 수많은 하위 클래스)이 “범인”입니다.
  • FileNotFoundExceptionIOException의 “자식”으로, 존재하지 않는 파일을 열려고 하거나 경로가 잘못되었을 때 나타납니다.

다른 파생 클래스:

  • EOFException — 역시 IOException의 직접적인 “자식”. 읽는 도중 예상치 못하게 파일 끝(EOF)에 도달했음을 알립니다.
  • MalformedInputException — “손자”: CharacterCodingException을 상속하고, 그 클래스는 다시 IOException을 상속합니다. 파일을 지정한 인코딩으로 올바르게 해석할 수 없을 때 발생합니다(예: "UTF-8"을 기대했지만 손상된 시퀀스가 온 경우).
  • 또한 SocketException, ZipException 등 각자의 책임 범위를 가진 특화된 “친척”들이 있습니다. 계층이 깊어질수록 상황은 더 좁고 특수해집니다.

간단히 요약한 도식:

java.lang.Exception
└── java.io.IOException
    ├── java.io.FileNotFoundException
    ├── java.io.EOFException
    ├── java.nio.charset.MalformedInputException
    └── ... (기타)

흥미로운 사실:
Java에서 파일과 관련된 거의 모든 연산은 IOException을 선언하거나 처리해야 합니다 — 이른바 체크 예외(checked exceptions)입니다. 컴파일러가 오류 처리를 잊지 못하게 합니다!

2. 이 예외들은 언제, 왜 발생할까요

존재하지 않는 파일 열기

가장 흔한 경우는 파일을 열려고 했는데 파일이 없는 상황입니다. 마치 정류장에 갔는데 그 버스가 시간표에조차 없는 것과 같습니다.

FileInputStream fis = new FileInputStream("abracadabra.txt"); // 펑! FileNotFoundException

권한 없이 파일에 쓰기 시도

권한이 없는 폴더에 파일을 쓰려고 하면 상황에 따라 FileNotFoundException 또는 IOException이 발생합니다.

FileOutputStream fos = new FileOutputStream("/system/secret.txt"); // 펑! FileNotFoundException 또는 IOException

매체 손상 등으로 인한 읽기/쓰기 오류

때로는 파일이 있어도 디스크가 손상되었거나, 다른 프로그램이 파일을 점유하고 있거나, 전원이 갑자기 꺼지는 등의 상황에서는 IOException이 다양한 메시지와 함께 발생합니다.

그 밖의 원인

  • 파일이 읽기 전용으로 열려 있는데 쓰기를 시도함.
  • 파일 경로가 너무 길거나 허용되지 않는 문자가 포함됨.
  • 파일이 다른 프로세스에서 사용 중.
  • 디스크가 가득 참.
  • 확인과 사용 사이에 다른 프로세스가 파일을 삭제함.

3. try-catch로 처리하기

파일, 스트림, 네트워크를 다룰 때마다 try-catch를 사용하세요. 에어백처럼, 무언가 잘못되어도 프로그램이 바로 종료되지 않고 올바르게 대응할 수 있습니다.

예외는 어떻게 올바르게 잡을까요?

Java에서는 구체적인 예외를 일반적인 예외보다 먼저 잡아야 합니다. 첫 번째 catch를 (IOException e)처럼 넓게 두면, 보다 좁은 FileNotFoundException 등은 도달하지 못해 동작하지 않습니다.

올바른 구조:

try {
    // 파일 작업
} catch (FileNotFoundException e) {
    // "파일을 찾을 수 없음" 상황 처리
} catch (IOException e) {
    // 기타 입출력 오류 처리
}

이유는?
FileNotFoundExceptionIOException의 특수한 경우이기 때문입니다. 일반적인 경우를 먼저 잡아버리면, 특수한 경우는 자신의 catch까지 “도달”하지 못합니다.

코드 예시: 파일 열기 오류 처리

import java.io.*;

public class FileReaderExample {
    public static void main(String[] args) {
        String filename = "notes.txt";
        try {
            BufferedReader reader = new BufferedReader(new FileReader(filename));
            String line = reader.readLine();
            System.out.println("파일의 첫 번째 줄: " + line);
            reader.close();
        } catch (FileNotFoundException e) {
            System.out.println("파일을 찾을 수 없음: " + filename);
        } catch (IOException e) {
            System.out.println("파일을 읽는 중 오류: " + e.getMessage());
        }
    }
}

주의:
파일이 존재하는지 미리 확인했더라도 항상 try-catch를 남겨두세요 — 파일은 언제든 사라질 수 있습니다(예: 다른 프로세스가 삭제).

4. 실습: 오류 처리를 포함해 코드 작성하기

간단한 예제를 만들고, 파일 부재와 같은 오류에 올바르게 반응하도록 해보겠습니다.

1단계: 존재하지 않는 파일을 열어보기

import java.io.*;

public class NotesApp {
    public static void main(String[] args) {
        String filename = "my_notes.txt";
        try {
            BufferedReader reader = new BufferedReader(new FileReader(filename));
            String line;
            System.out.println("메모:");
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
            reader.close();
        } catch (FileNotFoundException e) {
            System.out.println("이런! 메모 파일을 찾을 수 없습니다: " + filename);
            System.out.println("팁: 파일을 만들거나 이름을 확인하세요.");
        } catch (IOException e) {
            System.out.println("파일을 읽는 중 오류가 발생했습니다: " + e.getMessage());
        }
    }
}

2단계: 파일 쓰기 처리를 추가

import java.io.*;

public class NotesWriter {
    public static void main(String[] args) {
        String filename = "my_notes.txt";
        try {
            BufferedWriter writer = new BufferedWriter(new FileWriter(filename, true)); // append: true
            writer.write("새 메모!\n");
            writer.close();
            System.out.println("메모가 성공적으로 추가되었습니다!");
        } catch (IOException e) {
            System.out.println("파일에 쓰는 중 오류: " + e.getMessage());
        }
    }
}

3단계: 오류 로깅을 포함한 범용 예시

import java.io.*;

public class SafeFileCopier {
    public static void main(String[] args) {
        String source = "source.txt";
        String target = "target.txt";
        try {
            BufferedReader reader = new BufferedReader(new FileReader(source));
            BufferedWriter writer = new BufferedWriter(new FileWriter(target));
            String line;
            while ((line = reader.readLine()) != null) {
                writer.write(line);
                writer.newLine();
            }
            reader.close();
            writer.close();
            System.out.println("파일이 성공적으로 복사되었습니다!");
        } catch (FileNotFoundException e) {
            System.out.println("파일을 찾을 수 없음: " + e.getMessage());
        } catch (IOException e) {
            System.out.println("입출력 오류: " + e.getMessage());
        }
    }
}

팁:
실제 애플리케이션에서는 오류를 화면에만 출력하지 말고 파일이나 로그에도 기록해 두면, 나중에 무엇이 잘못되었는지 분석하는 데 도움이 됩니다.

5. 표: 주요 IO 예외

예외 언제 발생하나요? 어떻게 처리하나요?
FileNotFoundException
파일이 없거나 경로가 존재하지 않거나 권한이 없음 사용자에게 알리고 경로/권한을 확인하며 필요 시 파일 생성
EOFException
읽는 중 예상치 못한 파일 끝(EOF) 손상/불완전함을 알리고 데이터를 부분 복구 시도
IOException
일반적인 입출력 오류(디스크, 권한, 잠금) 세부 정보를 확인하고 작업을 정상 종료하며 가능하면 재시도
MalformedInputException
잘못된 인코딩 또는 파일 구조 손상을 알리고 다른 인코딩/소스를 시도

6. IO 예외 처리 시 흔한 실수

오류 №1: 일반 Exception만 잡기. 단순히 catch (Exception e)만 쓰고 싶어질 수 있지만, 그러면 무엇이 정확히 잘못됐는지 구분할 수 없습니다. 먼저 구체적인 예외(FileNotFoundException)를 잡고, 그다음 — 일반적인 IOException을 잡는 편이 좋습니다.

오류 №2: 오류 발생 시 스트림을 닫지 않음. 파일을 열었는데 예외가 발생하면 스트림이 닫히지 않은 채 남을 수 있습니다. try-with-resources를 사용하거나 finally에서 자원을 닫으세요.

오류 №3: 예외 메시지를 무시함. 단순히 “오류!”만 출력하지 말고 상세 정보를 보여 주세요: e.getMessage(). 이는 무엇이 잘못되었는지 더 빨리 이해하는 데 도움이 됩니다.

오류 №4: 쓰기 시 FileNotFoundException을 처리하지 않음. 많은 사람이 FileNotFoundException이 읽기에서만 발생한다고 생각합니다. 실제로는 잘못된 경로, 파일 생성 권한 부족 등으로 쓰기 중에도 발생할 수 있습니다.

오류 №5: 권한을 확인하지 않음. 프로그램이 제한된 권한으로 실행되면 많은 파일 작업이 실패할 수 있습니다. 항상 이를 고려하고 사용자에게 안내하세요.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION