기본 콘텐츠로 건너뛰기

방송대 방통대 Java프로그래밍 7강 - 패키지와 예외처리 - 요약 노트 시험족보 예상문제 - 올에이클래스

Java프로그래밍 7강 - 패키지와 예외처리

Java프로그래밍 7강 - 패키지와 예외처리

규모가 커진 Java 프로그램은 패키지로 클래스를 분류하고, 예외처리로 비정상 상황에서도 실행 흐름을 안전하게 관리한다. 이 글에서는 시스템·사용자 정의 패키지와 import, 접근 수준과 CLASSPATH부터 예외 계층, try-catch-finally, try-with-resources, throws와 사용자 정의 예외까지 7강의 내용을 체계적으로 정리한다.

1. 패키지의 개념과 시스템 패키지

1.1 패키지란

패키지는 서로 관련 있는 클래스와 인터페이스의 묶음이다. 클래스와 인터페이스는 패키지의 멤버로 존재하고, 컴파일된 .class 파일은 패키지 이름에 대응하는 폴더에 저장된다. 패키지 자체도 폴더와 비슷한 단위로 계층적으로 분류되므로 큰 프로그램의 구성 요소를 구조적으로 관리할 수 있다.

패키지의 용도설명
클래스 탐색관련 클래스를 기능별로 묶어 필요한 클래스를 쉽게 찾고 사용할 수 있다.
이름 충돌 방지graphics.Rectanglejava.awt.Rectangle처럼 같은 단순 이름의 클래스를 구별한다.
접근 범위 제어패키지 접근 수준을 이용해 같은 패키지 안에서만 사용할 구성 요소를 만들 수 있다.
계층적 분류점으로 구분한 이름과 디렉터리 구조를 대응시켜 관련 코드를 체계화한다.

1.2 JDK가 제공하는 시스템 패키지

시스템 패키지는 JDK가 제공하는 클래스 라이브러리다. JDK와 함께 설치되며, 클래스 파일은 기능에 따라 여러 패키지로 묶여 있다. 과거에는 rt.jar와 같은 JAR 파일로 압축되어 있었으나 최근 버전에서는 모듈 단위로 분산 저장된다.

가장 기본이 되는 최상위 시스템 패키지는 java이고 대다수 시스템 패키지 이름은 java.로 시작한다. 상위와 하위 패키지는 점으로 구분하므로 java.lang, java.io, java.nio.file, java.util, java.util.stream처럼 표현한다.

완전한 클래스 이름: 패키지 경로를 포함해 java.io.IOException처럼 표현한 이름이다. 같은 단순 클래스 이름이 여러 패키지에 있어도 완전한 이름은 서로 다르다.

Java의 가장 기본적인 클래스는 java.lang 패키지에 들어 있다. 이 패키지는 프로그램에서 자동으로 import되므로 String, System 등을 패키지 이름 없이 사용할 수 있다.

2. 사용자 정의 패키지와 클래스 사용

2.1 package 선언과 저장 위치

사용자 패키지를 정의하려면 소스 파일의 앞부분에 package 패키지이름;을 작성한다. 다음 클래스의 완전한 이름은 com.vehicle.Car가 된다.

package com.vehicle;

public class Car {
  String szType = "승용차";
}

컴파일 결과는 Car.class이며 패키지 경로와 일치하는 com/vehicle 폴더에 저장되어야 한다. 명령행에서 javac Car.java -d D:\javaClasses처럼 -d 옵션으로 클래스 파일의 기준 출력 폴더를 지정하면 그 아래에 패키지 디렉터리가 만들어진다. 이클립스에서는 패키지를 만들면 해당 폴더가 자동 생성되고, 클래스를 만들 때 클래스 이름과 패키지 이름을 함께 지정할 수도 있다.

패키지와 폴더의 대응: 패키지 이름이 com.vehicle라면 기준 폴더 아래의 저장 경로는 com/vehicle/Car.class가 된다. 점은 패키지 계층을, 폴더 구분자는 실제 저장 구조를 나타낸다.

2.2 완전한 이름과 import문

다른 패키지의 public 클래스를 사용할 때는 원칙적으로 패키지 경로를 포함한 완전한 이름을 사용한다. 같은 클래스를 여러 번 쓴다면 import문을 이용해 패키지 경로를 생략할 수 있다.

// 완전한 이름 사용
java.util.Scanner s1 = new java.util.Scanner(System.in);

// import 후 단순 이름 사용
import java.util.Scanner;

