들어가며
간단하게 설명하면,
패키지는 클래스를 묶는
수단이다.
묶어서 '다른 클래스' 또는
'다른 클래스들의 묶음'을
구분하기 위한 수단이다.
패키지 선언의 의미와 목적
자바 8을 기준으로 Java SE에서
제공하는 클래스의 수만 해도
300개가 넘는다.
단, 이름이 A로 시작하는 클래스의
수만 세었을 때, 그 정도이다.
따라서 이름이 Z로 시작하는
클래스까지 그 수가
수천을 넘는다는 것을 쉽게 짐작할
수 있다.
그런데 이들이 단순히 이름만
갖는다면
어떠한 용도로 사용되는 클래스인지
구분이 어려워진다.
예를 들어서 다음의 클래스가
무엇과 관련이 있는지 알 수
있는가?
Class CookieManager
클래스의 이름만 놓고 보면
짐작이 가지 않는다.
그런데 위의 클래스가 속한
다음 패키지의 이름을 보면
짐작이 가능하다.
Java.net // 클래스 CookieManager가 속한 패키지 이름
일단 패키지 이름이 Java로
시작한다.
이는 자바에서 제공하는
클래스임을 뜻한다.
그리고 net은 network를 줄인
표현으로
네트워크 관련 기능의 클래스임을
짐작하게 한다.
이렇듯 '패키지'라는 것은 클래스를
구분하고 파악하는데도 도움이 된다.
그리고 클래스를 패키지라는
것을 이용해서 구분을 지으면
클래스의 이름이 겹치는 문제도
해결할 수 있다.
프로그램을 작성하다 보면
직접 만드는 클래스의 수보다
다른 집단 또는 다른 기업에서
만든 클래스들을
직간접적으로 더 많이 사용하기
마련이다.
이때 둘 이상의 집단 또는 기업이
제공하는 클래스를 사용하다 보면
클래스의 이름이 충돌하는 문제가
발생할 수 있따.
그러나 기업 고유의 정보,
예를 들어서, URL과 같은 주소
정보를 이용해서
패키지의 이름을 지어 놓으면
이러한 이름 충돌의 문제를
해결할 수 있다.
이름 충돌이 발생하는 두 클래스의 등장 : 상황을 가상으로 구성
프로그램 개발에 있어서 다음
클래스를 사용하기로 결정하였다.
이 클래스는 인터넷 도메인
주소가 wxfx.com인 회사에서
개발한 클래스이다.
// 원의 넓이 관련 클래스 정의
public class Circle{
double rad;
final double PI;
public Circle(double r){
rad = r;
PI = 3.14;
}
public double getArea(){
return (rad * rad) * PI; // 원의 넓이 반환
}
}
다음 클래스도 이번 프로그램
개발에 있어서 필요한 클래스이다.
이 클래스는 인터넷 도메인
주소가 fxmx.com인 회사에서
개발한 클래스이다.
// 원의 둘레 관련 클래스 정의
public class Circle{
double rad;
final double PI;
public Circle(double r){
rad = r;
PI = 3.14;
}
public double getArea(){
return (rad * 2) * PI; // 원의 둘레 반환
}
}
위의 두 클래스 정의는 개별적으로
보았을 때 문제가 없다.
그러나 이를 동시에 사용해야
하는 상황에서는
클래스 이름이 Circle의 중복이
문제가 된다.
위의 클래스를 제공한 두 회사는
위치적으로나 업무적으로나
관련이 없는 회사이기에
이러한 문제는 충분히
발생할 수 있다.
그러나 위의 클래스 정의에
패키지 선언이 되어 있다면,
(패키지로 묶였다면)
이러한 이름 충돌 문제에서
벗어날 수 있다.
그리고 위의 두 Cricle 클래스
정의를 보면
다음과 같이 public 선언이
함께 존재함을 알 수 있다.
그런데 이 선언이 의미하는
바는 다음 Chapter에서
설명을 하니,
지금은 그냥 넘어가기로 하자.
public class Circle { . . . }
다만 다음의 사실 정도만
알고 있자.
- 하나의 소스파일에는 public으로 선언된 클래스의 정의를 하나만 둘 수 있다.
- 소스파일의 이름은 public으로 선언된 클래스의 이름과 동일해야 한다.
즉, 두 Circle 클래스 모두
public으로 선언되어 있기
때문에
소스파일의 이름도 둘 다
Circle.java로 동일할 수밖에
없다.
따라서 이 둘을 저장할 때에는
각각 다른 위치에 저장해야 한다.
이름 충돌의 해결을 위한 패키지의 효과
패키지 선언은 클래스의
접근 방법을 구분할 뿐만
아니라
클래스 파일이 공간적으로도
구분되게 한다.
즉 패키지 선언은 다음과 같은
두 가지 특성을 만들어 낸다.
- 클래스 접근 방법의 구분
- 서로 다른 패키지의 두 클래스는 인스턴스 생성 시 사용하는 이름이 다르다. - 클래스의 공간적인 구분
- 서로 다른 패키지의 두 클래스 파일은 저장되는 위치가 다르다.
위의 두 가지 특성을 확인하기
위해서
앞서 제공된 두 Circle 클래스를
대상으로
패키지 선언을 할 텐데,
이를 위해 먼저 패키지의 이름을
결정해야 한다.
그런데 패키지의 이름을 지을
때에는 다음의 관례를 따른다.
- 클래스의 이름과 구분이 되도록 패키지의 이름은 모두 소문자로 구성한다.
- 인터넷 도메인 이름의 역순으로 패키지 이름을 구성한다.
- 패키지 이름의 끝에 클래스를 정의한 주체 또는 팀을 구분하는 이름을 추가한다.
예를 들어서 인터넷 도메인이
wxfx.com인 회사의
smart팀에서 개발한 클래스를
묶을 패키지 이름을
위의 관례에 따라서 만들면
다음과 같다.
com.wxfx.smart
마찬가지로 인터넷 도메인이
fxmx.com인 회사의
simple팀에서 개발한 클래스를
묶을 패키지 이름은
다음과 같이 만든다.
com.fxmx.simple
이렇게 패키지의 이름이
결정되고
각 클래스를 패키지로
묶으면
클래스의 인스턴스 생성
방법은 다음과 같이 달라진다.
- 패키지 com.wxfx.smart의 Circle 인스턴스 생성 문장
→ com.wxfx.smart.Circle c1 = new com.wxfx.smart.Circle(3.5); - 패키지 com.fxmx.simple의 Circle 인스턴스 생성 문장
→ com.fxmx.simple.Circle c1 = new com.fxmx.simple.Circle(5.5);
이렇듯 인스턴스 생성 및
참조변수 선언 시
클래스의 이름 앞에 패키지
이름이 따라붙는 구조가 된다.
그리고 클래스 파일이 저장되는
위치도 다음과 같이 달라지는데,
이와 관련된 내용은 잠시 후
실습을 겸하여 설명하겠다.
- 패키지 com.wxfx.smart의 Circle.class 저장 위치
→ ···\com\wxfx\smart - 패키지 com.fxmx.simple의 Circle.class 저장 위치
→ ···\com\fxmx\simple
패키지의 선언 및 컴파일 방법
클래스를 패키지로 묶을 때에는
해당 클래스를 담고 있는
소스파일의 상단에 패키지
선언을 해야 한다.
예를 들어서 소스파일에
정의된 클래스를
패키지 com.wxfx.smart로
묶고 싶다면
다음 선언을 소스파일
상단에 삽입하면 된다.
package com.wxfx.smart;
따라서 앞서 제공한 Circle
클래스를 담고 있는
두 소스파일에 다음과 같이
패키지 선언을 추가해야 한다.
package com.wxfx.smart; // 패키지 선언
public class Circle{
double rad;
final double PI;
public Circle(double r){
rad = r;
PI = 3.14;
}
public double getArea(){
return (rad * rad) * PI;
}
}
package com.fxmx.simple; // 패키지 선언
public class Circle{
double rad;
final double PI;
public Circle(double r){
rad = r;
PI = 3.14;
}
public double getArea(){
return (rad * 2) * PI;
}
}
이로써 패키지 선언은 끝이
났으니 컴파일을 할 차례이다.
그런데 위의 두 파일의
이름이 동일하다.
따라서 소스파일의 저장
위치를 달리할 수밖에 없다.
그래서 이 두파일을 각각
다음 위치에 저장하기로
하겠다.
실행 환경을 아래와 같이 일치시켜야 이후 언급하는 내용이 통한다.
- 패키지 com.wxfx.smart의 Circle.java 저장 위치
→ C:\PackageStudy\src\circle1 - 패키지 com.fxmx.simple의 Circle.java 저장 위치
→ C:\PackageStudy\src\circle2
그리고 컴파일 및 실행을
목적으로
명령 프롬프트를 하나 띄워서
프롬프트 상의 경로가
다음의 상태가 되게 하자.
C:\PackageStudy>
이제 컴파일을 할 차례인데,
패키지로 묶인 컴파일을
할 때에는
다음과 같이 -d 옵션을
추가하면 편리하다.
C:\PackageStudy>javac -d <directory> <filename>
→ <directory> 패키지를 생성할 위치 정보
→ <filename> 컴파일할 파일의 이름
즉 <directory>에 현재 디렉토리를
의미하는 . 을
그리고 <filename>에 src\circle1\Circle.java를
입력하여
다음과 같이 컴파일 하면
'현재 디렉토리'에
컴파일 된 패키지 결과물이
만들어진다.
C:\PackageStudy>javac -d . src\circle1\Circle1.java
컴파일을 완료하였으면
파일 탐색기를 열어서
만들어진 결과물을
확인하자.
그러면 '현재 디렉토리'를
기준으로
'패키지 이름과 동일한 디렉토리
경로가 생성'되고,
그 안에 클래스 파일이 위치하는
것을 확인할 수 있다.
이어서 다른 파일 하나도
동일한 방법으로 컴파일
하자.
C:\PackageStudy>javac -d . src\circle2\Circle2.java
그러면 마찬가지로 다음과 같이
'패키지의 이름과 동일한 디렉토리
경로가 생성'되고,
그 안에 클래스 파일이 위치하는
것을 확인할 수 있다.
정리해보면, 앞서 언급하였듯이
동일한 이름의
두 클래스 파일이 패키지
선언으로 인해 물리적으로도
분리가 되었다.
그리고 각 인스턴스의 생성
방법도 다음과 같이 구분이
되었다.
- 패키지 com.wxfx.smart의 Circle의 인스턴스 생성 문장
→ com.wxfx.smart.Circle c1 = new com.wxfx.smart.Circle(3.5); - 패키지 com.fxmx.simple의 Circle의 인스턴스 생성 문장
→ com.fxmx.simple.Circle c2 = new com.fxmx.simple.Circle(5.5);
그럼 만약에 com.fxmx.simple
패키지로 묶인 클래스 파일의
디렉토리 이름을 인위적으로
변경하면 어떻게 될까?
그러면 해당 클래스 파일은
찾을 수 없게 된다.
즉 반드시 패키지 이름과
동일한 경로 및 이름의
디렉토리가 구성되어야 한다.
직접 디렉토리를 생성해서 클래스 파일을 가져다 놓아도 됩니다.
패키지 com.wxfx.smart로 묶인
Circle 클래스를 컴파일 하니
패키지 이름과 동일한 이름 및
경로의 디렉토리들이 생성되었다.
컴파일 과정에서 옵션 -d를 삽입한
결과이다.
그러나 프로그래머가 직접 패키지
이름과 동일한 경로의 디렉토리를
만들고
-d 옵션 없이 컴파일 하여 얻은
클래스 파일을 해당 디렉토리에
가져다 놓아도 된다.
그렇게 패키지를 묶어도 된다.
패키지로 묶은 클래스의 접근
패키지로 묶인 두 클래스
파일을 대상으로 예제를
작성해 보겠다.
이어서 제시하는 예제의
저장, 컴파일 및 실행의
경로도
여전히 C:\PackageStudy로
유지되어야 한다.
즉, 명령 프롬프트 상의
경로가 다음과 같아야 한다.
C:\PackageStudy>
public class Circle {
public static void main(Strin args[]){
com.wxfx.smart.Circle c1 = new com.wxfx.smart.Circle(3.5);
System.out.println("반지름 3.5의 원 넓이: " + c1.getArea());
com.fxmx.simple.Circle c2 = new com.fxmx.simple.Circle(3.5);
System.out.println("반지름 3.5의 원 둘레: " + c1.getPerimeter());
}
}
/*
실행 결과
반지름 3.5의 원 넓이: 38.465
반지름 3.5의 원 둘레: 21.98
*/
우선 제대로 컴파일 되었다는
점에 주목하자.
그렇다면 자바 컴파일러는
다음 문장을 어떻게 해석하고
해당 클래스 파일을 찾은
것일까?
com.wxfx.smart.Circle c1 = new com.wxfx.smart.Circle(3.5);
자바 컴파일러는 패키지를
먼저 찾는다.
즉 위의 문장을 접했을 때
com.wxfx.smart 패키지를
찾는다.
이때 클래스 파일을 찾을 때와
마찬가지로,
'클래스 패스'를 참조하여
패키지를 찾는다.
물론 클래스 패스가 별도로
지정되어 있지않으면
다음과 같이 현재 디렉토리를
기준으로 패키지를 찾는다.
현재 디렉토리를 기준으로 com\wxfx\smart 디렉토리를 찾는다.
패키지의 이름과 디렉토리
경로가 일치하기 때문에
위와 같은 방식으로 패키지를
찾게 된다.
그리고 패키지를 찾았으면
이제 그 안에서 Circle.class를
찾아서 인스턴스를 생성한다.
마찬가지로 다음 문장을 접하면
클래스 패스를 참조하여
com\fxmx\simple 디렉토리를
찾는다.
그리고 그 안에서 Circle.class를
찾아서 인스턴스를 생성한다.
com.fxmx.simple.Circle c2 = new com.fxmx.simple.Circle(3.5);
이로써 한 예제에서,
'동일한 이름의 서로다른
두 클래스'의 인스턴스를
생성해 보았다.
물론 이는 해당 클래스가
묶여 있는 패키지가 다르기에
가능한 일이다.
import 선언
앞서 보인 상황, 즉 동일한
이름의 두 클래스를 대상으로
인스턴스를 생성해야 하는
상황이라면
패키지의 이름은 생략이
불가능하다.
하지만 필요로 하는 클래스가
다음 하나라면,
com.wxfx.smart.Circle
패키지의 이름은 늘 붙이고
다니는 것은 번거로운 일이다.
뿐만 아니라 가독성 또한
낮아지는 것도 사실이다.
따라서 이러한 상황에서,
즉, 동일한 이름의 클래스가
사용되지 않는 상황에서의
패키지 이름 생략법을
자바는 제공하고 있다.
이와 관련하여 다음 예제를
보자.
이 예지는 앞서 만들었던 com.wxfx.smart.Circle을 필요로 한다.
import com.wxfx.smart.Circle;
public class Circle {
public static void main(String[] args){
Circle c1 = new Circle(3.5);
System.out.println("반지름 3.5의 원 넓이: " + c1.getArea());
Circle c2 = new Circle(5.5);
System.out.println("반지름 5.5의 원 넓이: " + c1.getArea());
}
}
/*
실행 결과
반지름 3.5의 원 넓이: 38.465
반지름 5.5의 원 넓이: 94.985
*/
위 예제의 다음 문장을 보자.
어렵지 않게 이 문장이 의미하는
를 짐작할 수 있을 것이다.
import com.wxfx.smart.Circle;
위의 문장을 통해서 컴팡일러에게
전달하는 내용은 다음과 같다.
지금부터 Circle이라 하면 com.wxfx.smart.Circle을 의미하는 것으로 간주하라.
따라서 위의 import 선언
이후로는
다음과 같이 인스턴스를
생성할 수 있다.
Circle c1 = new Circle(3.5);
물론 다음과 같이 동일한
이름의 클래스를 대상으로
동시에 import 선언을 하는
것은 불가능하다.
이러한 상황에서는 Circle이라는
이름에 대해서 '이름 충돌'이
발생한다.
import com.wxfx.smart.Circle;
import com.fxmx.simple.Circle;
그리고 클래스가 아닌 패키지를
대상으로
다음과 같이 import 선언을 하는
것도 가능하다.
import com.wxfx.smart.*;
위의 문장을 통해서 컴파일러에게
전달하는 내용은 다음과 같다.
지금부터 com.wxfx.smart로 묶인 클래스 패키지 언선을 생략하라.
예를 들어 com.wxfx.smart 패키지로
묶인 클래스로
Circle과 Triangle이 있다면,
위의 import 선언 이후로는
이 둘 모두 다음과 같이
클래스의 이름만으로
인스턴스를 생성할 수 있다.
Circle c = new Circle(1.0);
Triangle t = new Triangle(2.0);
끝으로 패키지 대상의
import 선언은
이름 출동이 발생할 수
있고,
또 의도하지 않은 클래스의
인스턴스를 생성하는 상황으로
이어질 수 있어서
가급적으로 사용을 자제하길
권고하고 있음도
알고 있기를 바란다.
참고 및 출처
|