Lesson: Packages

2024. 6. 7. 17:12High Level Programming Language/Learning the Java Language

이 레슨에서는 클래스와 인터페이스를 패키지로 묶는 방법, 패키지에 있는 클래스를 사용하는 방법, 컴파일러가 소스 파일을 찾을 수 있도록 파일 시스템을 정렬하는 방법을 설명합니다.

 

Creating and Using Packages

타입을 더 쉽게 찾고 사용할 수 있도록 하고, 이름 충돌을 방지하고, 액세스를 제어하기 위해 프로그래머는 관련 타입 그룹을 패키지로 묶습니다.


정의: 패키지는 관련된 타입을 그룹화하여 접근 보호와 이름 공간 관리를 제공하는 것입니다. 여기서 타입은 클래스, 인터페이스, 열거형, 어노테이션 타입을 의미합니다. 열거형과 어노테이션 타입은 각각 클래스와 인터페이스의 특수한 종류이므로, 이 레슨에서는 타입을 단순히 클래스와 인터페이스로 언급하는 경우가 많습니다.

 


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 {
    . . .
}

 

다음 사항들을 포함한 여러 가지 이유로 이러한 클래스와 인터페이스를 패키지로 묶어야 합니다.

  • 여러분과 다른 프로그래머는 이러한 타입이 서로 관련되어 있음을 쉽게 확인할 수 있습니다.
  • 여러분과 다른 프로그래머들은 그래픽 관련 기능을 제공할 수 있는 타입을 어디서 찾을 수 있는지 알고 있습니다.
  • 패키지가 새 네임스페이스를 생성하므로 타입 이름이 다른 패키지의 타입 이름과 충돌하지 않습니다.
  • 패키지 내의 타입들이 서로에게 무제한 접근할 수 있도록 허용하면서도, 패키지 외부의 타입들에 대한 접근은 제한할 수 있습니다.

 

Creating a Package

패키지를 생성하려면 패키지 이름을 선택하고 (이름 지정 규칙은 다음 섹션에서 논의됩니다) 해당 이름으로 패키지 statemnet을 패키지에 포함하려는 타입(클래스, 인터페이스, 열거형, 어노테이션 타입)을 포함하는 모든 소스 파일의 맨 위에 작성합니다.

 

패키지 statement(예: package graphics;)은 소스 파일의 첫 번째 줄에 위치해야 합니다. 각 소스 파일에는 하나의 패키지 statement만 있을 수 있으며, 이는 파일 내의 모든 타입에 적용됩니다.


참고: 단일 소스 파일에 여러 타입을 넣는 경우, 하나만 public으로 할 수 있으며, 그 이름은 소스 파일과 동일해야 합니다. 예를 들어, Circle.java 파일에 public class Circle을 정의하고, Draggable.java 파일에 public interface Draggable을 정의하고, Day.java 파일에 public enum Day를 정의할 수 있습니다.

동일 파일에 public 타입과 non-public 타입을 포함할 수 있지만 (non-public 타입이 작고 public 타입과 밀접하게 관련이 있는 경우가 아니면 권장되지 않습니다), public 타입만 패키지 외부에서 접근할 수 있습니다. 모든 최상위 non-public 타입은 패키지 전용입니다.


이전 섹션에 나열된 그래픽 인터페이스와 클래스를 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 statement를 사용하지 않으면 타입은 이름이 없는 패키지가 됩니다. 일반적으로 이름이 없는 패키지는 소규모 또는 임시 애플리케이션용이거나 개발 프로세스를 막 시작한 경우에만 사용됩니다. 이런 경우를 제외하면, 일반적으로 클래스와 인터페이스는 이름이 있는  패키지에 속해야 합니다.

 

Naming a Package