public class Main {
  public static void main(String args[]) {
    Scanner s2 = new Scanner(System.in);
  }
}

한 클래스만 가져올 때는 import 패키지이름.클래스이름;, 한 패키지의 클래스들을 가져올 때는 import 패키지이름.*; 형식을 사용한다. import문은 소스 코드 앞쪽에 놓으며 package 선언이 있다면 그 다음에 작성한다. import는 클래스 파일을 복사하는 기능이 아니라 소스에서 완전한 이름 대신 단순 이름을 사용할 수 있게 하는 선언이다.

package sample;

import java.util.Scanner;
import java.io.*;

public class Main {
  // 클래스 본문
}
자동 import: 모든 Java 프로그램에는 import java.lang.*;가 자동으로 포함된다. 반면 java.util, java.io 등은 필요에 따라 직접 import하거나 완전한 이름을 써야 한다.

2.3 패키지 접근 수준과 public

클래스 선언에 public이 없으면 패키지 접근 수준이 적용되어 같은 패키지에서만 사용할 수 있다. 다른 패키지에서 사용하는 클래스는 public이어야 하며, 단순히 import했다고 접근 제한이 사라지는 것은 아니다.

package com.vehicle;

class Car { }

public class Bus extends Car { }
import com.vehicle.*;

class MyCar extends Car { } // 오류: Car는 다른 패키지에서 접근 불가
class MyBus extends Bus { } // 가능: Bus는 public

public class PackageTest {
  public static void main(String args[]) {
    Bus bus = new Bus();
    Car car = new Car(); // 오류
  }
}

CarBus가 같은 com.vehicle 패키지에 있으므로 Bus는 Car를 상속할 수 있다. 그러나 다른 패키지의 MyCarPackageTest에서는 패키지 접근 수준인 Car를 사용할 수 없다.

3. 클래스 탐색과 CLASSPATH

3.1 컴파일러와 JVM이 클래스를 찾는 방법

컴파일하거나 실행하려면 필요한 클래스의 위치를 알아야 한다. 예를 들어 클래스 A를 사용한다면 컴파일러와 JVM은 A.class가 있는 디렉터리 또는 A.class를 담은 JAR 파일을 찾을 수 있어야 한다. 기본 패키지와 확장 패키지뿐 아니라 사용자가 만든 클래스도 검색 대상이 된다.

3.2 환경변수 CLASSPATH

컴파일러는 환경변수 CLASSPATH에 지정된 경로에서 사용자 클래스를 찾을 수 있다. 경로 목록에는 폴더뿐 아니라 JAR 파일도 포함할 수 있다.

예: 프로그램이 graphics.Circle을 사용하고 CLASSPATH가 특정 경로와 a.jar를 포함한다면, 해당 경로 아래의 graphics/Circle.class 또는 JAR 내부의 같은 패키지 경로에 클래스 파일이 있어야 한다.

이클립스에서는 프로젝트의 Properties → Java Build Path → Libraries → Classpath에서 필요한 클래스 경로나 라이브러리를 추가할 수 있다. 핵심은 완전한 클래스 이름의 패키지 구조와 실제 검색 경로가 서로 맞아야 한다는 점이다.

4. 예외와 예외 클래스의 계층

4.1 에러와 예외

에러는 실행 중 발생하는 심각한 오류로 더 이상의 정상 실행이 어려운 상황을 뜻한다. 예외는 정상 실행 흐름을 벗어난 예외적 사건이지만 적절히 처리하면 복구하거나 프로그램을 계속 수행할 수 있는 경미한 오류다.

예외 상황이 발생하면 예외 정보를 담은 객체가 만들어져 던져진다. 적합한 예외처리 코드가 이 객체를 잡아 처리하면 프로그램은 이후 흐름을 계속할 수 있다. 처리 코드가 없다면 오류 메시지와 함께 실행이 비정상적으로 종료될 수 있다.

구분특징대응
Error실행 중 발생한 심각한 오류로 정상적인 복구가 어렵다.일반 애플리케이션의 예외처리 대상과 구분한다.
Exception정상 흐름에서 벗어난 사건이며 상황에 따라 복구할 수 있다.예외 객체를 catch하여 적절한 처리 코드를 수행한다.

4.2 Throwable, checked와 unchecked 예외

Exception 클래스는 Throwable의 자식 클래스이며 toString(), printStackTrace() 같은 메소드를 상속받는다. 계층의 큰 흐름은 Object → Throwable → Error 또는 Exception이다.

