방송대 Java프로그래밍 7강: 패키지 탐색과 예외 처리 흐름
“클래스를 찾을 수 없다”와 “실행 중 예외가 발생했다”는 모두 프로그램이 멈추는 문제처럼 보이지만 진단 순서는 전혀 다르다. 전자는 클래스의 이름·위치·접근 범위를 확인해야 하고, 후자는 예외 객체가 어디서 발생해 어느 처리기로 이동하는지 추적해야 한다. 이 글은 실패 신호를 먼저 분류하고, 패키지·import·CLASSPATH와 try·catch·throws 가운데 어떤 도구를 선택할지 판단하는 과정을 다룬다.
멈춘 지점을 보면 먼저 확인할 경로가 달라진다
패키지는 관련된 클래스와 인터페이스를 묶고 이름 충돌과 접근 범위를 관리한다. 예외 처리는 실행 중 정상 흐름을 벗어난 사건을 객체로 전달하고 적절한 코드에서 복구하도록 한다. 두 주제의 공통점은 “코드가 필요한 대상을 어디서 찾는가”에 있다. 패키지 문제에서는 클래스 정의를 찾고, 예외 처리에서는 던져진 예외를 받을 처리기를 찾는다.
| 관찰한 신호 | 먼저 확인할 것 | 대표 해결 방향 |
|---|---|---|
| 컴파일러가 자료형 이름을 해석하지 못함 | 완전한 이름, import, 패키지 선언 | 클래스의 소속과 참조 이름을 일치시킴 |
| 다른 패키지의 클래스를 사용할 수 없음 | 클래스의 접근 수준 | 필요한 클래스를 public으로 공개 |
| JVM이 실행할 클래스 파일을 찾지 못함 | 클래스 파일·jar의 검색 경로 | CLASSPATH 또는 프로젝트 빌드 경로 확인 |
| 실행 중 특정 조건에서 흐름이 중단됨 | 발생한 예외 자료형과 호출 관계 | catch로 직접 처리하거나 throws로 전파 |
| 파일 등 자원 반환 코드가 복잡함 | 자원이 AutoCloseable인지 | try-with-resources로 자동 반환 |
첫 진단 질문: 실행 전에 이름·접근·경로에서 막혔는가, 실행 후 예외 객체가 만들어졌는가? 이 분류를 건너뛰면 import 문제에 catch를 추가하거나 예외 문제에 CLASSPATH를 고치는 식의 엉뚱한 해결을 하게 된다.
패키지는 폴더 이름 이상의 소속과 공개 경계다
패키지는 관련 있는 클래스와 인터페이스의 묶음이다. 컴파일된 .class 파일이 패키지 이름에 대응하는 폴더에 저장되므로 폴더 구조와 연결되지만, 단순 저장 폴더만을 뜻하지는 않는다. 패키지는 클래스를 쉽게 찾게 하고, 같은 단순 이름의 충돌을 피하며, 패키지 접근 수준으로 공개 범위를 조절한다.
사용자 정의 패키지는 소스 파일의 package 선언으로 정한다. 다음 학습용 예에서 Car의 완전한 이름은 com.vehicle.Car이고, 컴파일 결과는 패키지 계층에 맞는 com/vehicle/Car.class 위치에 놓인다.
package com.vehicle;
public class Car {
private final String type = "compact";
public String getType() {
return type;
}
}
패키지 이름은 점으로 계층을 구분한다. java.lang, java.io, java.nio.file, java.util, java.util.stream처럼 상위 이름이 같아도 각각 별도 패키지다. 특히 java.util을 가져왔다고 해서 하위 이름인 java.util.stream까지 자동으로 포함되는 것은 아니다.
JDK가 제공하는 클래스 라이브러리는 기능에 따라 시스템 패키지로 나뉜다. 강의에서는 과거의 단일 rt.jar 방식과 최근 JDK의 모듈 단위 분산 저장을 함께 소개한다. 설치 경로와 저장 방식은 환경에 따라 달라질 수 있으므로, 핵심은 특정 경로 암기가 아니라 “필요한 클래스가 JDK 또는 프로젝트 검색 범위에 있어야 한다”는 원리다.
클래스 이름을 해석하는 세 가지 방법을 구분한다
다른 패키지의 public 클래스를 사용할 때 가장 분명한 방법은 완전한 클래스 이름을 쓰는 것이다. 같은 클래스를 자주 사용하면 import로 이름을 미리 연결해 단순 이름만 쓸 수 있다. java.lang에 속한 기본 클래스는 별도의 import 없이 사용할 수 있다.
import java.util.Scanner;
public class InputDemo {
public static void main(String[] args) {
java.time.LocalDate day = java.time.LocalDate.now();
Scanner scanner = new Scanner(System.in);
String label = "today"; // java.lang.String은 자동으로 사용 가능
}
}
| 표현 방식 | 예 | 선택할 상황 |
|---|---|---|
| 완전한 이름 | java.util.Scanner | 같은 단순 이름의 클래스를 구분하거나 한두 번만 사용할 때 |
| 단일 클래스 import | import java.util.Scanner; | 특정 클래스를 반복해서 사용할 때 |
| 패키지 단위 import | import java.util.*; | 해당 패키지의 여러 클래스를 단순 이름으로 쓸 때 |
| 자동 포함 | java.lang의 String | 별도 import가 필요 없는 기본 시스템 패키지일 때 |
import는 소스 코드 맨 앞의 package 선언 다음에 둔다. 별표는 지정한 패키지에 직접 속한 클래스의 이름을 간단히 쓰게 할 뿐, 그 아래의 모든 하위 패키지를 재귀적으로 가져오는 표기가 아니다.
이름을 찾은 뒤에는 접근 수준이라는 두 번째 문을 통과한다
클래스 파일을 찾았다고 해서 언제나 사용할 수 있는 것은 아니다. 최상위 클래스에 public이 없으면 패키지 접근 수준을 가지므로 같은 패키지에서만 사용할 수 있다. 이 제한은 상속으로 우회되지 않는다.
// com.vehicle 패키지
package com.vehicle;
class Car { }
public class Bus extends Car { }
// 다른 패키지
package app;
import com.vehicle.*;
class CityBus extends Bus { } // 가능: Bus는 public
// class CityCar extends Car { } // 오류: Car는 패키지 접근 수준
import com.vehicle.*;가 있어도 Car의 접근 범위는 넓어지지 않는다. import는 이름을 해석하게 할 뿐 접근 권한을 부여하지 않기 때문이다. 반면 같은 com.vehicle 패키지 안에서는 package-private인 Car를 사용할 수 있다.
클래스 사용의 세 관문: ① 컴파일러가 패키지와 클래스 이름을 해석하는가? ② 현재 코드가 그 클래스에 접근할 수 있는가? ③ 실행 환경이 실제 클래스 파일이나 jar를 검색 경로에서 찾는가? 세 관문은 서로 대신할 수 없다.
CLASSPATH는 사용자 클래스 파일을 찾는 실행 환경의 지도다
컴파일하거나 실행할 때 컴파일러와 JVM은 필요한 A.class가 있는 경로나 그 파일을 포함한 jar를 찾아야 한다. 기본·확장 패키지 밖의 사용자 클래스를 찾도록 지정하는 검색 경로가 CLASSPATH다. 프로젝트 도구에서는 Java Build Path나 라이브러리 설정으로 같은 역할을 구성할 수 있다.
예를 들어 코드가 graphics.Circle을 사용한다면 검색 경로의 어떤 루트 아래에 graphics/Circle.class가 있거나, 검색 대상 jar 안에 같은 패키지 경로가 있어야 한다. CLASSPATH에 Circle.class 자체의 위치를 아무렇게나 넣는 것이 아니라, 완전한 이름의 패키지 경로를 따라갈 수 있는 시작점을 제공해야 한다.
진단 구분: 소스에서 단순 이름을 못 찾으면 import와 패키지 선언을 먼저 보고, 접근 오류면 public 여부를 본다. 이름과 접근이 맞는데 실행 환경이 클래스를 못 찾는다면 클래스 파일 생성 위치와 검색 경로를 점검한다.
예외는 정상 흐름을 바꾸는 객체이고 에러와 구분된다
강의에서는 에러(Error)를 실행 중 발생하는 심각한 문제로, 예외(Exception)를 처리하여 복구 가능성을 갖는 경미한 오류로 구분한다. 예외가 발생하면 그 정보를 담은 예외 객체가 만들어져 던져지고, 일치하는 예외 처리 코드가 이를 잡으면 처리 후 프로그램 흐름을 이어 갈 수 있다.
계층으로 보면 Throwable 아래에 Error와 Exception이 있다. Exception 아래의 RuntimeException 계열은 unchecked exception이며, IOException처럼 그 밖의 많은 예외는 checked exception이다.
| 분류 | 강의의 처리 관점 | 예 |
|---|---|---|
Error | 더 이상 정상 실행을 지속하기 어려운 심각한 오류 | Error의 하위 유형 |
| checked exception | 직접 처리하거나 호출자에게 전파하는 명시적 조치 필요 | IOException |
| unchecked exception | 컴파일러가 처리를 강제하지 않으며 코드 조건 점검이 중요 | ArithmeticException, NullPointerException, IndexOutOfBoundsException |
unchecked라는 이름은 “발생해도 괜찮다”는 뜻이 아니다. 예를 들어 배열 범위를 벗어나거나 0으로 나누는 문제는 문법적으로 catch가 필수는 아니지만, 입력 조건과 인덱스를 올바르게 검사해 원인을 제거해야 한다.
try에서 예외가 생기면 남은 문장을 건너뛰고 처리기로 이동한다
try에는 예외가 발생할 수 있는 코드를 두고, catch에는 특정 예외 자료형을 받을 처리 코드를 둔다. 여러 catch가 있으면 발생한 객체와 자료형이 일치하거나 그 상위 유형인 첫 번째 catch 하나만 실행된다. 상위 예외를 먼저 두면 하위 예외용 catch에 도달할 수 없으므로 구체적인 하위 유형을 앞에 둔다.
int[] numbers = {4, 8, 12};
try {
System.out.println(numbers[3]);
System.out.println(20 / 0);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("index error");
} catch (ArithmeticException e) {
System.out.println("division error");
}
System.out.println("continue");
실행을 단계별로 보면 ① numbers[3]에서 배열 범위 예외가 생긴다. ② try의 다음 문장인 20 / 0은 실행되지 않는다. ③ 첫 번째 catch가 일치하여 index error를 출력한다. ④ catch 다음으로 이동해 continue를 출력한다. 따라서 산술 예외용 catch가 존재해도 이번 실행에서는 사용되지 않는다.
finally는 정상적인 예외 처리 흐름에서 예외 발생 여부와 무관하게 실행되므로 자원 반환 같은 마무리 작업에 사용할 수 있다. 다만 자원 관리에는 뒤에서 다룰 try-with-resources가 더 간결한 선택이 될 수 있다.
직접 처리와 간접 처리는 책임을 맡는 위치가 다르다
예외가 발생할 수 있는 메서드가 그 자리에서 try-catch로 해결하면 직접 처리다. 메서드 선언의 throws에 예외 유형을 표시해 호출한 메서드가 처리하도록 맡기면 간접 처리, 즉 예외의 전파다.
class CharInput {
char readChar() throws java.io.IOException {
int value = System.in.read();
return (char) value;
}
}
public class InputTest {
public static void main(String[] args) {
CharInput input = new CharInput();
try {
System.out.println(input.readChar());
} catch (java.io.IOException e) {
System.out.println("input failed");
}
}
}
readChar()는 IOException을 처리하지 않고 호출자에게 전달한다. 따라서 호출한 main()이 try-catch로 처리한다. checked exception을 발생시킬 수 있는 메서드를 호출할 때는 이렇게 직접 처리하거나, 현재 메서드도 throws로 더 위의 호출자에게 전파해야 한다.
| 판단 상황 | 직접 처리 try-catch | 간접 처리 throws |
|---|---|---|
| 현재 메서드가 복구 방법을 알고 있음 | 적합 | 호출자가 처리해야 할 특별한 이유가 없다면 불필요 |
| 호출자가 실패 정책을 결정해야 함 | 정보 없이 임의 처리하면 부적합 | 적합 |
| 책임의 의미 | 예외 객체를 잡아 처리 | 처리 책임을 호출자에게 전달 |
| 문법 위치 | 실행문 주변 | 메서드 선언부 |
throw와 throws 구분: throw는 실행문에서 예외 객체 하나를 실제로 던지고, throws는 메서드 선언부에서 발생 가능한 예외 유형과 처리 책임의 전달을 표시한다.
try-with-resources는 사용과 반환을 한 구조로 묶는다
파일, 네트워크 연결, 데이터베이스 연결 같은 외부 자원은 사용 후 close()로 반환해야 한다. 일반 try-catch-finally에서는 자원 변수를 바깥에 선언하고 finally에서 null 여부를 확인하며 닫아야 하므로 코드가 길고 반환 누락 가능성도 생긴다.
try-with-resources는 괄호 안에 자원 객체를 선언한다. 그 자원 유형이 AutoCloseable을 구현했다면 try 블록을 벗어날 때 자동으로 close()가 호출된다.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class FirstLineReader {
static void printFirstLine(String path) {
try (BufferedReader reader =
new BufferedReader(new FileReader(path))) {
System.out.println(reader.readLine());
} catch (IOException e) {
System.out.println("read failed");
}
}
}
경로가 올바르면 첫 줄을 읽고 블록을 나가면서 reader가 닫힌다. 파일 열기나 읽기에서 예외가 발생해도 생성이 완료된 자원은 반환된다. 그러므로 단순히 finally 문법을 줄이는 편의 기능이 아니라, 자원의 생명주기와 예외 흐름을 같은 구조 안에서 관리하는 장치로 이해해야 한다.
사용자 정의 예외는 도메인 조건을 이름 있는 실패로 만든다
표준 예외만으로 프로그램의 의미를 충분히 표현하기 어렵다면 사용자가 예외 클래스를 만들 수 있다. 강의에서는 일반적으로 Exception을 상속하고, 필요한 조건에서 새 예외 객체를 throw하는 형태를 제시한다.
다음은 학습을 위해 주문 수량이 허용 범위를 넘으면 이름 있는 예외를 던지도록 직접 구성한 예다. validate(9)는 통과하지만 validate(13)은 QuantityException 객체를 만든 뒤 던진다.
class QuantityException extends Exception {
QuantityException(String message) {
super(message);
}
}
class OrderRule {
static void validate(int quantity) throws QuantityException {
if (quantity > 12) {
throw new QuantityException("maximum is 12");
}
}
}
이 예외는 Exception을 직접 상속했으므로 checked exception이다. 호출자는 try-catch로 처리하거나 다시 throws로 전파해야 한다. 중요한 것은 예외 클래스를 많이 만드는 것이 아니라, 호출자가 구분해 처리할 필요가 있는 실패 조건에 명확한 이름과 정보를 부여하는 것이다.
실패 상황은 이름·접근·위치·흐름·책임 순서로 판정한다
- 이름: 완전한 클래스 이름과 import가 클래스의 실제 패키지와 맞는지 확인한다.
- 접근: 다른 패키지라면 클래스가 public인지, package-private 경계를 넘으려는지 본다.
- 위치: 컴파일된 패키지 경로 또는 jar가 검색 경로에 있는지 확인한다.
- 흐름: 예외가 발생한 문장에서 try가 중단되고 어떤 catch가 처음 일치하는지 추적한다.
- 책임: 현재 메서드가 복구할지, throws로 호출자에게 판단을 넘길지 결정한다.
- 자원: 반환이 필요한 객체라면 try-with-resources를 사용할 수 있는지 확인한다.
간단한 자가 점검으로 “import를 추가하면 package-private 클래스에 접근할 수 있다”, “catch가 여러 개면 모두 실행된다”, “throws가 예외 객체를 생성한다”라는 세 문장을 판정해 보자. 세 문장 모두 틀리다. import는 접근 권한을 바꾸지 않고, catch는 첫 일치 블록 하나만 실행되며, 객체를 실제로 던지는 문장은 throw다.
핵심 개념 정리
- 패키지는 클래스·인터페이스의 소속을 정해 이름 탐색, 충돌 방지와 접근 범위에 관여한다.
- 완전한 이름과 import는 이름 해석 방법이며, public 여부와 클래스 검색 경로는 별도 조건이다.
- 예외가 발생하면 try의 남은 문장을 건너뛰고 자료형이 맞는 첫 catch로 제어가 이동한다.
- checked exception은 직접 처리하거나 throws로 전파해야 하고, unchecked exception은 컴파일러가 처리를 강제하지 않는다.
- throw는 객체를 던지는 실행문이고, throws는 메서드가 처리 책임을 호출자에게 전달한다는 선언이다.
- try-with-resources는 AutoCloseable 자원의 반환을 블록 종료와 연결하며, 사용자 정의 예외는 프로그램 고유의 실패 조건을 표현한다.
오류 메시지를 만나면 곧바로 문법을 고치기보다 이름 해석 실패인지 → 접근 경계인지 → 클래스 위치 문제인지 → 실행 중 예외인지 분류하고, 예외라면 발생 지점 → 첫 일치 catch → 처리 책임의 최종 위치 → 자원 반환까지 화살표로 추적한다. 이 순서는 새로운 패키지와 호출 계층에서도 같은 방식으로 적용할 수 있다.
예상문제 10선
1. Java 패키지의 역할을 가장 정확하게 설명한 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
- ① 오답: 예외 객체의 전달은 예외 처리의 역할이며 패키지의 정의가 아니다.
- ② 오답: 패키지는 서로 다른 소속의 같은 단순 클래스 이름을 구분하는 데 도움을 준다.
- ③ 오답: 패키지는 접근 경계를 만들며, import도 package-private 범위를 넓히지 않는다.
- ④ 정답: 소속 분류, 이름 충돌 방지, 탐색과 접근 제어라는 핵심 기능을 함께 설명했다.
2. import java.util.*;에 대한 설명으로 옳은 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
- ① 정답: 별표는 지정된 패키지에 속한 클래스 이름을 간단히 쓰게 하는 표기다.
- ② 오답: 점으로 이어진 하위 패키지는 별도 패키지이므로 자동 포함되지 않는다.
- ③ 오답: import는 이름 해석만 돕고 클래스의 접근 수준을 바꾸지 않는다.
- ④ 오답: import는 실제 클래스 파일이나 jar를 생성하거나 대체하지 않는다.
3. 다른 패키지의 class Car를 import했지만 접근 오류가 난다. Car 선언에 없는 키워드 중 먼저 확인할 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
- ① 오답: final은 상속이나 변경을 제한하지만 패키지 밖 공개 여부를 정하지 않는다.
- ② 오답: abstract는 객체 생성과 미완성 구현에 관한 표지이며 접근 범위의 원인이 아니다.
- ③ 정답: 최상위 클래스에 public이 없으면 패키지 접근 수준이어서 다른 패키지에서 사용할 수 없다.
- ④ 오답: 최상위 클래스의 패키지 간 접근 문제를 static 추가로 해결하지 않는다.
4. 본문의 배열 예외 코드가 출력하는 순서로 옳은 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
- ① 오답: 배열 접근이 산술 문장보다 먼저 실행되어 배열 예외가 먼저 발생한다.
- ② 정답: 첫 문장에서 배열 예외를 잡고 try-catch 뒤의 출력으로 이어진다.
- ③ 오답: 예외가 생기면 try의 남은
20 / 0을 건너뛰므로 두 번째 catch는 실행되지 않는다. - ④ 오답: catch 처리가 끝난 뒤에야 try-catch 다음의
continue가 출력된다.
5. checked exception을 발생시킬 수 있는 메서드 호출에 대한 잘못된 설명은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
- ① 오답: 예외 객체를 현재 호출부에서 잡는 유효한 직접 처리 방식이다.
- ② 오답: 현재 메서드가 복구하지 않고 호출자에게 책임을 넘기는 유효한 방식이다.
- ③ 오답: checked exception에서는 어느 호출 단계가 책임질지 명시해야 한다.
- ④ 정답: import는 이름만 해석하며 checked exception의 처리 의무를 없애지 않는다.
6. throw와 throws를 바르게 연결한 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
- ① 오답: 두 키워드는 패키지 이름 해석이 아니라 예외 발생과 전파에 사용한다.
- ② 정답: 실행문과 메서드 선언부라는 문법 위치 및 역할을 정확히 구분했다.
- ③ 오답: 자동 자원 반환은 try-with-resources의 역할이고 finally는 별도 블록이다.
- ④ 오답: 키워드를 쓴다고 예외 계층의 checked·unchecked 분류가 바뀌지 않는다.
7. catch가 RuntimeException용과 ArithmeticException용으로 모두 필요할 때 올바른 선언 순서는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
- ① 정답: 더 구체적인 하위 예외를 먼저 잡아야 뒤의 상위 유형 처리기도 도달 가능하다.
- ② 오답: 상위 유형이 하위 예외까지 먼저 잡아 뒤의 구체적 catch가 도달 불가능해진다.
- ③ 오답: 같은 유형의 catch를 중복하면 유형별 처리라는 목적을 잃고 유효한 분기가 되지 않는다.
- ④ 오답: finally는 catch 목록 뒤에 위치하며 catch 선택 순서를 대신하지 않는다.
8. try-with-resources가 자원을 자동으로 반환하기 위한 핵심 조건은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
- ① 오답: 예외 계층 상속은 close 호출을 위한 자원 계약이 아니다.
- ② 오답: 접근 수준이 아니라 자동으로 닫을 수 있다는 인터페이스 계약이 필요하다.
- ③ 정답: 블록을 벗어날 때
close()를 호출할 수 있게 하는 핵심 자원 계약이다. - ④ 오답: 파일 자원 클래스처럼 다른 패키지의 클래스도 조건을 만족하면 사용할 수 있다.
9. 본문의 OrderRule.validate(13)에서 일어나는 일은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
- ① 오답: 13은 허용 최댓값 12보다 크므로 예외 조건에 들어간다.
- ② 오답: 예제에서 명시적으로 생성하는 유형은 사용자 정의
QuantityException이다. - ③ 오답: throws는 가능성과 전파를 선언하고, 객체 생성과 던지기는
throw new가 수행한다. - ④ 정답:
13 > 12가 참이므로 사용자 정의 예외 객체가 호출자 쪽으로 전달된다.
10. 다른 패키지의 파일 읽기 클래스를 사용했는데 실행 전에 접근 오류가 난다. 가장 적절한 첫 해결 순서는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
- ① 정답: 실행 전 이름·접근 문제를 먼저 해결하고, 이후 checked exception의 처리 책임을 정하는 순서가 맞다.
- ② 오답: 산술 예외 처리는 클래스 접근 오류와 자료형도 단계도 관련이 없다.
- ③ 오답: throws는 예외 전파 선언이며 클래스의 패키지 접근 수준을 바꾸지 않는다.
- ④ 오답: 검색 경로 제거는 사용자 클래스 위치 문제를 해결하지 않으며 패키지 공개 범위도 바꾸지 않는다.
참고 자료와 작성 기준
이 글은 해당 차시 강의자료를 바탕으로 학습 목적에 맞게 재구성한 비공식 학습자료입니다. 실패 신호 분류표, 클래스 사용의 세 관문, 예외 실행 추적, 처리 책임 선택표와 사용자 정의 예외 사례는 학습자가 새로운 코드에서도 판단 절차를 적용하도록 구성하고 검토했습니다.
- 작성·편집: 올에이클래스 학습연구팀
- 주요 근거: 한국방송통신대학교 Java프로그래밍 7강 「패키지와 예외처리」 강의자료(교재 6장)
- 보충 자료: 별도의 외부 자료를 본문 근거로 사용하지 않음
- 편집 원칙: 올에이클래스 편집 정책
- 최종 내용 검토: 2026-09-01
댓글
댓글 쓰기