전 세계의 프로그래머들이 자바 프로그래밍 언어를 사용하여 클래스와 인터페이스를 작성하기 때문에, 많은 프로그래머들이 다른 타입에 대해 동일한 이름을 사용할 가능성이 높습니다. 실제로, 이전 예제에서 이미 java.awt 패키지에 Rectangle 클래스가 있음에도 불구하고 Rectangle 클래스를 정의합니다. 그래도 컴파일러는 이름이 동일한 클래스가 다른 패키지에 있는 경우 두 클래스가 동일한 이름을 가지는 것을 허용합니다. 각 Rectangle 클래스의 완전한 이름(fully qualified name)에는 패키지 이름이 포함됩니다. 즉, graphics 패키지의 Rectangle 클래스의 완전한 이름은 graphics.Rectangle이고, java.awt 패키지의 Rectangle 클래스의 완전한 이름은 java.awt.Rectangle입니다.

 

이는 두 명의 독립적인 프로그래머가 동일한 이름을 패키지에 사용하는 경우를 제외하면 잘 작동합니다. 이 문제를 방지하는 것은 관례입니다.

 

Naming Conventions

패키지 이름은 클래스나 인터페이스 이름과의 충돌을 피하기 위해 모두 소문자로 작성됩니다.

회사는 역순의 인터넷 도메인 이름을 사용하여 패키지 이름을 시작합니다. 예를 들어, example.com의 프로그래머가 생성한 mypackage라는 패키지는 com.example.mypackage가 됩니다.

한 회사 내에서 발생하는 이름 충돌은 해당 회사 내의 관례에 따라 처리해야 하며, 예를 들어 회사 이름 뒤에 지역 또는 프로젝트 이름을 포함할 수 있습니다(예: com.example.region.mypackage).

자바 언어 자체의 패키지는 java. 또는 javax.로 시작합니다.

경우에 따라 인터넷 도메인 이름이 유효한 패키지 이름이 아닐 수 있습니다. 이는 도메인 이름에 하이픈이나 다른 특수 문자가 포함된 경우, 패키지 이름이 숫자 또는 자바 이름의 시작으로 사용할 수 없는 문자로 시작하는 경우, 또는 패키지 이름에 "int"와 같은 자바 예약어가 포함된 경우 발생할 수 있습니다. 이 경우, 제안된 관례는 밑줄을 추가하는 것입니다. 예를 들어:

Legalizing Package Names

Domain Name Package Name Prefix
hyphenated-name.example.org org.example.hyphenated_name
example.int int_.example
123name.example.com com.example._123name

 

Using Package Members

패키지를 구성하는 타입은 패키지 멤버로 알려져 있습니다.

패키지 외부에서 공용 패키지 멤버를 사용하려면 다음 중 하나를 수행해야 합니다:

 멤버를 완전한 이름으로 참조
 패키지 멤버를 임포트
 멤버의 전체 패키지를 임포트

각 방법은 다음 섹션에서 설명하는 것처럼 다양한 상황에 적합합니다.

 

Referring to a Package Member by Its Qualified Name

지금까지 이 튜토리얼의 대부분 예제는 Rectangle과 StackOfInts와 같이 타입을 단순한 이름으로 참조했습니다. 코드가 해당 멤버와 같은 패키지에 있거나 그 멤버가 임포트된 경우 패키지 멤버의 단순한 이름을 사용할 수 있습니다.

그러나 다른 패키지의 멤버를 사용하려고 하고 그 패키지가 임포트되지 않은 경우에는 패키지 이름을 포함한 멤버의 완전한 이름을 사용해야 합니다. 다음은 이전 예제에서 graphics 패키지에 선언된 Rectangle 클래스의 완전한 이름입니다.

graphics.Rectangle


이 완전한 이름을 사용하여 graphics.Rectangle의 인스턴스를 생성할 수 있습니다:

graphics.Rectangle myRect = new graphics.Rectangle();


완전한 이름은 가끔 사용하는 경우에는 괜찮습니다. 그러나 이름을 반복적으로 사용할 때는 완전한 이름을 반복해서 입력하는 것이 번거롭고 코드가 읽기 어려워집니다. 대신, 멤버나 그 패키지를 임포트한 후 단순한 이름을 사용할 수 있습니다.

 

Importing an Entire Package