구분계층처리 규칙과 예
checked exceptionRuntimeException 계열을 제외한 Exception 하위 클래스발생 가능성이 있으면 명시적으로 처리하거나 throws로 전달해야 한다. 예: IOException
unchecked exceptionRuntimeException과 그 하위 클래스컴파일러가 처리를 강제하지 않는다. 예: ArithmeticException, NullPointerException, IndexOutOfBoundsException
시험 포인트: checked 예외는 처리 코드가 없으면 컴파일 오류가 발생할 수 있다. RuntimeException 계열은 처리하지 않아도 컴파일되지만, 정확하지 않은 코드나 잘못된 상태 때문에 실행 중 발생할 수 있다.

5. try-catch-finally를 이용한 직접 처리

5.1 직접 처리의 기본 구조

예외가 발생할 수 있는 위치에서 바로 예외 객체를 잡아 처리하는 방식을 직접 처리라고 한다. try-catch 또는 try-catch-finally 구문을 사용하면 일반 코드와 예외처리 코드를 분리할 수 있어 가독성이 좋아진다.

try {
  // 예외를 던질 수 있는 문장 또는 메소드 호출
} catch (ExceptionType1 ex1) {
  // ExceptionType1 처리
} catch (ExceptionType2 ex2) {
  // ExceptionType2 처리
} finally {
  // 필요할 때 작성하는 정리 코드
}

try 블록에서 예외가 발생하면 그 지점에서 try의 실행이 중단되고, 발생한 예외의 자료형과 일치하거나 그 상위 유형을 매개변수로 가진 catch 블록 하나가 실행된다. 예외가 발생하지 않으면 catch 블록은 실행되지 않는다. 여러 예외 유형을 나누어 처리하려면 catch 블록을 여러 개 작성한다.

상위 유형과 하위 유형의 catch가 모두 있다면 하위 유형을 처리하는 catch를 먼저 배치해야 한다. 상위 유형을 앞에 두면 모든 하위 예외까지 먼저 잡아 뒤의 세부 catch에 도달할 수 없기 때문이다.

5.2 finally 블록

finally는 필요에 따라 작성하며 예외 발생 여부와 관계없이 실행된다. 할당받은 리소스를 반환하기 위한 코드를 두는 데 적합하다. 전통적인 파일 처리에서는 try에서 연 파일을 finally에서 닫아 예외가 발생해도 반환되도록 구성한다.

흐름 정리: 예외가 발생하면 try의 남은 문장은 건너뛰고 일치하는 catch 하나를 실행한다. 이후 finally가 있으면 예외 발생 여부와 무관하게 실행한다.

5.3 여러 예외를 구분하는 예

public class Main {
  public static void main(String[] args) {
    try {
      int[] numbers = { 1, 2, 3 };
      System.out.println(numbers[4]);

      int result = 10 / 0;
      System.out.println("결과: " + result);
    } catch (ArrayIndexOutOfBoundsException e) {
      System.out.println("배열 인덱스가 잘못되었습니다.");
    } catch (ArithmeticException e) {
      System.out.println("0으로 나눌 수 없습니다.");
    }
    System.out.println("프로그램이 정상 종료됩니다.");
  }
}

첫 번째 배열 접근에서 ArrayIndexOutOfBoundsException이 발생하면 try 블록의 나머지 코드는 실행되지 않는다. 따라서 뒤의 10 / 0에는 도달하지 않고 첫 번째 catch가 실행된 뒤 try-catch 다음 문장으로 진행한다. 출력은 배열 인덱스 오류 메시지와 정상 종료 메시지다.

6. try-with-resources와 리소스 반환

6.1 자동 반환이 필요한 이유

파일, 네트워크 연결, 데이터베이스 연결 같은 외부 자원은 사용이 끝나면 close()로 반환해야 한다. 예외가 발생하더라도 반환되어야 하므로 과거에는 복잡한 finally 블록에서 null 여부를 확인하고 close 과정의 예외까지 다시 처리했다.

try-with-resources는 try의 괄호 안에서 리소스를 선언하고, 블록을 벗어날 때 자동으로 닫히게 하는 구문이다. 사용할 수 있는 리소스는 AutoCloseable 인터페이스를 구현한 클래스의 객체다.

try (리소스 생성 구문) {
  // 리소스를 사용하는 코드
} catch (ExceptionType ex) {
  // 예외 처리
}
import java.io.*;

