같은 반에 민수가 둘이면 “김민수”, “이민수”처럼 성을 붙여 부른다. 클래스에도 성을 붙여서, 서로 다른 회사가 똑같은 이름으로 만든 클래스를 한 프로그램에서 함께 쓸 수 있다. 성은 폴더 구조와 짝이 맞아야 해서, 번역해 나온 파일을 다른 폴더로 옮기면 그 클래스를 찾지 못한다. 매번 성까지 붙여 부르기 번거로우면 파일 맨 위에 “이 파일에서 민수는 김민수다”라고 한 번 적어 두고 이름만 부른다. 단, 이름이 같은 둘을 한 파일에서 이렇게 적어 둘 수는 없다.
예시로 보기
내가 만든 Car 클래스가 있는데, 가져다 쓰려는 다른 회사 코드에도 Car가 있다. 이름만으로는 new Car()가 어느 쪽인지 가릴 수 없다. 자바는 클래스를 패키지라는 묶음에 넣고, 패키지 이름을 클래스 이름 앞에 붙여 가른다. 다음 폴더 구조로 만들어 본다.
src/
├── com/mycompany/Car.java package com.mycompany;
├── com/yourcompany/Car.java package com.yourcompany;
├── com/hankook/Tire.java package com.hankook;
└── app/Main.java package app;
// src/com/mycompany/Car.java
package com.mycompany; // Car 클래스의 패키지
import com.hankook.Tire; // Tire 클래스의 전체 이름
public class Car {
Tire tire = new Tire(); // com.hankook.Tire를 쓴다
public String info() {
return "mycompany Car + " + tire.info();
}
}
// src/app/Main.java
package app;
import com.mycompany.Car;
public class Main {
public static void main(String[] args) {
Car mine = new Car(); // import한 쪽
com.yourcompany.Car yours = new com.yourcompany.Car(); // 전체 이름으로
System.out.println(mine.info());
System.out.println(yours.info());
System.out.println(mine.getClass().getName());
System.out.println(yours.getClass().getName());
}
}
번역하고 실행한 결과다1.
$ javac -d out $(find src -name "*.java")
$ find out -name "*.class"
out/app/Main.class
out/com/hankook/Tire.class
out/com/mycompany/Car.class
out/com/yourcompany/Car.class
$ java -cp out app.Main
mycompany Car + hankook Tire
yourcompany Car
com.mycompany.Car
com.yourcompany.Car
이름이 같은 Car 둘이 한 프로그램에서 함께 돈다. 클래스의 진짜 이름은 com.mycompany.Car처럼 패키지까지 붙인 전체 이름이기 때문이다. 번역해 나온 .class 파일도 패키지 이름과 똑같은 폴더 아래에 놓였다.
java.util.Date와 java.sql.Date, import한 이름과 전체 이름이 같은 클래스인 것)은 — 18_java-package_impl.java정의
패키지는 이름이 같은 클래스를 가려내는 데 쓰는 묶음이다.
- 패키지 이름은 보통 회사의 도메인 이름을 거꾸로 써서 만든다. 도메인은 세상에 하나뿐이라 서로 겹치지 않는다.
- 점(
.)으로 위 패키지와 아래 패키지를 나눈다.com.mycompany는com아래의mycompany다. - 패키지에 든 바이트코드 파일(
.class)은 따로 떼어 다른 폴더로 옮길 수 없다.
flowchart TD
com["com"] --> my["mycompany"]
com --> your["yourcompany"]
com --> hk["hankook"]
my --> c1["Car.class<br/>com.mycompany.Car"]
your --> c2["Car.class<br/>com.yourcompany.Car"]
hk --> t["Tire.class<br/>com.hankook.Tire"]
폴더를 위에서부터 따라 내려간 길이 곧 클래스의 전체 이름이다.
마지막 항목을 실험으로 확인한다. out/com/mycompany/Car.class를 out/com/other/로 옮기고 다시 실행하면 이렇게 된다1.
Exception in thread "main" java.lang.NoClassDefFoundError: com/mycompany/Car
Car.class 안에는 “나는 com.mycompany.Car다”라고 적혀 있고, 자바는 그 이름대로 com/mycompany/ 폴더에서 파일을 찾는다. 폴더와 이름이 어긋나면 찾지 못한다.
패키지 선언
package 키워드 뒤에 패키지 이름을 적는다. 소스 파일의 맨 위에 와야 한다.
package 상위패키지.하위패키지;
public class 클래스명 { ... }
- 패키지 이름은 모두 소문자로 쓴다.
- 겹치지 않게 회사 도메인을 거꾸로 쓰고, 끝에 프로젝트 이름을 붙인다:
com.samsung.projectname,com.lg.projectname,org.apache.projectname.
import 문
다른 패키지의 클래스를 쓰려면 import 문으로 그 클래스가 어느 패키지의 것인지 밝힌다.
import문은 패키지 선언과 클래스 선언 사이에 쓴다.import뒤에는 클래스의 전체 이름을 적는다:import com.hankook.Tire;
import는 긴 이름을 줄여 부르게 해 주는 것뿐이다. import 없이 전체 이름을 직접 써도 똑같이 돈다(위 Main의 com.yourcompany.Car)1.
| 잘못 쓴 코드 | 번역기 오류 |
|---|---|
import 없이 다른 패키지의 Car를 이름만으로 쓴다 |
cannot find symbol ... symbol: class Car |
한 파일에서 import com.mycompany.Car;와 import com.yourcompany.Car;를 함께 쓴다 |
a type with the same simple name is already defined by the single-type-import of Car |
import 문을 package 선언보다 위에 쓴다 |
class, interface, enum, or record expected |
둘째 줄이 요약의 조건이다. 이름이 같은 두 클래스를 한 파일에서 쓰려면 한쪽만 import하고 다른 쪽은 전체 이름으로 쓴다1.
활용
- 자바가 주는 클래스도 패키지에 들어 있다.
String의 전체 이름은java.lang.String,Arrays는java.util.Arrays다.java.lang패키지만은import없이 쓸 수 있어서String과System에는import를 쓰지 않았다1. - 자바 안에도 이름이 같은 클래스가 있다.
java.util.Date와java.sql.Date는 이름은 같고 서로 다른 클래스다1. package선언이 없는 파일의 클래스는 이름 없는 패키지에 들어간다. 지금까지 만든 연습 코드가 모두 그렇다1.- 패키지는 이름을 가르는 것 말고도, 누가 이 클래스와 멤버를 쓸 수 있는지의 경계가 된다(접근 제한자).
연결
- 선수: 클래스와 객체, JVM과 바이트코드
- 패키지 폴더째로 한 파일에 묶기: 라이브러리와 JAR
- 패키지를 다시 묶는 더 큰 단위: 모듈
확인 문제
C1 패키지를 쓰는 까닭과, 패키지 이름을 짓는 규칙 두 가지를 쓰라. package 선언과 import 문은 파일의 어디에 쓰는가?
답: 이름이 같은 클래스를 가려내려고 쓴다. 이름은 모두 소문자로, 회사 도메인을 거꾸로 쓰고 끝에 프로젝트 이름을 붙인다. package 선언은 파일 맨 위, import 문은 패키지 선언과 클래스 선언 사이에 쓴다.
C2 out/com/mycompany/Car.class를 out/com/other/ 폴더로 옮기면 왜 프로그램이 Car를 찾지 못하는가?
답: 클래스의 이름은 패키지까지 붙인 com.mycompany.Car이고, 자바는 그 이름을 폴더 길 com/mycompany/Car.class로 바꿔 찾는다. 파일을 옮기면 그 길에 파일이 없다. 파일 안에 적힌 패키지 이름도 그대로라 새 폴더와 맞지 않는다.
이유: “import 문을 고치면 된다”는 오답이다. 옮긴 .class 파일 속의 이름은 여전히 com.mycompany.Car다. 패키지를 바꾸려면 소스의 package 선언을 고쳐 다시 번역해야 한다.
C3 com.mycompany.Car와 com.yourcompany.Car를 app.Main 한 파일에서 둘 다 쓰려 한다. 되는 것을 고르라. (가) 둘 다 import하고 Car라고 쓴다. (나) 하나만 import하고 다른 하나는 com.yourcompany.Car라고 쓴다. (다) 아무것도 import하지 않고 둘 다 전체 이름으로 쓴다.
답: (나)와 (다).
이유: (가)는 Car라는 짧은 이름이 둘을 가리키게 되어 a type with the same simple name is already defined 오류가 난다. import는 줄임말을 정하는 것이라, 같은 줄임말을 두 클래스에 줄 수 없다.