특정 패키지에 포함된 모든 타입을 임포트하려면, 와일드카드 문자 별표(*)를 사용하여 import 문을 작성합니다.

import graphics.*;

 

이제 graphics 패키지의 클래스나 인터페이스를 단순한 이름으로 참조할 수 있습니다.

Circle myCircle = new Circle();
Rectangle myRectangle = new Rectangle();


import statement에서 별표는 패키지 내의 모든 클래스를 지정하는 데만 사용할 수 있습니다. 패키지 내의 일부 클래스에만 일치하도록 사용할 수는 없습니다. 예를 들어, 다음과 같은 경우는 그래픽스 패키지의 A로 시작하는 모든 클래스에 일치하지 않습니다.

// 작동하지 않습니다
import graphics.A*;


대신, 이는 컴파일러 오류를 발생시킵니다. import statement을 사용할 때는 일반적으로 단일 패키지 멤버나 전체 패키지를 임포트합니다.

참고: 또 다른 덜 일반적인 import 형태는 외부 클래스의 public 중첩 클래스를 임포트하는 것입니다. 예를 들어, graphics.Rectangle 클래스에 Rectangle.DoubleWide 및 Rectangle.Square와 같은 유용한 중첩 클래스가 포함되어 있는 경우, 다음 두 문장을 사용하여 Rectangle과 그 중첩 클래스를 임포트할 수 있습니다.

import graphics.Rectangle;
import graphics.Rectangle.*;


두 번째 import 문은 Rectangle을 임포트하지 않는다는 점을 유의하세요.

또 다른 덜 일반적인 import 형태인 static import 문은 이 섹션의 마지막에서 다룰 것입니다.

편의를 위해, 자바 컴파일러는 각 소스 파일에 대해 두 개의 전체 패키지를 자동으로 임포트합니다: (1) java.lang 패키지와 (2) 현재 패키지(현재 파일의 패키지).

 

Apparent Hierarchies of Packages

처음에는 패키지가 계층적인 것처럼 보이지만, 실제로는 그렇지 않습니다. 예를 들어, 자바 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 Abstract Window Toolkit)는 관련된 여러 패키지의 관계를 명확히 하기 위해 사용되지만, 포함 관계를 나타내지는 않습니다.

import java.awt.*;java.awt 패키지의 모든 타입을 임포트하지만, java.awt.color, java.awt.font 또는 다른 java.awt.xxxx 패키지들은 임포트하지 않습니다. 만약 java.awt뿐만 아니라 java.awt.color의 클래스와 다른 타입을 사용하려면, 두 패키지의 모든 파일을 임포트해야 합니다:

import java.awt.*;
import java.awt.color.*;


Name Ambiguities[모호성]

한 패키지의 멤버가 다른 패키지의 멤버와 이름을 공유하고 두 패키지가 모두 임포트된 경우, 각 멤버를 완전한 이름으로 참조해야 합니다. 예를 들어, graphics 패키지는 Rectangle이라는 클래스를 정의했습니다. java.awt 패키지에도 Rectangle 클래스가 포함되어 있습니다. graphics와 java.awt가 모두 임포트된 경우 다음과 같은 코드는 모호합니다.

Rectangle rect;


이러한 상황에서는 어떤 Rectangle 클래스를 원하는지 정확히 나타내기 위해 멤버의 완전한 이름을 사용해야 합니다. 예를 들어,

graphics.Rectangle rect;

 

The Static Import Statement

일부 상황에서는 하나 또는 두 개의 클래스에서 static final 필드(상수)와 static 메서드에 자주 접근해야 할 때가 있습니다. 이러한 클래스의 이름을 계속해서 접두사로 붙이면 코드가 복잡해질 수 있습니다. import static statement은 사용하고자 하는 상수와 static 메서드를 임포트하여 클래스 이름을 접두사로 붙일 필요가 없도록 해줍니다.

java.lang.Math 클래스는 PI 상수와 사인, 코사인, 탄젠트, 제곱근, 최대값, 최소값, 지수 계산 등을 위한 많은 static 메서드를 정의합니다. 예를 들어,