public class Test {
  public static void main(String[] args) {
    try (BufferedReader br =
           new BufferedReader(new FileReader("file.txt"))) {
      String line;
      while ((line = br.readLine()) != null) {
        System.out.println(line);
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

블록이 정상 종료되든 예외로 끝나든 BufferedReader가 자동으로 닫히므로 finally에서 직접 br.close()를 호출할 필요가 없다. 코드가 짧아질 뿐 아니라 반환 누락 가능성도 줄어든다.

try-with-resources의 핵심: AutoCloseable 리소스를 try 괄호 안에서 생성하면 사용 후 자동 반환이 보장된다.

7. throws에 의한 간접 처리와 사용자 정의 예외

7.1 예외의 간접 처리와 전파

예외 발생 가능성이 있는 코드를 가진 메소드가 직접 try-catch로 처리하지 않고 호출한 쪽에 처리를 맡길 수 있다. 메소드 선언부의 매개변수 목록 뒤에 throws 예외유형을 적는 방식이며 이를 예외의 간접 처리 또는 전파라고 한다.

class CharInput {
  int nInput;

  public char getInput() throws IOException {
    nInput = System.in.read();
    return (char)nInput;
  }
}

getInput()System.in.read()가 던질 수 있는 IOException을 직접 잡지 않고 호출자에게 전파한다. checked 예외를 throws로 선언한 메소드를 부르는 쪽은 다시 throws로 전달하거나 try-catch로 처리해야 한다. FileInputStream 생성자의 FileNotFoundException, 입력 스트림의 read()가 던지는 IOException이 대표적인 예다.

import java.io.*;

public class Test {
  public static void main(String args[]) {
    CharInput charInput = new CharInput();
    try {
      System.out.println(charInput.getInput());
    } catch (IOException ex) {
      System.out.println(ex);
    }
  }
}
throw와 throws: throw는 예외 객체 하나를 실제로 던지는 문장이고, throws는 메소드가 특정 예외를 호출자에게 전파할 수 있음을 선언부에 표시한다.

7.2 사용자 정의 예외

프로그램의 업무 규칙에 맞는 예외가 필요하면 사용자가 직접 예외 클래스를 만들 수 있다. 일반적으로 Exception 클래스를 상속하고, 필요한 시점에 throw로 객체를 던진다.

class MyException extends Exception {
  public MyException() {
    super();
  }

  public String toString() {
    return "MyException";
  }
}

class MyExceptionTest {
  public void testFunc(int x) throws MyException {
    if (x > 10)
      throw new MyException();
  }
}

testFunc()는 x가 10보다 클 때 새 MyException 객체를 던진다. 이 클래스는 Exception을 직접 상속하므로 checked 예외에 해당하며, 메소드 선언에 throws를 표시하고 호출자가 처리하도록 구성한다.

8. 핵심 개념 정리

  • 패키지는 관련 클래스와 인터페이스를 계층적으로 묶어 탐색, 이름 충돌 방지, 접근 제어를 돕는다.
  • 사용자 패키지는 package로 선언하며 점으로 구분한 패키지 이름은 클래스 파일의 폴더 구조와 대응한다.
  • 다른 패키지의 public 클래스는 완전한 이름 또는 import문으로 사용한다. java.lang.*는 자동으로 import된다.
  • 패키지 접근 수준의 클래스는 같은 패키지에서만 사용할 수 있고, import가 접근 권한을 확대하지는 않는다.
  • 컴파일러와 JVM은 클래스 디렉터리나 JAR 파일을 찾아야 하며 CLASSPATH가 사용자 클래스의 검색 경로를 제공한다.
  • checked 예외는 명시적으로 처리하거나 throws로 전파해야 하고 RuntimeException 계열은 unchecked 예외다.
  • try에서 예외가 발생하면 적합한 catch 하나가 실행되고, finally는 예외 여부와 관계없이 실행된다.
  • try-with-resources는 AutoCloseable 리소스를 자동 반환하여 복잡한 close 코드를 줄인다.
  • throw는 예외 객체를 던지고 throws는 메소드가 예외를 호출자에게 전파함을 선언한다.
  • 사용자 정의 예외는 일반적으로 Exception을 상속해 만들고 필요한 조건에서 객체를 생성해 던진다.
최종 정리
패키지는 프로그램의 정적 구조를 정리하고, 예외처리는 실행 중의 비정상 흐름을 통제한다. 두 기능을 올바르게 사용하면 클래스의 위치와 접근 범위를 명확히 하면서 오류가 발생해도 자원을 안전하게 반환하고 적절한 복구 흐름을 이어 갈 수 있다.

9. 예상문제 20선

1. Java 패키지의 용도로 옳지 않은 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ④
패키지는 코드의 분류, 이름 충돌 방지와 접근 제어에 사용된다. 실행 중 예외를 처리하는 기능은 패키지의 역할이 아니다.

2. Java의 패키지 계층을 구분하는 기호는?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ②
java.util.stream처럼 점을 사용해 상위 패키지와 하위 패키지의 계층을 구분한다.

3. 모든 Java 프로그램에 자동으로 import되는 패키지는?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ①
String과 System 같은 기본 클래스가 있는 java.lang 패키지는 자동으로 import된다.

4. com.vehicle.Car라는 완전한 이름에서 Car가 의미하는 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ③
com.vehicle은 패키지 경로이고 Car는 그 패키지에 속한 클래스의 단순 이름이다.

5. 소스 파일에 package문과 import문이 모두 있을 때 올바른 순서는?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ③
package 선언은 소스의 앞에 두고 import문은 package문이 있다면 그 다음, 클래스 선언보다 앞에 배치한다.

6. import java.util.Scanner;의 효과로 옳은 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ①
import는 완전한 클래스 이름의 패키지 부분을 생략하고 단순 이름을 사용하도록 돕는 선언이다.

7. 다른 패키지에서 public이 없는 최상위 Car 클래스를 import했을 때의 결과는?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ④
import는 이름 사용을 편리하게 할 뿐 접근 권한을 확대하지 않는다. 패키지 접근 클래스는 같은 패키지에서만 사용한다.

8. 사용자 클래스의 검색 경로나 JAR 파일을 지정할 수 있는 환경변수는?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ②
컴파일러와 JVM은 CLASSPATH에 지정된 폴더나 JAR에서 필요한 사용자 클래스를 찾을 수 있다.

9. 에러와 예외의 비교로 옳은 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ①
예외는 정상 흐름을 벗어난 사건이지만 적합한 처리 코드가 잡으면 프로그램이 계속 실행될 수 있다.

10. 예외 클래스 계층의 순서로 옳은 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ④
Throwable은 Object의 하위 클래스이고 Exception과 Error는 Throwable의 하위 계층에 놓인다.

11. checked exception에 대한 설명으로 옳은 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ②
IOException 같은 checked 예외는 try-catch로 처리하거나 메소드 선언의 throws로 호출자에게 전달해야 한다.

12. 다음 중 unchecked exception의 예는?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ③
ArithmeticException은 RuntimeException 계열이므로 컴파일러가 명시적 처리를 강제하지 않는 unchecked 예외다.

13. try 블록에서 예외가 발생했을 때의 흐름으로 옳은 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ④
예외 발생 지점 이후의 try 문장은 건너뛰고 해당 예외와 일치하거나 상위 유형인 catch 하나로 제어가 이동한다.

14. 상위 예외와 하위 예외를 각각 catch할 때 올바른 배치는?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ①
상위 유형을 먼저 두면 하위 예외까지 모두 잡아 뒤의 하위 catch가 도달 불가능해지므로 세부 유형부터 배치한다.

15. finally 블록에 대한 설명으로 가장 알맞은 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ③
finally는 정상 실행이나 예외 발생과 무관하게 수행되므로 전통적으로 파일 닫기 같은 정리 코드에 사용한다.

16. try-with-resources에서 사용할 수 있는 리소스 객체의 조건은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ②
AutoCloseable을 구현한 객체를 try 괄호에서 선언하면 블록을 벗어날 때 close가 자동으로 이루어진다.

17. try-with-resources의 주된 장점은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ②
리소스의 자동 반환을 보장하므로 finally에서 직접 close하는 복잡한 코드를 줄이고 반환 누락을 예방한다.

18. throw와 throws의 차이에 대한 설명으로 옳은 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ④
throw new MyException()은 객체를 실제로 던지는 문장이고 throws IOException은 메소드 선언부의 전파 표시다.

19. checked 예외를 throws로 선언한 메소드를 호출하는 쪽의 올바른 대응은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ③
호출자는 checked 예외를 직접 잡아서 처리하거나 자신의 선언부에 throws를 표시해 더 위 호출자에게 전달해야 한다.

20. 강의 예시의 MyException처럼 checked 사용자 정의 예외를 만드는 방법은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ①
Exception을 직접 상속하면 checked 사용자 정의 예외가 되며 필요한 조건에서 throw new MyException()으로 던질 수 있다.

댓글