패키지 이해하기
프로그래머는 타입을 더 쉽게 찾고 사용하고, 이름 충돌을 피하고, 액세스를 제어하기 위해 관련 타입 그룹을 패키지로 묶습니다.
Definition: 패키지는 액세스 보호 및 네임스페이스 관리를 제공하는 관련 타입의 그룹입니다. 타입은 클래스, 인터페이스, 열거형, 어노테이션 타입을 의미합니다. 열거형과 어노테이션 타입은 각각 클래스와 인터페이스의 특수한 종류이므로 이 섹션에서는 타입을 단순히 클래스와 인터페이스라고 부르는 경우가 많습니다.
Java 플랫폼의 일부인 타입은 기능별로 클래스를 묶은 다양한 패키지의 멤버로, 기본 클래스는 java.lang, 읽기 및 쓰기(입력 및 출력)용 클래스는 java.io 등에 있습니다. 타입을 패키지에 넣을 수도 있습니다.
원, 직사각형, 선, 점과 같은 그래픽 객체를 나타내는 클래스 그룹을 작성한다고 가정해 보겠습니다. 또한 마우스로 드래그할 수 있는 경우 클래스가 구현하는 인터페이스인 Draggable을 작성합니다.
//in the Draggable.java file
public interface Draggable {
...
}
//in the Graphic.java file
public abstract class Graphic {
...
}
//in the Circle.java file
public class Circle extends Graphic
implements Draggable {
. . .
}
//in the Rectangle.java file
public class Rectangle extends Graphic
implements Draggable {
. . .
}
//in the Point.java file
public class Point extends Graphic
implements Draggable {
. . .
}
//in the Line.java file
public class Line extends Graphic
implements Draggable {
. . .
}다음과 같은 여러 가지 이유로 이러한 클래스와 인터페이스를 패키지로 묶어야 합니다:
- 사용자와 다른 프로그래머는 이러한 타입이 서로 관련되어 있음을 쉽게 파악할 수 있습니다.
- 사용자와 다른 프로그래머는 그래픽 관련 기능을 제공할 수 있는 타입을 어디서 찾을 수 있는지 알 수 있습니다.
- 패키지가 새 네임스페이스를 생성하기 때문에 타입의 이름이 다른 패키지의 타입 이름과 충돌하지 않습니다.
- 패키지 내의 타입은 서로 제한 없이 액세스할 수 있도록 허용하면서도 패키지 외부의 타입에 대한 액세스는 제한할 수 있습니다.
패키지 만들기
패키지를 만들려면 패키지 이름을 선택하고(명명 규칙은 다음 섹션에서 설명합니다) 패키지에 포함하려는 타입(클래스, 인터페이스, 열거형 및 어노테이션 타입)이 포함된 모든 소스 파일의 맨 위에 해당 이름을 가진 패키지 문을 넣습니다.
패키지 문(예: package graphics;)은 소스 파일의 첫 번째 줄에 위치해야 합니다. 각 소스 파일에는 패키지 문이 하나만 있을 수 있으며, 파일의 모든 타입에 적용됩니다.
Note: 하나의 소스 파일에 여러 타입을 넣는 경우 하나만 public이 될 수 있으며, 소스 파일과 이름이 같아야 합니다. 예를 들어,
Circle.java파일에public class Circle을 정의하고,Draggable.java파일에public interface Draggable을 정의하고,Day.java파일에public enum Day를 정의하는 등의 방식으로 정의할 수 있습니다.non-public 타입을 public 타입과 같은 파일에 포함할 수 있지만(non-public 타입이 작고 public 타입과 밀접한 관련이 없는 한 권장하지 않음), 패키지 외부에서는 public 타입만 액세스할 수 있습니다. 최상위 레벨의 모든 non-public 타입은 package private이 됩니다.
앞 섹션에 나열된 그래픽 인터페이스와 클래스를 graphics라는 패키지에 넣는다면 다음과 같이 6개의 소스 파일이 필요합니다:
//in the Draggable.java file
package graphics;
public interface Draggable {
. . .
}
//in the Graphic.java file
package graphics;
public abstract class Graphic {
. . .
}
//in the Circle.java file
package graphics;
public class Circle extends Graphic
implements Draggable {
. . .
}
//in the Rectangle.java file
package graphics;
public class Rectangle extends Graphic
implements Draggable {
. . .
}
//in the Point.java file
package graphics;
public class Point extends Graphic
implements Draggable {
. . .
}
//in the Line.java file
package graphics;
public class Line extends Graphic
implements Draggable {
. . .
}package문을 사용하지 않으면 타입은 이름 없는 패키지로 끝납니다. 일반적으로 이름 없는 패키지는 소규모 또는 임시 애플리케이션이나 개발 프로세스를 막 시작할 때만 사용됩니다. 그렇지 않으면 클래스와 인터페이스는 명명된 패키지에 속합니다.
패키지 이름 지정 및 이름 지정 규칙
전 세계 프로그래머들이 Java 프로그래밍 언어를 사용하여 클래스와 인터페이스를 작성하기 때문에 많은 프로그래머들이 서로 다른 타입에 동일한 이름을 사용할 가능성이 높습니다. 실제로 앞의 예제가 바로 그런 경우입니다: 이 예제는 java.awt 패키지에 이미 Rectangle 클래스가 있는데도 Rectangle 클래스를 정의합니다. 하지만 컴파일러는 두 클래스가 서로 다른 패키지에 있는 경우 동일한 이름을 가질 수 있도록 허용합니다. 각 Rectangle 클래스의 정규화된 이름에는 패키지 이름이 포함됩니다. 즉, graphics 패키지에 있는 Rectangle 클래스의 정규화된 이름은 graphics.Rectangle이고, java.awt 패키지에 있는 Rectangle 클래스의 정규화된 이름은 java.awt.Rectangle입니다.
두 명의 독립 프로그래머가 패키지에 같은 이름을 사용하지 않는 한 이 방법은 잘 작동합니다. 이 문제를 방지하는 방법은 무엇일까요? 규칙입니다.
패키지 이름은 클래스나 인터페이스 이름과의 충돌을 피하기 위해 모두 소문자로 작성됩니다.
예를 들어, example.com의 프로그래머가 만든 mypackage라는 패키지의 경우 com.example.mypackage로 시작하는 것과 같이 회사는 인터넷 도메인 이름을 거꾸로 사용하여 패키지 이름을 시작합니다.
한 회사 내에서 발생하는 이름 충돌은 회사 이름 뒤에 지역 또는 프로젝트 이름을 포함하는 등 해당 회사 내 관례에 따라 처리해야 합니다(예: com.example.region.mypackage).
Java 언어 자체의 패키지는 java. 또는 javax.로 시작합니다.
경우에 따라 인터넷 도메인 이름이 유효한 패키지 이름이 아닐 수도 있습니다. 도메인 이름에 하이픈이나 기타 특수 문자가 포함된 경우, 패키지 이름이 숫자나 기타 Java 이름의 시작 부분으로 사용하기에 부적절한 문자로 시작하는 경우, 패키지 이름에 int와 같은 예약된 Java 키워드가 포함된 경우 이런 경우가 발생할 수 있습니다. 이 경우 밑줄을 추가하는 것이 좋습니다. 예를 들어
| Domain Name | Package Name Prefix |
|---|---|
hyphenated-name.example.org | org.example.hyphenated_name |
example.int | int_.example |
123name.example.com | com.example._123name |
패키지 멤버 사용
패키지를 구성하는 타입을 패키지 멤버라고 합니다.
패키지 외부에서 public 패키지 멤버를 사용하려면 다음 중 하나를 수행해야 합니다:
- 정규화된 이름으로 멤버를 참조합니다.
- 패키지 멤버 가져오기
- 멤버의 전체 패키지 가져오기
다음 섹션에서 설명하는 대로 각각 다른 상황에 적합합니다.
정규화된 이름으로 패키지 멤버 참조하기
지금까지 이 튜토리얼의 대부분의 예제에서는 Rectangle, StackOfInts와 같은 간단한 이름으로 타입을 참조했습니다. 작성 중인 코드가 해당 멤버와 동일한 패키지에 있거나 해당 멤버를 임포트한 경우 패키지 멤버의 간단한 이름을 사용할 수 있습니다.
그러나 다른 패키지의 멤버를 사용하려는데 해당 패키지를 가져오지 않은 경우에는 패키지 이름이 포함된 멤버의 정규화된 이름을 사용해야 합니다. 다음은 이전 예제에서 graphics 패키지에 선언된 Rectangle 클래스의 정규화된 이름입니다.
graphics.Rectangle이 한정된 이름을 사용하여 graphics.Rectangle의 인스턴스를 만들 수 있습니다:
graphics.Rectangle myRect = new graphics.Rectangle();한정된 이름은 자주 사용하지 않을 때는 괜찮습니다. 그러나 이름을 반복적으로 사용하는 경우 이름을 반복해서 입력하는 것이 지루해지고 코드를 읽기 어려워집니다. 대안으로 멤버 또는 해당 패키지를 가져온 다음 간단한 이름을 사용할 수 있습니다.
패키지 멤버 가져오기
특정 멤버를 현재 파일로 가져오려면 파일 시작 부분에 타입 정의가 있는 경우 package 문 뒤에 import 문을 넣습니다. 다음은 이전 섹션에서 만든 graphics 패키지에서 Rectangle 클래스를 임포트하는 방법입니다.
import graphics.Rectangle;이제 Rectangle 클래스를 간단한 이름으로 참조할 수 있습니다.
Rectangle myRectangle = new Rectangle();이 방법은 graphics 패키지에서 몇 가지 멤버만 사용하는 경우 잘 작동합니다. 그러나 한 패키지의 많은 타입을 사용하는 경우 전체 패키지를 가져와야 합니다.
전체 패키지 가져오기
특정 패키지에 포함된 모든 타입을 가져오려면 별표(*) 와일드카드 문자와 함께 가져오기 문을 사용하세요.
import graphics.*;이제 graphics 패키지의 모든 클래스나 인터페이스를 간단한 이름으로 참조할 수 있습니다.
Circle myCircle = new Circle();
Rectangle myRectangle = new Rectangle();가져오기 문에서 별표는 여기에 표시된 것처럼 패키지 내의 모든 클래스를 지정하는 데만 사용할 수 있습니다. 패키지에 있는 클래스의 하위 집합을 일치시키는 데는 사용할 수 없습니다. 예를 들어 다음은 그래픽 패키지에서 A로 시작하는 모든 클래스와 일치하지 않습니다.
// does not work
import graphics.A*;대신 컴파일러 오류가 발생합니다. import 문을 사용하면 일반적으로 단일 패키지 멤버 또는 전체 패키지만 가져옵니다.
Note: 덜 일반적인 또 다른 형태의 import를 사용하면 둘러싸는 클래스의 public 중첩 클래스를 임포트할 수 있습니다. 예를 들어,
graphics.Rectangle클래스에Rectangle.DoubleWide및Rectangle.Square와 같은 유용한 중첩 클래스가 포함된 경우 다음 두 문을 사용하여Rectangle과 그 중첩 클래스를 가져올 수 있습니다.
import graphics.Rectangle;
import graphics.Rectangle.*;두 번째 가져오기 문은
Rectangle을 가져오지 않는다는 점에 유의하세요.덜 일반적인 또 다른 가져오기 형식인 static import 문은 이 섹션의 마지막 부분에서 설명합니다.
편의를 위해 Java 컴파일러는 각 소스 파일에 대해 두 개의 전체 패키지를 자동으로 가져옵니다:
java.lang패키지와- 현재 패키지(현재 파일에 대한 패키지).
패키지의 겉으로 드러나는 계층 구조
처음에는 패키지가 계층 구조로 보이지만 실제로는 그렇지 않습니다. 예를 들어, Java API에는 java.awt 패키지, java.awt.color 패키지, java.awt.font 패키지 및 java.awt로 시작하는 다른 많은 패키지가 포함되어 있습니다. 그러나 java.awt.color 패키지, java.awt.font 패키지 및 기타 java.awt.xxxx 패키지는 java.awt 패키지에 포함되지 않습니다. 접두사 java.awt(자바 추상 창 툴킷)는 여러 관련 패키지에 사용되어 관계를 명확히 하지만 포함 여부를 표시하지는 않습니다.
java.awt.*를 가져오면 java.awt 패키지의 모든 타입을 가져오지만, java.awt.color, java.awt.font 또는 기타 java.awt.xxxx 패키지는 가져오지 않습니다. java.awt.color의 클래스 및 기타 타입과 java.awt의 타입을 사용하려는 경우 모든 파일과 함께 두 패키지를 모두 가져와야 합니다:
import java.awt.*;
import java.awt.color.*;이름 모호성
한 패키지의 멤버가 다른 패키지의 멤버와 이름을 공유하고 두 패키지를 모두 가져오는 경우, 각 멤버를 정규화된 이름으로 참조해야 합니다. 예를 들어 그래픽 패키지는 Rectangle이라는 클래스를 정의했습니다. java.awt 패키지에도 Rectangle 클래스가 포함되어 있습니다. graphics와 java.awt를 모두 가져온 경우 다음이 모호합니다.
Rectangle rect;이러한 상황에서는 멤버의 정규화된 이름을 사용하여 원하는 Rectangle 클래스를 정확하게 표시해야 합니다. 예를 들어
graphics.Rectangle rect;정적 임포트 문
하나 또는 두 개의 클래스에서 정적 최종 필드(상수)와 정적 메서드에 자주 액세스해야 하는 상황이 있습니다. 이러한 클래스의 이름 앞에 접두사를 계속 붙이면 코드가 복잡해질 수 있습니다. 정적 import 문은 사용하려는 상수와 정적 메서드를 임포트하는 방법을 제공하여 클래스 이름 앞에 접두사를 붙일 필요가 없도록 합니다.
java.lang.Math 클래스는 PI 상수와 사인, 코사인, 접선, 제곱근, 최대치, 최소치, 지수 등을 계산하는 메서드를 포함한 많은 정적 메서드를 정의합니다. 예를 들어
public static final double PI = 3.141592653589793;
public static double cos(double a) {
...
}일반적으로 다른 클래스의 이러한 객체를 사용하려면 다음과 같이 클래스 이름 앞에 접두사를 붙입니다.
double r = Math.cos(Math.PI * theta);static import 문을 사용하면 클래스 이름 앞에 Math라는 접두사를 붙이지 않고도 java.lang.Math의 정적 멤버를 가져올 수 있습니다. Math의 정적 멤버는 개별적으로 임포트할 수 있습니다:
import static java.lang.Math.PI;또는 그룹으로도 임포트할 수 있습니다:
import static java.lang.Math.*;일단 가져온 정적 멤버는 자격 조건 없이 사용할 수 있습니다. 예를 들어 이전 코드 스니펫은 다음과 같이 됩니다:
double r = Math.cos(PI * theta);물론 자주 사용하는 상수와 정적 메서드가 포함된 클래스를 직접 작성한 다음 정적 임포트 문을 사용할 수 있습니다. 예를 들어
import static mypackage.MyConstants.*;Note: 정적 가져오기는 매우 드물게 사용하세요. 정적 가져오기를 과도하게 사용하면 코드를 읽는 사람이 특정 정적 객체를 정의하는 클래스를 알 수 없으므로 코드를 읽고 유지 관리하기 어려운 코드가 될 수 있습니다. 정적 임포트를 올바르게 사용하면 클래스 이름 반복을 제거하여 코드를 더 읽기 쉽게 만들 수 있습니다.
패키지 마무리하기
타입에 대한 패키지를 만들려면 해당 타입(클래스, 인터페이스, 열거형 또는 어노테이션 타입)이 포함된 소스 파일의 첫 번째 문으로 package 문을 넣습니다.
다른 패키지에 있는 public 타입을 사용하려면 다음 세 가지를 선택할 수 있습니다:
- 타입의 정규화된 이름을 사용합니다,
- 타입을 가져오거나
- 해당 타입이 속한 전체 패키지를 가져오기.
패키지의 소스 및 클래스 파일의 경로 이름은 패키지 이름을 반영합니다.
컴파일러와 JVM이 타입에 대한 ‘.class’ 파일을 찾을 수 있도록 CLASSPATH를 설정해야 할 수도 있습니다.