자바의 패키지에 대해 학습
자바는 클래스를 체계적으로 관리하기 위해 패키지(package)를 사용하는데 패키지는 파일 시스템의 폴더와 비슷한 개념이며 물리적으로는 폴더 형태
패키지는 클래스의 일부분으로 취급하고, 클래스를 유일하게 만들어주는 식별자 역할
자바가 제공하는 패키지 중 가장 자주 쓰이는 패키지는 java.lang 와 java.util
이 중 java.lang은 아주 기본적인 것들이기 때문에 import하지 않아도 자바가 알아서 java.lang의 클래스를 불러옴
⏫Top
package 키워드는 자바 파일의 최상단에 작성하며, 클래스가 속해있는 패키지를 선언하기 위해 사용
클래스의 전체 이름은 패키지명 + 클래스명 (FQCN, Fully Qualified Class Name) 이며 계층 구조의 패키지를 점(.)을 사용해 구분
├── src # src 하위부터 패키지 경로 #
│ ├── Main.class # 속해있는 package가 없으면 미표시
│ ├── dami
│ │ ├── Solution.class # package dami;
│ │ ├── hackerrank
│ │ │ ├── HackerRank.class # package dami.hackerrank;
│ │ │ └── medium
│ │ │ ├── A.class # package dami.hackerrank.medium;
│ │ │ ├── B.class # package dami.hackerrank.medium;
│ │ │ └── C.class # package dami.hackerrank.medium;
│ │ ├── leetcode
│ │ │ ├── Leetcode.class # package dami.leetcode;
│ │ │ ├── easy
│ │ │ │ ├── A.class # package dami.leetcode.easy;
│ │ │ │ ├── D.class # package dami.leetcode.easy;
│ │ │ │ ├── D$InnerD.class # package dami.leetcode.easy;
package dami.leetcode.easy;
public class D {
. . .
public class InnerD {
. . .
}
}
- D 클래스의 전체 이름은
dami.leetcode.easy.D
가 되고, 파일 시스템 디렉토리 구조는 'dami\leetcode\easy\D.class' - 내부 클래스는 $ 기호 사용해서 연결
패키지 특징
- 패키지가 다를 경우 중복 클래스명을 구분해서 사용 가능
- 클래스 파일만 복사해서 다른 경로로 이동 시 클래스 사용이 불가능하기 때문에 이동하려면 반드시 '패키지 + 클래스' 전체를 이동
선언 규칙
- 숫자로 시작하면 안되고, _와 $를 제외한 특수문자 사용 불가능
- java로 시작하는 패키지명은 자바 표준 API에서만 사용 가능하기 때문에 불가능
- 모두 소문자로 작성하는 것이 관례
- 대규모 프로젝트의 경우 패키지명이 중복되지 않도록 회사 도메인 이름으로 패키지 생성하고 마지막은 프로젝트명
com.samsung.projectname org.apache.projectname
패키지 선언이 포함된 클래스를 명령 프롬프트에서 정상적으로 컴파일하려면 -d
옵션 사용 필요
옵션 미사용 시 패키지 미생성
javac ClassName.java # 패키지 폴더 미생성하고 .class 파일 생성
javac -d . ClassName.java # 현재 위치에 패키지 폴더 생성 후 생성된 가장 하위 경로에 .class 파일 생성
javac -d ..\bin ClassName.java # 현재 폴더와 같은 뎁스 위치에 패키지 폴더 생성 후 생성된 가장 하위 경로에 .class 파일 생성
javac -d C:\Temp\bin ClassName.java # 입력한 절대경로에 패키지 폴더 생성 후 생성된 가장 하위 경로에 .class 파일 생성
또한, 패키지가 있는 클래스를 실행하려면 생성한 바이트코드가 있는 폴더가 아닌 패키지가 시작하는 최상위 폴더에서 java
명령어로 실행 (패키지는 클래스명의 일부이기 때문에)
IDE 사용 시 컴파일하면 IDE가 알아서 지정된 경로에 패키지 포함 클래스파일 생성
⏫Top
다른 패키지에 존재하는 클래스를 사용하기 위해 import
키워드를 사용해 다른 클래스를 불러오는 것
- 사용할 때 패키지와 클래스를 모두 작성하는 방법
package com.mycompany; public class Car { com.hankook.Tire tire = new com.hankook.Tire(); }
import
사용해 클래스를 불러온 후 클래스 이름만 작성하는 방법package com.mycompany; ////////////////////////// import com.hankook.Tire; // 또는 import com.hankook.*; ////////////////////////// public class Car { Tire tire = new Tire(); }
- 전체 코드가 복잡해질 수 있는 1번 방법에 비해 훨씬 간결한 코드 작성 가능
- 패키지 선언과 클래스 선언 사이에 작성
- 특정 패키지 내 다수의 클래스를 사용해야 한다면
*
기호 사용 가능*
: 패키지에 속하는 모든 클래스
import
개수는 제한이 없으며 필요한 패키지가 있다면 얼마든지 추가 가능import
로 지정된 패키지의 하위 패키지는import
대상에 미포함- 하위 패키지의 클래스가 필요하면
import
추가
import com.mycompany.*; import com.mycompany.project.*;
- 하위 패키지의 클래스가 필요하면
단, 경우에 따라 다른 패키지의 클래스를 사용하기 위해 1번 방법 필요
- 서로 다른 패키지에 동일한 클래스명이 존재하고, 두 패키지의 모든 클래스가
import
되어있을 때
├── src
│ ├── dami
│ │ ├── hackerrank
│ │ │ └── medium
│ │ │ ├── A.class
│ │ │ ├── B.class
│ │ │ └── C.class
│ │ ├── leetcode
│ │ │ ├── easy
│ │ │ │ ├── A.class
│ │ │ │ ├── D.class
│ │ │ │ ├── D$InnerD.class
import dami.hackerrank.medium.*;
import dami.leetcode.easy.*;
public class Ex {
public static void main(String[] args) {
// 컴파일러는 dami.hackerrank.medium의 A 클래스와 dami.leetcode.easy 패키지의 A 클래스 중 어떤 클래스를 로딩해야하는지 모름
A error = new A(); // 컴파일 에러
// 1번 방법으로 직접 전체 패키지 + 클래스명 작성해서 사용
dami.hackerrank.medium.A success1 = new dami.hackerrank.medium.A();
dami.leetcode.easy.A success2 = new dami.leetcode.easy.A();
}
}
- 컴파일 에러
Reference to 'A' is ambiguous, both 'dami.hackerrank.medium.A' and 'dami.leetcode.easy.A' match
⏫Top
클래스를 찾기 위한 경로
JVM이 프로그램을 실행하기 위해 클래스파일(.class)을 찾는 기준이 되는 경로
.java로 끝나는 소스 코드 파일을 컴파일하면 .class로 끝나는 바이트코드가 생성되고, JVM은 이 바이트코드 명령을 실행하여 프로그램 실행
클래스패스는 .class 파일이 포함된 디렉토리와 파일을 콜론(:)으로 구분한 목록이며, 자바 실행 명령 시(java 또는 jre) 클래스패스에 지정된 경로를 모두 탐색해서 특정 클래스에 대한 코드가 포함된 .class 파일 검색
- MacOS 클래스패스 설정
- 터미널에서
vi /etc/profile
입력해서CLASSPATH=. . .
입력 - 콜론(:)으로 구분
- 터미널에서
- Windows
- 시스템 환경변수 설정 창에서 설정
- 세미콜론(;)으로 구분
- 단, IDE 사용 시 툴이 알아서 클래스패스를 추가해주기 때문에 환경변수 직접 지정할 필요 없음
기본적으로 패키지에 포함되지 않은 클래스 파일에 대해 클래스패스 설정
클래스패스에 사용 가능한 값 유형
- /Users/mongzza/java/classes 와 같은 디렉토리
- zip 파일
- jar 파일
클래스패스를 지정하는 두 가지 방법
- CLASSPATH 환경변수 설정
- 자바 실행 명령 시 -classpath 옵션 사용
⏫Top
/Users/mongzza/java/classes 경로에 있는 MyClass.java 파일을 컴파일
(현재 경로 : /Users/mongzza/java/classes)
javac MyClass.java # 패키지 없는 경우
javac -d . MyClass.java # 패키지 있는 경우(package : kr.mycompany.project)
- 패키지 없는 경우 : /Users/mongzza/java/classes 경로에 MyClass.class 파일 생성
- 패키지 있는 경우 : /Users/mongzza/java/classes/kr/mycompany/project 경로에 MyClass.class 파일 생성
MyClass 프로그램 실행 전에 클래스패스 설정(하위 패키지 디렉토리들의 최상위 루트 디렉토리를 지정)
CLASSPATH=/Users/mongzza/java/classes
다른 경로에 추가할 클래스 파일이 더 있다면 다음과 같이 콜론(:)을 사용해 여러개 설정
CLASSPATH=/Users/mongzza/java/classes:/Users/mongzza/java/classes/util
클래스패스 지정 후 자바 실행 명령
java MyClass # 패키지 없는 경우
java kr.mycompany.project.MyClass # 패키지 있는 경우
⏫Top
javac 또는 java 명령 시 클래스패스를 지정하는 옵션. -cp
로 단축 사용 가능
-cp <디렉토리 및 zip/jar 파일의 클래스 검색 경로>
-classpath <디렉토리 및 zip/jar 파일의 클래스 검색 경로>
javac -classpath <클래스패스> <자바 소스 파일 경로>
Maven이나 Gradle 등 빌드툴 사용 시 추가한 의존성들을 IDE가 javac, java 명령 시 직접 classpath 옵션에 추가해주는 것
/Users/mongzza/java/classes 경로에 있는 MyClass.java 파일을 클래스패스 지정해서 컴파일
(현재 경로 : /)
javac -classpath /Users/mongzza/java/classes /Users/mongzza/java/classes/MyClass.java
다른 경로에 추가할 클래스 파일이 더 있다면 콜론(:) 사용해 여러개 설정
javac -cp /Users/mongzza/java/classes:/Users/valid.jar /Users/mongzza/java/classes/MyClass.java
⏫Top
클래스 및 클래스의 구성 멤버에 대한 접근을 제한하는 역할. 라이브러리 클래스 설계 시 접근지시자로 외부에서 접근 가능/불가능한 멤버를 구분하는 것이 필요
- 다른 패키지에서 클래스를 사용하지 못하도록 제한(클래스 제한)
- 클래스로부터 객체를 생성하지 못하도록 제한(생성자 제한)
- 특정 필드나 메소드를 숨김 처리(필드, 메소드 제한)
종류 | 범위 | 적용 가능 대상 |
---|---|---|
private | 선언된 클래스 내에서만 접근 가능 | 필드, 생성자, 메소드 |
(default) | 접근 제어자를 별도로 지정하지 않은 경우 선언된 클래스와 같은 패키지 내에서만 접근 가능 |
클래스, 필드, 생성자, 메소드 |
protected | 선언된 클래스와 같은 패키지에 있거나 선언된 클래스를 상속받는 자식 클래스 내에서만 접근 가능 | 필드, 생성자, 메소드 |
public | 모든 클래스에서 접근 가능 | 클래스, 필드, 생성자, 메소드 |
클래스에 적용 가능한 지시자는 public
과 default
선언된 클래스와 같은 패키지 내에서만 사용 가능하며 다른 패키지에서 사용 불가능(하위 패키지 또한 다른 패키지로 취급)
package dami.access;
class AccessModifier {
. . .
}
package dami.access;
public class Possible {
public void method() {
AccessModifier modifier = new AccessModifier(); // 성공
}
}
package dami.access.other;
public class Impossible {
public void method() {
AccessModifier modifier = new AccessModifier(); // 컴파일 에러
}
}
- 컴파일 에러 발생
'dami.access.AccessModifier' is not public in 'dami.access'. Cannot be accessed from outside package
모든 패키지에서 제한 없이 접근 가능
package dami.access;
public class AccessModifier {
. . .
}
- 아무 클래스에서나
AccessModifier
의 객체 생성 가능
생성자에 적용 가능한 지시자는 public
, projected
, default
, private
클래스에 생성자를 작성하지 않을 경우 자동으로 추가되는 생성자는 클래스 접근지시자와 같은 레벨
또한, 클래스 제한 수준보다 생성자 제한 수준이 더 낮아도 생성자는 클래스 내부에 포함되기 때문에 클래스의 제한 수준까지 접근 가능
- 예를 들어 default 클래스에 public 생성자가 있다면, 생성자는 클래스에 의해 강제로 default 수준의 생성자가 되는 것과 마찬가지
모든 패키지의 모든 클래스 내부에서 제한 없이 생성자 호출 가능
선언된 클래스와 같은 패키지에 있거나 상속받은 클래스 내부에서 생성자 호출 가능
선언된 클래스와 같은 패키지에 있는 클래스 내부에서 생성자 호출 가능
선언된 클래스 내부에서만 생성자를 호출할 수 있고, 객체 생성 가능. 싱글톤 패턴에서 사용
다양한 접근지시자 생성자를 선언한 클래스
package dami.access;
public class AccessModifier {
public AccessModifier(boolean b) { . . . }
protected AccessModifier(int i) { . . . }
AccessModifier(double d) { . . . }
private AccessModifier(char c) { . . . }
public void method() {
AccessModifier am = new AccessModifier(false);
AccessModifier am = new AccessModifier(1);
AccessModifier am = new AccessModifier(1.0);
AccessModifier am = new AccessModifier('A');
}
}
- 모든 생성자 호출 가능
같은 패키지의 다른 클래스
package dami.access;
public Other1 {
public void method() {
AccessModifier am = new AccessModifier(false);
AccessModifier am = new AccessModifier(1);
AccessModifier am = new AccessModifier(1.0);
// AccessModifier am = new AccessModifier('A');
}
}
- private 생성자 컴파일 에러
다른 패키지의 클래스
package dami.access;
public Other2 {
public void method() {
AccessModifier am = new AccessModifier(false);
// AccessModifier am = new AccessModifier(1);
// AccessModifier am = new AccessModifier(1.0);
// AccessModifier am = new AccessModifier('A');
}
}
- public 생성자만 접근 가능
필드와 메소드에 적용 가능한 지시자는 public
, projected
, default
, private
모든 패키지의 모든 클래스 내부에서 제한 없이 필드와 메소드 호출 가능
선언된 클래스와 같은 패키지에 있거나 상속받은 클래스 내부에서 필드와 메소드 호출 가능
선언된 클래스와 같은 패키지에 있는 클래스 내부에서 필드와 메소드 호출 가능
선언된 클래스 내부에서만 필드와 메소드를 호출 가능
다양한 접근지시자 필드와 메소드를 선언한 클래스
package dami.access;
public class AccessModifier {
public int publicField;
protected int protectedField;
int defaultField;
private int privateField;
public void publicMethod() { . . . }
protected void protectedMethod() { . . . }
void defaultMethod() { . . . }
private void privateMethod() { . . . }
public void method() {
publicField = 1;
protectedField = 2;
defaultField = 3;
privateField = 4;
publicMethod();
protectedMethod();
defaultMethod();
privateMethod();
}
}
- 모든 멤버 호출 가능
같은 패키지의 다른 클래스
package dami.access;
public Other1 {
public void method() {
AccessModifier am = new AccessModifier();
am.publicField = 1;
am.protectedField = 2;
am.defaultField = 3;
//am.privateField = 4;
publicMethod();
protectedMethod();
defaultMethod();
//privateMethod();
}
}
- private 멤버 컴파일 에러
다른 패키지의 클래스
package dami.access;
public Other2 {
public void method() {
AccessModifier am = new AccessModifier();
am.publicField = 1;
//am.protectedField = 2;
//am.defaultField = 3;
//am.privateField = 4;
publicMethod();
//protectedMethod();
//defaultMethod();
//privateMethod();
}
}
- public 멤버만 접근 가능
이 외, static
, final
키워드 관련 내용은 5주차 - 클래스 정의 방법 참고
일반적으로 객체 지향 프로그래밍은 객체의 데이터를 외부에서 직접 접근하지 못하게 제한하고, 메소드를 통해 데이터를 변경하는 방법 선호.
따라서, 필드는 주로 private
지정자를 사용하고 Setter
나 Getter
를 사용해 필드 값을 변경하도록 설계
⏫Top
자바에서 상수를 정의할 땐 Interface나 Enum을 사용하는 대신 일반 클래스에 static final 필드 선언 후 private 생성자를 정의하는 것이 적절한 방법
- 신용권, 『이것이 자바다』, 한빛미디어(2015)
- 자바 클래스패스(classpath)란?
- javac java 옵션 -classpath 활용 정리(컴파일 옵션)
- 패키지