public static final double PI 
    = 3.141592653589793;
public static double cos(double a)
{
    ...
}

 

일반적으로 다른 클래스에서 이러한 객체를 사용하려면 다음과 같이 클래스 이름을 접두사로 붙입니다.

double r = Math.cos(Math.PI * theta);


import static statement을 사용하여 java.lang.Math의 static 멤버를 임포트하면, 클래스 이름 Math를 접두사로 붙일 필요가 없습니다. Math의 static 멤버는 개별적으로 임포트할 수 있습니다:

import static java.lang.Math.PI;


또는 그룹으로 임포트할 수 있습니다:

import static java.lang.Math.*;


임포트된 후에는 static 멤버를 접두사 없이 사용할 수 있습니다. 예를 들어, 이전 코드 조각은 다음과 같이 됩니다:

double r = cos(PI * theta);


물론, 상수와 자주 사용하는 static 메서드를 포함하는 자신만의 클래스를 작성하고, import static statement을 사용할 수 있습니다. 예를 들어,

import static mypackage.MyConstants.*;


참고: import static는 매우 신중하게 사용해야 합니다. import static를 남용하면 코드의 가독성과 유지보수가 어려워질 수 있습니다. 코드의 독자가 특정 static 객체를 정의하는 클래스를 알 수 없기 때문입니다. 적절하게 사용하면, import static는 클래스 이름의 반복을 제거하여 코드의 가독성을 높일 수 있습니다.

 

Managing Source and Class Files

Java 플랫폼의 많은 구현은 계층적 파일 시스템을 사용하여 소스 및 클래스 파일을 관리하지만 Java 언어 사양에서는 이를 요구하지 않습니다. 전략은 다음과 같습니다.

이름이 유형의 단순 이름이고 확장자가 .java인 텍스트 파일에 클래스, 인터페이스, 열거 또는 주석 유형의 소스 코드를 넣습니다. 예를 들어:

//in the Rectangle.java file 
package graphics;
public class Rectangle {
   ... 
}

 

그런 다음 해당 유형이 속한 패키지의 이름을 반영하는 이름의 디렉터리에 소스 파일을 넣습니다.

.....\graphics\Rectangle.java

 

패키지 멤버의 완전한 이름(qualified name)과 파일의 경로 이름(path name)은 평행합니다. Microsoft Windows 파일 이름 구분자로는 백슬래시(\\)를 사용하고, UNIX에서는 슬래시(/)를 사용한다고 가정합니다.

  • class name - graphics.Rectangle
  • pathname to file - graphics\Rectangle.java

관례에 따라, 회사는 관례적으로 패키지 이름에 역방향 인터넷 도메인 이름을 사용합니다. 인터넷 도메인 이름이 example.com인 example 회사는 모든 패키지 이름 앞에 com.example이 붙습니다. 패키지 이름의 각 구성 요소는 하위 디렉터리에 해당합니다. 따라서 example 회사에 Rectangle.java 소스 파일이 포함된 com.example.graphics 패키지가 있는 경우 다음과 같은 일련의 하위 디렉터리에 포함됩니다.

....\com\example\graphics\Rectangle.java

 

소스 파일을 컴파일하면 컴파일러는 정의된 각 타입에 대해 서로 다른 출력 파일을 생성합니다. 출력 파일의 기본 이름은 타입의 이름이고 확장자는 .class입니다. 예를 들어 소스 파일이 다음과 같다면

//in the Rectangle.java file
package com.example.graphics;
public class Rectangle {
      . . . 
}

class Helper{
      . . . 
}

 

그러면 컴파일된 파일은 다음 위치에 위치하게 됩니다.

<path to the parent directory of the output files>\com\example\graphics\Rectangle.class
<path to the parent directory of the output files>\com\example\graphics\Helper.class

 

.java 소스 파일과 마찬가지로 컴파일된 .class 파일은 패키지 이름을 반영하는 일련의 디렉터리에 있어야 합니다. 그러나 .class 파일 경로는 .java 소스 파일 경로와 동일할 필요는 없습니다. 다음과 같이 소스 및 클래스 디렉터리를 별도로 정렬할 수 있습니다.

