Package란?
패키지(Package)는 서로 관련된 여러 클래스와 인터페이스를 묶은 그룹이다. 클래스는 패키지 하위에 저장되고, 실제 클래스명은 패키지명까지 포함한 것이다. 예시로, String 클래스는 java.lang 패키지에 속하며, 실제 이름은 java.lang.String이다.
Package와 File System
Java는 파일 시스템의 디렉토리와 파일을 사용하여 패키지와 클래스 및 인터페이스를 조직화하고 관리한다. 패키지는 디렉토리와 맵핑되고 클래스와 인터페이스는 파일과 맵핑된다. String 클래스 파일은 java 디렉터리의 lang 서브 디렉터리 내에 저장된 파일이다.
이로써, 동일한 이름을 가진 클래스일지라도 다른 패키지에 존재하는 것이 가능하여 이름 충돌을 방지한다. 그리하여 다른 개발자의 클래스나 라이브러리 클래스와의 충돌을 피할수 있다. 이것이 가능한 이유는 클래스의 실제 이름은 패키지명을 포함한 것이기 때문이다. 파일시스템의 파일명과 동일한 매커니즘이다.
모든 파일은 디렉터리 내에 존재하는 것과 동일하게, 클래스도 패키지 내에 저장되어야한다. 그런데 소스파일을 작성할때 패키지내에 저장 및 선언하지 않고도 문제가 발생하지 않았던 것은 'unnamed package' 때문이다. 소스파일에 파일이 속할 패키지를 지정하지 않은 클래스는 자동적으로 unnamed package 패키지에 모두 속하게 된다.
패키지는 첫번째 문장에 선언해야하며, 하나의 소스파일에 단 한번만 선언될 수 있다. 패키지명은 해당 클래스의 경로를 나타내기 때문에 한번만 선언이 가능한 것이다. 그리고 클래스명과 구분하기 위해 소문자로 이름을 짓는다.
소스코드 실행 환경 설정
소스코드 컴파일 후 특정 경로에 저장
작성한 소스코드를 실행하기 위하여, 특정 디렉터리 경로에 컴파일한 클래스 파일(.class)로 저장해야한다.
package com.shop.book
class Test {
public static void main(String[] args) {
System.out.println("Test");
}
}
java compiler로 -d 옵션을 통해 지정한 패키지 경로를 찾아, 소스파일을 컴파일하여 클래스 파일을 생성하는 명령어이다. 만일, 기존의 디렉토리가 존재하지 않으면 디렉토리 생성을 해준다. -d옵션 뒤에는 해당 패키지의 루트 디렉터리의 경로를 지정한다.
javac -d [root_directory_of_package] [source_file_name].java
아래와 같이 현재 디렉토리 표현인 .으로 설정하면, 해당 예시의 현재 경로는 /usr/lib/jvm/java-21-openjdk-amd64이다. 패키지의 루트 디렉터리의 경로를 /usr/lib/jvm/java-21-openjdk-amd64로 지정하는 것이다.
root@ubuntuserver:/usr/lib/jvm/java-21-openjdk-amd64# javac -d . [source_file_name.java]
그렇다면 컴파일된 클래스 파일이 저장 경로는, 패키지의 루트 디렉터리의 경로(현재 디렉터리 경로))에서 패키지 경로(package com.shop.book)까지 반영되어 /usr/lib/jvm/java-21-openjdk-amd64/com/shop/book 디렉터리에 Test.class가 저장된다.
package com.shop.book
class Test {
public static void main(String[] args) {
System.out.println("Test");
}
}
import문이란?
소스코드를 작성할 때 다른 패키지의 클래스를 사용할려면, 패키지명이 포함된 클래스 이름을 사용해야한다. 하지만 패키지 이름을 모두 붙일수는 없으니, import문으로 사용하고자 하는 클래스의 패키지를 명시해주면 소스코드에서 클래스의 패키지명은 생략할 수 있다.
왜냐하면 컴파일러가 컴파일시에 import문을 통해 패키지에 대한 정보를 알아내서, 모든 클래스명 앞에 패키지명을 붙여주기 때문이다. 이 작업은 컴파일 타임에 수행되는 작업이므로, 성능에는 영향을 미치지 않는다.
import문의 선언
import문은 다른 패키지를 가리키므로 package문과 다르게 여러번 선언이 가능하다. 한 패키지에서 여러 클래스를 사용하는 경우에 일일히 클래스를 import는 하는 것보다, 패키지명.*으로 import하는 것이 더 편리하다. 하지만 참조할 패키지 수가 많은 경우에는 어느 클래스가 어느 패키지에 속하는지 구별하기 어려워 유의가 필요하다.
*를 사용하면, 여러 클래스중에서 일치하는 클래스명을 컴파일러가 검색을 해야되지만, 컴파일 타임에 수행되는 작업이므로 성능상 차이는 없다.
String 같은 java.lang 패키지의 클래스를 패키지명 없이 사용할 수 있던 것은, 묵시적으로 import java.lang.* 이 선언되있었기 때문이다. 이는 Java API로 애플리케이션 개발에 필수적인 패키지기 때문에, java 컴파일러가 자동으로 포함한다.
만일, import문으로 패키지를 지정하지 않으면, 소스코드에서 String클래스를 사용할때도 fullname으로 작성해야한다.
java.lang.String str = new java.lang.String();
static import문
import문을 사용하면 클래스의 패키지명을 생략할수 있는 것처럼, static import문을 사용하면 static 멤버를 호출할때 클래스명을 생략할 수 있다. 그리하면 편리해지고 아래처럼 코드도 간결해진다.
import static java.lang.Math.random;
// 기존
System.out.println(Math.random());
// static import 선언
out.println(random());
챰고 자료
- 자바의 정석 (남궁성 지음)
'Java > Java Language' 카테고리의 다른 글
| [Java] 다형성 (Polymorphism) (0) | 2024.06.22 |
|---|---|
| [Java] Modifier (Access Modifier와 Encapsulation) (0) | 2024.06.20 |
| [Java] 오버라이딩 (Overriding) (0) | 2024.06.18 |
| [Java] 상속 (Inheritance) (0) | 2024.06.18 |
| [Java] 변수의 초기화 (0) | 2024.06.13 |