패키지와 import
package 선언으로 클래스를 패키지에 넣어 완전한 이름을 붙이고, 다른 패키지의 클래스를 import나 완전한 이름으로 쓰는 방법과 이름이 겹칠 때의 해결법을 배웁니다.
지금까지는 Member와 MemberRegistry를 실행용 클래스 안에 중첩해 한 파일로 만들었습니다. 예제가 작을 때는 이 방식이 편하지만, 프로그램이 커지면 클래스마다 파일을 나누게 됩니다. 그러면 곧 두 가지 문제가 생깁니다. 회원 클래스, 저장소 클래스, 콘솔 입출력 클래스가 한 폴더에 뒤섞여 무엇이 어디에 속하는지 알기 어려워집니다. 또 게시판 기능을 만들다가 게시판 운영진을 나타내는 Member 클래스가 따로 필요해지면, 이름이 같은 클래스 두 개를 구별할 방법이 없습니다.
자바는 패키지(package)로 이 문제를 풉니다. 패키지는 관련된 클래스를 묶는 이름 공간이고, 클래스의 진짜 이름은 패키지 이름까지 붙은 members.model.Member 같은 형태입니다. import는 이렇게 긴 이름을 소스 코드에서 짧게 쓰게 해 주는 문장입니다. 이 글에서는 같은 이름의 클래스 두 개를 한 파일에서 쓰려다 생기는 컴파일 오류로 시작해서, 패키지 선언과 import의 동작, 회원 저장소를 패키지로 나누는 방법까지 차례로 살펴봅니다.
이름이 같은 두 클래스를 한 파일에서 쓰기
자바 표준 라이브러리에는 Date라는 클래스가 두 개 있습니다. 하나는 날짜와 시각을 나타내는 java.util.Date이고, 다른 하나는 데이터베이스의 날짜 값을 다루는 java.sql.Date입니다. 다음 코드는 두 클래스를 모두 import한 뒤 Date를 쓰려고 합니다.
import java.sql.Date;
import java.util.Date;
public final class AmbiguousDateImport {
public static void main(String[] args) {
Date now = new Date();
System.out.println(now);
}
}이 파일은 컴파일되지 않습니다. 오류 메시지의 첫 줄은 다음과 같습니다.
error: a type with the same simple name is already defined by the single-type-import of Date두 클래스에서 패키지 이름을 뺀 부분, 곧 단순 이름(simple name)은 둘 다 Date입니다. 그런데 import는 '이 파일에서 Date라고 쓰면 이 클래스를 뜻한다'고 정하는 문장이므로, 한 파일에서 같은 단순 이름을 두 클래스에 붙일 수는 없습니다. 그래서 컴파일러는 둘째 import 줄에서 이미 Date라는 이름이 정해져 있다는 오류를 냅니다. main 안의 Date가 어느 쪽인지 알 수 없다는 reference to Date is ambiguous 오류도 이어서 출력됩니다. 첫 오류는 Date를 한 번도 쓰지 않아도 나기 때문에, 이런 import 두 줄은 함께 둘 수 없습니다.
해결 방법은 한쪽만 import하고 다른 쪽은 패키지 이름까지 붙인 완전한 이름(fully qualified name)으로 쓰는 것입니다. 예를 들어 위 파일에서 import java.sql.Date; 줄을 지우면 단순 이름 Date는 java.util.Date만 뜻하게 되어 컴파일됩니다. 데이터베이스용 날짜가 필요한 곳에서는 다음처럼 완전한 이름을 씁니다.
import java.time.LocalDate;
LocalDate memberDay = LocalDate.of(2026, 7, 14);
java.sql.Date databaseDate = java.sql.Date.valueOf(memberDay);java.sql.Date는 import하지 않았지만, 완전한 이름은 그 자체로 어느 클래스인지 분명하므로 import가 필요 없습니다. 날짜 값을 만드는 java.time.LocalDate는 import했으므로 짧게 LocalDate라고 썼고, java.sql.Date.valueOf는 이 값을 데이터베이스용 날짜로 바꿉니다. 이 두 줄을 앞의 파일에 넣어도 java.util.Date를 뜻하는 단순 이름 Date와 충돌하지 않습니다.
다른 언어에는 import ... as SqlDate처럼 별칭을 붙이는 문법이 있지만 자바에는 없습니다. 이름이 겹치는 일이 잦다면 완전한 이름을 쓰면서 databaseDate처럼 변수 이름에 역할을 드러내는 것이 자바다운 방법입니다.
package 선언과 완전한 이름
클래스를 패키지에 넣으려면 소스 파일 맨 위에 package 선언을 씁니다. 다음 클래스는 members.app 패키지에 속합니다.
package members.app;
public final class PackageNameDemo {
public static void main(String[] args) {
System.out.println(PackageNameDemo.class.getName());
System.out.println(PackageNameDemo.class.getSimpleName());
}
}PackageNameDemo.class는 이 클래스 자체를 나타내는 값이고, getName()과 getSimpleName()은 각각 클래스의 완전한 이름과 단순 이름을 돌려줍니다. 리플렉션이라고 부르는 이 기능은 리플렉션으로 클래스 정보 읽고 호출하기에서 자세히 다루고, 여기서는 이름을 출력하는 데만 씁니다.
members.app.PackageNameDemo
PackageNameDemo첫 줄 package members.app; 때문에 이 클래스의 완전한 이름은 members.app.PackageNameDemo가 됩니다. 소스에 적은 이름은 PackageNameDemo뿐이지만 자바가 이 클래스를 가리킬 때 쓰는 이름은 패키지까지 붙은 쪽입니다. 그래서 다른 패키지에 이름이 같은 PackageNameDemo가 있어도 둘은 서로 다른 클래스입니다.
package 선언은 파일에 하나만, 그리고 주석을 빼면 맨 앞에 와야 합니다. import 문은 그다음에 오고, 클래스 선언이 마지막입니다. package 선언이 없는 파일의 클래스는 이름 없는 패키지(unnamed package)에 속합니다. 지금까지 만든 예제가 모두 그랬습니다. 이름 없는 패키지는 작은 실습에는 편하지만, 이름이 없으니 다른 패키지에서 import할 방법이 없습니다. 그래서 실제 프로그램의 클래스는 모두 이름 있는 패키지에 넣습니다.
패키지 클래스를 컴파일하고 실행하기
패키지에 속한 클래스는 컴파일과 실행 방법도 조금 달라집니다. 프로젝트 폴더에서 위 파일을 src/members/app/PackageNameDemo.java에 저장하고 다음 명령을 실행해 보세요.
javac -d out src/members/app/PackageNameDemo.java
java -cp out members.app.PackageNameDemojavac의 -d out은 클래스 파일을 out 폴더 아래에 패키지 구조대로 만들라는 뜻입니다. 그래서 클래스 파일은 out/PackageNameDemo.class가 아니라 out/members/app/PackageNameDemo.class에 생깁니다. 실행할 때는 파일 경로가 아니라 완전한 클래스 이름을 줍니다. -cp out은 클래스를 찾기 시작할 폴더이고, java는 members.app.PackageNameDemo의 점을 폴더 구분으로 바꿔 out/members/app/PackageNameDemo.class를 찾습니다.
단순 이름으로 java -cp out PackageNameDemo라고 실행하면 out 바로 아래에서 클래스를 찾다가 실패해 Could not find or load main class PackageNameDemo 오류가 납니다. out/members/app 폴더에 들어가서 java PackageNameDemo를 실행해도 안 됩니다. 클래스 파일은 찾지만 그 안에 적힌 이름이 members/app/PackageNameDemo라서 wrong name 오류가 납니다. 패키지 클래스는 언제나 클래스 경로의 시작 폴더에서 완전한 이름으로 실행한다고 기억하면 됩니다.
소스 폴더와 패키지 이름
소스 파일도 패키지 이름과 같은 폴더 구조에 둡니다. members.app 패키지의 클래스라면 src/members/app/에 두는 식입니다. 사실 javac는 파일이 다른 폴더에 있어도 컴파일해 주지만, 소스 위치와 패키지가 어긋나면 사람도 도구도 클래스를 찾기 어렵습니다. IDE와 빌드 도구는 이 대응을 전제로 동작하므로 사실상 지켜야 하는 규칙입니다.
패키지 이름은 모두 소문자로 짓습니다. 전 세계에서 겹치지 않게 하려고 회사나 개인의 인터넷 도메인을 거꾸로 써서 시작하는 관례도 있습니다. 예를 들어 andongmin.com 도메인의 회원 관리 프로그램이라면 다음처럼 지을 수 있습니다.
com.andongmin.members.model
com.andongmin.members.application
com.andongmin.members.console이 교재의 예제는 짧게 쓰려고 members.model처럼 도메인 부분을 뺍니다.
하위 패키지는 포함 관계가 아니다
members.model과 members.model.internal은 이름이 이어지고 폴더도 하나가 다른 하나 안에 있지만, 자바에게는 아무 관계 없는 별개의 패키지입니다. 그래서 members.model.internal에 있는 클래스가 members.model의 클래스를 쓸 때도, 서로 전혀 다른 패키지끼리 쓸 때와 똑같이 import가 필요합니다. 접근 권한도 마찬가지입니다. 다음 글에서 배울 '같은 패키지에만 공개'하는 클래스나 멤버를 하위 패키지에서는 쓸 수 없습니다. 이름의 점(.)은 사람이 보기 좋게 정리하는 계층일 뿐, 하위 패키지가 상위 패키지 안에 들어 있다는 뜻은 아닙니다.
import는 이름을 짧게 쓰는 규칙일 뿐이다
다음 코드는 java.time 패키지의 LocalDate와 java.util 패키지의 UUID를 import해서 씁니다. 파일 맨 위의 package 선언 다음에 import 두 줄이 오고, 그 덕분에 본문에서는 java.time.LocalDate 대신 LocalDate라고만 씁니다.
package members.app;
import java.time.LocalDate;
import java.util.UUID;
public final class PackageImportDemo {
public static void main(String[] args) {
LocalDate joinedOn = LocalDate.of(2026, 7, 14);
UUID memberId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
System.out.println("joinedOn=" + joinedOn);
System.out.println("memberId=" + memberId);
}
}joinedOn=2026-07-14
memberId=123e4567-e89b-12d3-a456-426614174000LocalDate.of(2026, 7, 14)는 2026년 7월 14일을 나타내는 날짜 값을 만들고, 출력하면 2026-07-14 형식이 됩니다. UUID는 겹치지 않는 식별자를 나타내는 클래스로, 여기서는 정해 둔 문자열에서 값을 만들었기 때문에 같은 문자열이 그대로 출력됩니다. LocalDate 같은 날짜 클래스는 10장의 LocalDateTime·ZonedDateTime·Instant의 차이 등에서 자세히 다루니, 지금은 import가 하는 일에 집중합시다.
import java.time.LocalDate;는 '이 파일에서 LocalDate라고 쓰면 java.time.LocalDate를 뜻한다'는 약속입니다. 컴파일러는 소스의 LocalDate를 완전한 이름으로 바꿔 읽고, 컴파일된 클래스 파일에는 완전한 이름이 기록됩니다. 그래서 import를 지우고 코드에 java.time.LocalDate를 직접 써도 결과는 똑같습니다. import는 다른 클래스의 코드를 이 파일에 복사해 넣지 않고, 프로그램 실행에 무언가를 추가하지도 않습니다. C 언어의 #include와는 하는 일이 전혀 다릅니다.
import 없이 단순 이름만으로 쓸 수 있는 클래스도 있습니다. 같은 패키지에 있는 클래스와 java.lang 패키지의 클래스입니다. String, System, Integer, Math처럼 지금까지 import 없이 써 온 클래스가 모두 java.lang 패키지에 들어 있습니다.
별표 import의 범위
패키지 이름 뒤에 별표를 붙이면 그 패키지의 클래스 전체를 단순 이름으로 쓸 수 있습니다. 다음 한 줄은 java.util 패키지에 대해 그렇게 선언합니다. 컴파일러는 코드에 나온 단순 이름을 이 패키지에서 찾아 줄 뿐이므로, 쓰지 않는 클래스까지 프로그램에 들어가지는 않습니다.
import java.util.*;이 선언이 있으면 java.util.List, java.util.ArrayList, java.util.Scanner를 모두 단순 이름으로 쓸 수 있습니다. 하지만 하위 패키지는 포함하지 않습니다. 앞에서 본 대로 java.util.concurrent는 java.util과 별개의 패키지이므로, java.util.concurrent.Executor를 단순 이름 Executor로 쓰면 cannot find symbol 오류가 납니다. 그 클래스는 import java.util.concurrent.Executor;로 따로 가져와야 합니다.
별표 import는 짧지만, 파일 위쪽만 봐서는 이 파일이 어떤 클래스를 쓰는지 알 수 없습니다. 또 import java.util.*;와 import java.sql.*;를 함께 쓰는 파일에서 Date를 쓰면, 앞에서 본 것과 같은 reference to Date is ambiguous 오류가 납니다. 처음에는 문제가 없다가 나중에 Date를 쓰는 순간 오류가 나니 원인을 찾기도 어렵습니다. 그래서 이 교재는 필요한 클래스를 하나씩 import합니다. 대부분의 IDE도 import 줄을 자동으로 정리해 주므로 하나씩 쓰는 것이 번거롭지 않습니다.
더 깊이: 단순 이름을 찾는 순서
단순 이름이 여러 곳에 있을 때 자바가 무엇을 고르는지에는 우선순위가 있습니다. 하나씩 import한 이름이 가장 먼저이고, 그다음이 같은 패키지의 클래스입니다. 별표 import와 java.lang이 그 뒤입니다. 예를 들어 import java.util.*;와 import java.sql.*;가 함께 있더라도 import java.util.Date; 한 줄을 더하면 Date는 java.util.Date로 정해져 오류가 사라집니다. 같은 패키지에 직접 만든 Date 클래스가 있다면 별표 import의 Date보다 그쪽이 먼저입니다.
Java 25에는 모듈 하나가 공개하는 패키지 전체를 가져오는 모듈 import 선언(import module java.base;)도 정식 기능으로 들어왔습니다(JEP 511). 이 선언은 별표 import보다도 우선순위가 낮습니다. 그래서 두 모듈에서 단순 이름이 겹치면, 위와 마찬가지로 원하는 클래스를 하나씩 import해서 정합니다. 모듈은 모듈 시스템과 ServiceLoader에서 다룹니다.
회원 저장소를 패키지로 나누기
이제 회원 저장소 프로그램을 패키지로 나눠 보겠습니다. 패키지는 파일 종류가 아니라 하는 일을 기준으로 나눕니다.
members
├─ model
│ ├─ Member.java
│ └─ MemberRegistry.java
├─ application
│ └─ MemberRegistryService.java
└─ console
└─ MemberRegistryCli.javamodel 패키지에는 회원과 저장소처럼 데이터와 규칙을 가진 클래스를 둡니다. '이메일은 비어 있지 않다', '저장소가 가득 차면 더 넣지 않는다' 같은 규칙이 여기에 있습니다. application 패키지는 '회원 가입' 같은 기능 하나를 위해 모델 객체를 어떤 순서로 쓸지 정합니다. console 패키지는 Scanner로 입력을 읽고 결과를 문자열로 출력하는 일만 합니다. 이렇게 나누면 출력 형식을 바꿀 때는 console만, 회원 규칙을 바꿀 때는 model만 고치면 됩니다.
처음부터 util, common, misc처럼 무엇이든 들어갈 수 있는 패키지를 만들면 이 장점이 사라집니다. 이름만 봐서는 무엇이 들어 있는지 알 수 없으니, 관련 없는 클래스가 쌓이고 서로 얽히기 쉽습니다. 함께 바뀌고 서로 자주 협력하는 클래스를 같은 패키지에 두는 것이 기본입니다.
두 패키지로 나눈 실행 예제
application 패키지는 프로그램이 더 커져야 의미가 있으니, 여기서는 model과 console 두 패키지만 실제 파일로 만들어 보겠습니다. 첫 번째 파일은 회원 클래스입니다.
package members.model;
final class Member {
private final String email;
private final int age;
Member(String email, int age) {
if (email == null || email.isBlank() || age < 14 || age > 120) {
throw new IllegalArgumentException("invalid member");
}
this.email = email;
this.age = age;
}
int age() {
return age;
}
}class 앞에 public이 없다는 점을 눈여겨보세요. 접근 제어자를 쓰지 않은 클래스는 같은 패키지 안에서만 쓸 수 있습니다. Member는 MemberRegistry만 쓰는 클래스이므로 바깥 패키지에 보일 필요가 없습니다. 두 번째 파일은 저장소입니다.
package members.model;
public final class MemberRegistry {
private final Member[] members;
private int size;
public MemberRegistry(int capacity) {
members = new Member[capacity];
}
public void add(String email, int age) {
if (size == members.length) throw new IllegalStateException("full");
members[size] = new Member(email, age);
size++;
}
public String summary() {
int total = 0;
for (int index = 0; index < size; index++) {
total += members[index].age();
}
return "count=" + size + ", total=" + total;
}
}MemberRegistry는 console 패키지에서 써야 하므로 클래스와 생성자, add, summary에 public을 붙였습니다. 반면 add는 이메일과 나이를 받아 저장소 안에서 Member를 만들기 때문에, 바깥 패키지는 Member라는 클래스가 있는지조차 알 필요가 없습니다. 이 부분은 다음 글의 주제인 접근 제어자를 미리 써 본 것입니다. 마지막 파일은 입력을 읽는 콘솔 클래스입니다.
package members.console;
import java.util.Scanner;
import members.model.MemberRegistry;
public final class MemberRegistryCli {
public static void main(String[] args) {
Scanner scanner = new Scanner("[email protected] 40\n[email protected] 50\nquit\n");
MemberRegistry registry = new MemberRegistry(2);
while (scanner.hasNextLine()) {
String line = scanner.nextLine().trim();
if (line.equals("quit")) break;
String[] parts = line.split("\\s+");
registry.add(parts[0], Integer.parseInt(parts[1]));
}
System.out.println(registry.summary());
}
}MemberRegistryCli는 members.console 패키지에 있으므로, 다른 패키지의 MemberRegistry를 쓰려면 import members.model.MemberRegistry;가 필요합니다. 표준 라이브러리 클래스든 직접 만든 클래스든 import하는 방법은 같습니다. 입력은 키보드 대신 Scanner에 문자열을 넘겨 고정했습니다. 세 파일을 저장한 뒤 한 번에 컴파일하고 실행합니다.
javac -d out src/members/model/Member.java src/members/model/MemberRegistry.java src/members/console/MemberRegistryCli.java
java -cp out members.console.MemberRegistryClicount=2, total=90입력 두 줄로 회원 두 명이 저장되고, 나이 합계 40 + 50 = 90이 출력됩니다. 컴파일이 끝나면 out/members/model/ 아래에 Member.class와 MemberRegistry.class가, out/members/console/ 아래에 MemberRegistryCli.class가 생깁니다. MemberRegistryCli.java만 따로 컴파일하면 MemberRegistry를 찾지 못해 package members.model does not exist 오류가 나므로, 지금은 이처럼 필요한 소스 파일을 모두 함께 넘깁니다.
console 패키지는 MemberRegistry의 public 메서드만 호출할 뿐, 내부 배열이나 Member에는 손대지 않습니다. 실제로 MemberRegistryCli에 import members.model.Member;를 추가하면 Member is not public in members.model; cannot be accessed from outside package라는 컴파일 오류가 납니다. 패키지를 나누고 나면, 무엇을 바깥에 공개할지를 이렇게 컴파일러가 확인해 주는 형태로 정할 수 있습니다.
다른 패키지의 클래스를 쓰는 세 가지 방법
정리하면 다른 패키지의 클래스를 쓰는 방법은 세 가지입니다. 첫째, 완전한 이름을 그대로 씁니다.
members.model.MemberRegistry registry = new members.model.MemberRegistry(10);어느 클래스인지는 분명하지만 쓸 때마다 길어서 읽기 불편합니다. 둘째, 앞의 MemberRegistryCli처럼 파일 위쪽에 import members.model.MemberRegistry;를 두고 단순 이름을 씁니다.
MemberRegistry registry = new MemberRegistry(10);대부분의 코드는 이 방법을 씁니다. 파일 위쪽의 import 줄만 보면 이 파일이 어떤 클래스에 의존하는지 알 수 있다는 장점도 있습니다. 셋째, 별표 import로 패키지 전체를 가져옵니다. 짧지만 앞에서 본 이유로 이 교재에서는 쓰지 않습니다. 같은 파일에서 단순 이름이 같은 두 클래스가 필요하면, 첫 번째와 두 번째 방법을 섞어 하나만 import하고 다른 하나는 완전한 이름으로 씁니다.
패키지 선언, 개별 import, 완전한 이름, 별표 import와 중복 단순 이름을 서로 다른 소스 표현으로 비교한다.
package는 소속을 정하고 import는 짧은 이름을 해석한다
완전한 이름과 단순 이름의 대응, 이름 충돌과 패키지 경계를 비교합니다.
각 행은 별도 소스 표현의 비교입니다. 위에서 아래로 실행되는 단계가 아닙니다.
| 역할 | 소스 표현 | 해석 결과·경계 |
|---|---|---|
| 패키지 선언 |
현재 타입: | 완전한 이름은
|
| 개별 import |
소스에서 |
|
| 완전한 이름 | java.sql.Date.valueOf(memberDay) |
이 타입의 import 불필요 |
| 별표 import | import java.util.*; |
|
| 단순 이름 충돌 |
| 같은 파일의 중복 단순 이름 컴파일 실패 · 하나는 완전한 이름으로 사용 |
- 패키지 선언
package members.app;현재 타입
PackageNameDemo의 완전한 이름은members.app.PackageNameDemo입니다.- 개별 import
import java.util.UUID;소스의
UUID를java.util.UUID로 해석합니다.- 완전한 이름
java.sql.Date.valueOf(memberDay)java.sql.Date를 직접 지정하므로 이 타입의 import는 불필요합니다.- 별표 import
import java.util.*;java.util.List는 포함하지만java.util.concurrent.Executor는 포함하지 않습니다.- 단순 이름 충돌
import java.sql.Date;import java.util.Date;한 파일에서 두 선언은 단순 이름
Date가 겹쳐 컴파일 실패합니다. 하나는 완전한 이름으로 사용합니다.
import는 클래스 파일 복사가 아니라 이름 해석 규칙입니다. 별칭 import는 없으며, 같은 패키지와 java.lang의 타입은 import 없이 사용할 수 있습니다.
그림의 각 행은 이 글에서 본 소스 표현이 어떤 완전한 이름으로 해석되는지 보여 줍니다. 위에서 아래로 실행되는 순서가 아니라 서로 독립된 비교라는 점에 유의하세요.
패키지 이름은 폴더 정리용이기 전에 클래스 이름의 일부라는 점도 기억해 두세요. 패키지 이름을 바꾸면 그 패키지의 클래스를 import하거나 완전한 이름으로 쓰는 모든 코드를 함께 고쳐야 합니다. 그러니 처음부터 패키지를 잘게 쪼개기보다, 하는 일이 분명히 다른 큰 단위부터 나누고 필요할 때 더 나누는 편이 좋습니다.
정리
- 클래스의 진짜 이름은 패키지까지 붙은 완전한 이름입니다.
package members.app;로 선언한PackageNameDemo의 이름은members.app.PackageNameDemo입니다. - 패키지 클래스는
javac -d out으로 패키지 구조대로 컴파일하고,java -cp out members.app.PackageNameDemo처럼 완전한 이름으로 실행합니다. import는 소스에서 단순 이름을 완전한 이름으로 읽게 하는 규칙일 뿐, 코드를 복사하거나 실행에 무언가를 더하지 않습니다. 같은 패키지와java.lang의 클래스는import없이 씁니다.- 별표
import는 하위 패키지를 포함하지 않습니다. 하위 패키지는 이름만 이어질 뿐 별개의 패키지입니다. - 단순 이름이 같은 두 클래스를 한 파일에서 쓸 때는 하나만
import하고 다른 하나는 완전한 이름으로 씁니다. 자바에는 별칭import가 없습니다.
다음 글에서는 MemberRegistry에 붙인 public과 Member에서 뺀 public이 정확히 무엇을 허용하고 막는지, 네 가지 접근 제어자를 비교하며 알아봅니다.
연습 문제
문제 1: 두 날짜 클래스를 한 파일에서 쓰기
java.time.LocalDate로 2026년 7월 14일을 만들고 java.sql.Date로 변환한 뒤, 두 값의 클래스 이름을 완전한 이름으로 출력하세요. 클래스는 members.app 패키지에 둡니다. 두 날짜 클래스를 함께 쓰되 단순 이름이 충돌하지 않게 하세요.
해설 보기
package members.app;
import java.time.LocalDate;
public final class QualifiedDateNames {
public static void main(String[] args) {
LocalDate day = LocalDate.of(2026, 7, 14);
java.sql.Date stored = java.sql.Date.valueOf(day);
System.out.println(day.getClass().getName());
System.out.println(stored.getClass().getName());
}
}java.time.LocalDate
java.sql.DateLocalDate만 import하고 java.sql.Date는 완전한 이름으로 썼습니다. 이 파일에는 단순 이름 Date가 한 번도 등장하지 않으므로 충돌할 일이 없습니다. 읽는 사람도 호출문만 보고 어느 패키지의 클래스인지 알 수 있습니다. getClass().getName()은 객체의 실제 클래스의 완전한 이름을 돌려주므로 두 줄에 각각의 패키지 이름이 붙어 출력됩니다.
컴파일과 실행은 본문의 PackageNameDemo와 같은 방식으로 합니다.
javac -d out src/members/app/QualifiedDateNames.java
java -cp out members.app.QualifiedDateNames날짜를 고정한 것은 실행할 때마다 같은 결과를 얻기 위해서입니다. 실행하는 날의 날짜가 필요하면 첫 줄을 LocalDate day = LocalDate.now();로 바꾸면 됩니다. 이때 날짜는 실행 환경의 기본 시간대를 기준으로 정해지며, 출력되는 두 클래스 이름은 날짜와 상관없이 같습니다.
문제 2: 컴파일되는지 판단하기
다음 세 경우가 각각 컴파일되는지 판단하고, 안 된다면 이유를 설명하세요. 세 경우 모두 members.console 패키지의 클래스 하나를 담은 파일입니다.
- (가)
import java.util.*;한 줄만 쓰고, 클래스 안에 필드를Executor executor;로 선언했습니다. - (나)
import java.util.List;와import java.awt.List;두 줄을 함께 썼습니다. 클래스 본문에서는List를 한 번도 쓰지 않았습니다. - (다)
import문 없이, 클래스 안에 필드java.util.List<String> names;와java.awt.List widget;을 선언했습니다.
해설 보기
(가)는 컴파일되지 않습니다. Executor는 java.util.concurrent 패키지의 클래스인데, import java.util.*;는 하위 패키지를 포함하지 않으므로 cannot find symbol 오류가 납니다. import java.util.concurrent.Executor;를 추가해야 합니다.
(나)도 컴파일되지 않습니다. java.util.List와 java.awt.List는 단순 이름이 둘 다 List이므로, 두 클래스를 하나씩 import하는 순간 같은 단순 이름이 이미 정해져 있다는 오류가 납니다. 클래스 본문에서 List를 한 번도 쓰지 않았어도 마찬가지입니다.
(다)는 컴파일됩니다. 두 클래스를 모두 완전한 이름으로 썼으므로 import가 필요 없고, 단순 이름이 겹칠 일도 없습니다. 실제로는 더 자주 쓰는 java.util.List를 import하고 java.awt.List만 완전한 이름으로 쓰는 편이 읽기 쉽습니다.