패키지는 서로 연관된 타입들에 대해서 그룹화하여 접근을 제어하고 네임스페이스를 관리하도록 한다. 이때 타입들은 클래스, 인터페이스, ENUM, 어노테이션을 의미한다.
패키지를 사용하므로서, 클래스 이름이 중복되어 발생하는 문제를 방지할 수 있으며, 다른 패키지에서 해당 패키지에 요소들에 대해 접근하는 것을 제어할 수 있다.
패키지에 넣고 싶은 소스 파일의 가장 상단에 “package” 키워드를 사용해 패키지를 명시한다. 패키지 선언문은 반드시 소스파일의 첫번째 줄에 있어야 하며, 소스파일에는 오직 하나의 패키지 선언문만 존재할 수 있다. 즉, 하나의 소스파일이 여러 패키지에 속해있는 것은 불가능하다.
다만 하나의 소스파일에 public type의 요소와 non-public type의 요소가 동시에 존재할 수 있다. 이 경우는 non-public type의 요소가 매우 작고 public type과 매우 연관되있지 않은 경우 권장되지 않는다. 만약 “public class”가 아닌 “class” 키워드를 사용하면 ,해당 클래스는 package private이 된다. 이는 해당 패키지 내부에서는 접근이 가능하지만, 패키지 외부에서는 접근이 제한되는 접근 제어자이다.
패키지 선언문을 사용하지 않을 경우, 해당 요소는 unnamed package에 속하게 된다. unnamed 패키지는 작거나 혹은 잠깐만 사용할 어플리케이션이거나 개발 프로세스를 처음 시작하는 단계에서만 사용되어야 한다. 위 상황이 아니라면, 클래스나 인터페이스는 named package에 속해야 한다.
// in Test.java file
package test;
public interface Test{
}
// in SubTest.java file
package test;
public class SubTest implements Test{
}
자바 개발자들 간에 클래스의 fully qualified name이 중복되지 않도록 패키지 네이밍 컨벤션을 지켜야 한다. 패키지 이름은 클래스나 인터페이스와의 충돌을 방지하기 위해 소문자로 작성되어야 한다.
일반적으로 회사에서는 인터넷 도메인 주소를 뒤집은 형태를 패키지 이름의 시작부분으로 한다. 예를 들어 example.com 이라는 도메인을 가진 회사에서는, package 이름은 com.example.* 로 작성할 것이다. 하나의 회사에서 이름 충돌을 방지하기 위해 지역명이나 다른 요소들을 패키지명에 추가할 수 있다.
몇몇 경우, 인터넷 도메인 주소가 패키지 네임에 적합하지 않을 수 있다. 예를 들어 도메인 주소에 “-”이 포함되거나 다른 특별한 문자가 포함되있을 수 있다. 패키지 이름의 시작 부분에 “숫자” 혹은 “int”와 같은 자바 키워드가 있는 것은 금지된다. 따라서 이를 방지하기 위해 아래와 같이 수정하여 패키지 이름에 적용한다.
hyphenated-name.example.org ⇒ org.example.hyphenated_name
example.int ⇒ int_.example
123name.example.com ⇒ com.example._123name
패키지에 담겨있는 요소들을 패키지 멤버라고 부른다. public한 패키지 멤버를 패키지 밖에서 사용하기 위해 다음 3가지 방법을 사용한다.
주의사항! import는 fully qualified name을 사용하지 않고, 간략하게 패키지 멤버를 사용하기 위함이다. 즉, import
키워드가 패키지 멤버를 가져오는 개념이 아니다. 패키지 멤버를 실제 가져오도록 돕는 키워드는 classpath
이다.
각각은 상황에 따라 적절한 방법들이다.
사용하고자 하는 패키지 멤버가 import 되있지 않을 경우, 다른 패키지에서 해당 패키지 멤버를 사용하고자 한다면 fully qualified name을 참조해야 한다.
com.example.test.TestClass testClass = new com.example.test.TestClass();
import 키워드는 pacakge 키워드 이후와 패키지 멤버의 선언 이전에 명시되어야 한다.
package com.example.test2;
import com.example.test.TestClass;
TestClass testClass = new TestClass();
asterisk(*)를 사용하여 패키지 전체를 import할 수 있다. 이 경우, 해당 패키지에 속해있는 모든 요소들을 사용할 수 있다. 그러나 클래스의 subset은 사용할 수 없다. 다시 말해, 클래스 내부에 선언된 중첩 클래스는 사용할 수 없다.
이를 사용하기 위해서는 다음과 같이 import 해야한다.
import graphics.Rectangle;
import graphics.Rectangle.*;
위 선언을 통해 Rectangle과 Rectangle 내부에 있는 중첩 클래스를 모두 사용할 수 있다. 다만, 하단의 import문만 선언될 경우 Rectangle클래스는 사용할 수 없다. 명시적으로 선언해줘야 한다.
편의를 위해서 자바 컴파일러는 자동으로 java.lang 패키지와 현재 소스파일이 속해있는 패키지를 import해준다.
패키지명을 처음 보면, 계층적인 구조를 갖는 것으로 보인다. 하지만, 실제로 그렇지는 않다. Java API에 속해있는 java.awt 패키지를 예로 들겠다. “java.awt.color”, “java.awt.font”는 마치 “java.awt”에 속해있는 패키지처럼 보이지만, 해당 패키지들은 “java.awt”에 속해있지 않다. “java.awt”는 단지 여러 종류의 관련있는 패키지들의 관계를 명백하게 나타낼 뿐이다.
만약 “import java.awt.*”를 선언하면, “java.awt.xxx”에 해당하는 패키지들이 모두 import 될 것처럼 보이지만, “java.awt”에 속한 패키지 멤버들만 import 될 것이다. 따라서, “java.awt”와 “java.awt.xxx”를 모두 import 하려면 다음과 같이 선언해야 한다.
import java.awt.*;
import java.awt.color.*;
서로 다른 패키지에서 이름이 같은 패키지 멤버가 있다고 가정하자. 만약 두 패키지를 모두 import하고 특정 패키지 멤버를 사용하고자 한다면, 해당 패키지 멤버의 fully qualified name을 명시적으로 참조해야 한다. 예를 들어, “graphics.Rectangle”과 “java.awt.Rectangle”이 존재한다고 할 때 Rectangle 클래스를 사용하려면 다음과 같이 명시해야한다.
import graphics.Rectangle;
import java.awt.Rectangle;
graphics.Rectangle rect;
패키지에 담긴 자바 파일은 실제 파일 시스템에 적재될 때 subdirectory들의 series형태로 존재하게 된다. 즉, com.example.graphics.Rectangle.java
와 같은 패키지 멤버가 존재할 때, 해당 멤버는 \com\example\graphics\Rectangle.java
로 파일 시스템에 존재하게 된다.
소스파일을 컴파일 하면, 컴파일러는 패키지에 정의된 패키지 멤버들을 .class
파일 형태로 변환 및 생성하며 해당 클래스 파일들은 패키지 구조와 동일한 구조로 디렉토리에 저장된다. 기본적으로 컴파일하면 클래스 파일은 자바 소스파일과 동일한 디렉토리에 저장된다. 하지만 반드시 그래야 하는 것은 아니며, 소스 파일과 클래스 파일의 디렉토리를 분리할 수 있다.
이렇게 함으로써 개발자는 소스파일을 다른 개발자에게 드러내지 않고, 클래스 파일들만 공유할 수 있다. 또한, 컴파일러와 JVM은 프로그램에서 사용하는 패키지 멤버들을 찾을 수 있게 된다. 이를 위해 사용하는 것이 클래스 패스이다. 클래스 패스는 클래스 파일들이 존재하는 디렉토리의 절대경로를 정의한 것이다. 컴파일러와 JVM은 클래스 패스에 클래스 파일을 생성 및 찾기를 수행한다.
예를 들어, <path>\classes
가 클래스 패스이며, com.example.graphics
가 패키지 이름이라면, 컴파일러는 패키지 멤버의 클래스 파일을 생성할 때 <path>\classes\com\example\graphics\xxx.class
경로에 파일을 생성할 것이다.
단, 패키지의 구조대로 클래스 파일을 sub directory에 생성하는 것은 "-classpath" 옵션이 아닌 "-d" 옵션을 통해서 이뤄지는 것이다. "-d"를 통해 컴파일하면 패키지 구조에 맞춰 자동으로 디렉토리를 생성한 이후에 해당 디렉토리 구조 안에 클래스 파일을 생성한다.
classpath를 지정하기 위해 위와 같이 시스템 환경변수를 설정하는 방법 외에 “-classpath” 옵션을 사용할 수 있다. 해당 옵션은 “java”, “javac” 명령어에서 모두 사용된다.
설명을 위해 다음 Main.java
, TestClass.java
를 생성한다.
class Main{
public static void main(String[] args) {
TestClass testClass = new TestClass();
testClass.testMethod();
}
}
class TestClass{
void testMethod(){
System.out.println("test method call!");
}
}
-classpath
옵션 없이도 컴파일이 가능하다. 또한, Main.java
를 컴파일하면 Main
에서 사용하는 TestClass
를 사용하기 위해 TestClass.class
를 자동으로 생성한다.-classpath
가 힘을 발휘한다. javac
에서의 -classpath
는 컴파일한 소스파일에서 사용하는 클래스 파일들이 어디에 있는지 알려준다. 만약, 컴파일되지 않은 .java
형식이라면 해당 파일을 자동으로 컴파일 해준다.classpath
를 지정하지 않으면?jvm
은 해당 클래스를 찾지 못한다. 이러한 이유로 classpath
를 사용하는 것이다.-classpath
에 함께 명시해줘야 한다. 즉, 실행시킬 클래스 파일 경로
와 참조되는 클래스 파일 경로
를 모두 -classpath
에 명시해줘야한다.(윈도우 환경에서는 ;
를 사용하여 여러 클래스패스를 지정할 수 있다.)패키지를 생성하는 방법
에서 설명하여 생략한다.
참고자료
- http://www.cas.mcmaster.ca/~lis3/javatutorial/Classpath.htm
- https://stackoverflow.com/questions/25116532/does-the-javac-command-automatically-create-directories-specified-in-the-package
- http://www.cas.mcmaster.ca/~lis3/javatutorial/Classpath.htm
- https://docs.oracle.com/javase/tutorial/java/package/packages.html