<path_one>\sources\com\example\graphics\Rectangle.java

<path_two>\classes\com\example\graphics\Rectangle.class

 

이렇게 하면 소스를 공개하지 않고도 다른 프로그래머에게 클래스 디렉토리를 제공할 수 있습니다. 또한 컴파일러와 JVM(Java Virtual Machine)이 프로그램에서 사용하는 모든 유형을 찾을 수 있도록 소스 및 클래스 파일을 이러한 방식으로 관리해야 합니다.

 

클래스 디렉터리의 전체 경로인 <path_two>\classes를 클래스 경로라고 하며 CLASSPATH 시스템 변수로 설정됩니다. 컴파일러와 JVM은 모두 클래스 경로에 패키지 이름을 추가하여 .class 파일에 대한 경로를 구성합니다. 예를 들어,

<path_two>\classes

 

는 클래스 경로이고 패키지 이름은 다음과 같습니다.

com.example.graphics

 

그런 다음 컴파일러와 JVM은 다음에서 .class 파일을 찾습니다.

<path_two>\classes\com\example\graphics.

 

클래스 경로에는 세미콜론(Windows) 또는 콜론(UNIX)으로 구분된 여러 경로가 포함될 수 있습니다. 기본적으로 컴파일러와 JVM은 현재 디렉토리와 Java 플랫폼 클래스가 포함된 JAR 파일을 검색하여 이러한 디렉토리가 자동으로 클래스 경로에 있도록 합니다.

 

Setting the CLASSPATH System Variable

현재 CLASSPATH 변수를 표시하려면 Windows 및 UNIX(Bourne 쉘)에서 다음 명령을 사용하십시오.

In Windows:   C:\> set CLASSPATH
In UNIX:      % echo $CLASSPATH

 

CLASSPATH 변수의 현재 내용을 삭제하려면 다음 명령을 사용하십시오.

In Windows:   C:\> set CLASSPATH=
In UNIX:      % unset CLASSPATH; export CLASSPATH

 

CLASSPATH 변수를 설정하려면 다음 명령을 사용하십시오(예:).

In Windows:   C:\> set CLASSPATH=C:\users\george\java\classes
In UNIX:      % CLASSPATH=/home/george/java/classes; export CLASSPATH

 

 

"패키지 멤버의 완전한 이름(qualified name)과 파일의 경로 이름(path name)은 평행합니다"라는 것은 패키지 구조와 파일 시스템 디렉토리 구조가 서로 대응된다는 의미입니다. 즉, 자바 소스 파일의 위치가 패키지 이름과 일치한다는 뜻입니다.

예를 들어, graphics 패키지의 Rectangle 클래스가 다음과 같이 선언되어 있다고 가정해봅시다:

package graphics;

public class Rectangle {
    // 클래스 내용
}


이 클래스의 파일 경로는 다음과 같이 될 것입니다:

  • Windows: graphics\Rectangle.java
  • UNIX/Linux: graphics/Rectangle.java

패키지 이름 graphics는 파일 시스템의 디렉토리 graphics와 대응됩니다. 따라서 파일 경로가 패키지 구조와 평행합니다. 이는 패키지 이름이 디렉토리 구조와 일치한다는 것을 의미합니다.

예시:
다음과 같은 패키지 및 클래스 구조가 있다고 가정해봅시다:

package com.example.shapes;

public class Circle {
    // 클래스 내용
}


이 클래스의 파일 경로는 다음과 같이 됩니다:

  • Windows: com\example\shapes\Circle.java
  • UNIX/Linux: com/example/shapes/Circle.java

여기서 패키지 com.example.shapes는 파일 시스템의 디렉토리 com/example/shapes와 대응되며, 클래스 Circle은 파일 Circle.java에 정의되어 있습니다.

이러한 구조는 자바 컴파일러와 JVM이 클래스 파일을 찾고 로드하는 데 도움을 줍니다.