본문 바로가기
IT 공부/자바와 웹 애플리케이션

[자바] - unnamed package의 문제점과 JDK의 클래스패스 설정 방법들

by exdus3156 2023. 12. 31.

1. 패키지는 코드로 명시해야 한다.

자바의 패키지(package)는 단순히 클래스(.class) 파일들을 디렉토리에 배치한 개념이 아니라 자바 컴파일러나 JVM이 특정 클래스의 위치를 정확하게 식별하게 도와주는 실질적인 "코드"다.

따라서 패키지 선언문이 없는 자바 소스코드를 컴파일한 후에, 특정 디렉토리 내부에 배치해도 해당 디렉토리는 클래스 파일의 패키지가 될 수 없다. JVM은 클래스를 찾지 못한다(No Class Def Found Error)고 내뱉는다. 패키지 없이 컴파일한 소스코드는 나중에 디렉토리에 배치해도 그 경로가 패키지가 될 수 없다. 패키지는 반드시 코드로 명시되어야 한다.

 

예를 들어, 아래와 같은 자바 소스코드가 있다. 패키지 선언문이 없다.

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello");
    }
}

이것을 컴파일한 후, Hello.class 파일을 [book] 이라는 디렉토리 안에 넣었다. 이제 이 클래스 파일의 패키지는 자동으로 book.Hello가 될까?

아니다. 될 수 없다. 코드 상에서 구체적으로 패키지를 명시하지 않았기 때문에 이리저리 디렉토리에 배치한들 이미 돌이킬 수 없는 강을 건넜다. 

 

자바 가상 머신이나 컴파일러는 클래스패스(classpath, cp)를 통해 클래스 파일의 root 디렉토리를 식별한다. 현재 위 명령어는 cp를 따로 설정하지 않았다. 따라서 디폴트로 현재 디렉토리에서 클래스를 찾는다. ("If --class-path, -classpath, or -cp aren’t specified, then the user class path is the current directory." javac)

그런데 분명히 현재 디렉토리의 book 폴더 안에 Hello.class가 있는데도 찾지를 못하고 있다. 패키지를 정확히 코드 상에서 명시하지 않았기 때문에 이 클래스는 다른 클래스가 참조할 수도 없고, java나 javac가 찾지도 못하는 것이다.

이 파일을 굳이 실행하려면 어쩔 수 없이 해당 클래스가 있는 디렉토리로 넘어가서 실행할 수 밖에 없는데, 패키지 이름이 명시되지 않은 경우 자바는 자동으로 unnamed package라고 암묵적 패키지를 설정해주기 때문이다. 암묵적 패키지는 클래스패스 경로에서 실행할 수 있다. 그러나 실행만 할 수 있을 뿐이다. 다른 클래스가 참조하지 못한다.

(※ 위와 같은 이유로 규모 있는 자바 개발 시, unnamed package는 피해야 한다.)

중요한 핵심은 자바 클래스는 반드시 패키지로 식별되어야 하며, 이것이 반드시 소스코드 상에 명시되어야 한다는 점이다.

패키지는 물리적으로는 디렉터리 계층 구조로 구현된다. 하지만 소스코드에서 이 구조를 명시하지 않으면 컴파일 후에 아무리 디렉토리에 배치해봤자 그것은 절대 클래스의 패키지가 될 수 없다는 점에 주의하자. 패키지는 디렉토리로 구현되는 것이지, 디렉토리에 배치하는 개념이 아니다.

 

 


2. 클래스패스의 의미

패키지가 아무리 물리적으로는 디렉토리로 구현된다지만, 그렇다고해서 C드라이브에서 시작하는 고정된 경로 개념과 같은 것은 아니다.

패키지 개념에는 root 개념이 있다. 이것이 클래스패스다. 수많은 클래스들이 다양한 패키지(디렉토리)로 구분되는데, 이 모든 패키지에는 하나의 루트 디렉토리가 존재한다.

예를 들어, 아래와 같은 소스코드가 있다.

package com.test.book;

public class Hello {
    //... 
}

이 클래스를 아래와 같이 (-d 옵션과 함께) 컴파일하면 컴파일된 클래스 파일은 자동으로 [com] - [test] - [book] 디렉토리에 알맞게 배치된다.

javac -d . Hello.java;

(※ -d 옵션이 없이 컴파일해도 된다. 하지만 그때는 일일이 디렉토리를 만들어서 클래스들을 배치해야 할 것이다...;)

 

그런데 [com]이라는 디렉토리의 상위 디렉토리를 명시하지 않았다. [com] 디렉토리의 위치를 모르면 사실상 [test], [book], Hello.class도 찾지 못할 것이다.

 

따라서 java나 javac에게 바로 이 상위 디렉토리, 즉 패키지의 루트 디렉토리 위치를 알려줘야 비로소 모든 클래스 파일의 위치를 정확히 식별할 수 있을 것이다. 이것이 클래스패스가 뜻하는 바다.

위와 같이 클래스패스(cp)를 설정해야만 java는 E:\testpath 라는 디렉토리에서 시작해 com, test, book 을 차례대로 검색할 수 있게 된다. 

 

 


 

3. 클래스패스 설정 방법들

(1) 암묵적인 방식

If --class-path, -classpath, or -cp aren’t specified, then the user class path is the current directory.

위 문구는 공식 문서(링크)에 있는 내용이다. cp를 따로 설정하지 않으면 현재 디렉토리를 클래스패스라고 간주한다. 따라서 콘솔에서 루트 위치로 직접 가서 실행해야 할 것이다.

 

(2) cp 옵션 지정

위 예시와 같이, cp 옵션으로 패키지의 루트 디렉토리를 지정해줄 수도 있다.

 

(3) 환경 변수 등록

환경 변수에 CLASSPATH라는 이름의 변수를 등록하고, 원하는 경로를 지정할 수 있다. 그러면 cp를 설정하지 않아도 패키지의 루트를 여기서 시작한다. (개인적으로 이 방식을 사용한 적은 없었다.)

 

(4) 표준 라이브러리의 경우는?

JavaSE의 표준 라이브러리의 경우 JRE에서 관리되기 때문에 특별히 클래스패스를 명시하지 않아도 자동으로 위치를 찾고 식별할 수 있다. 따로 클래스패스를 지정할 필요도 없고, 개발자가 만든 패키지의 위치를 식별하기 위해 cp를 지정해도 표준 라이브러리와는 별개로 취급되므로 걱정할 것이 없다.

 

(5) 번외 : IDE 개발 환경은 자동으로 설정해준다

IDE는 자동으로 클래스패스를 알아서 지정해준다. 지금까지 논의한 모든 것을 IDE가 알아서 해준다. 그래픽 인터페이스와 단축키를 사용하기 때문에 단지 명령어가 가려져 있을 뿐, 내부적으로는 콘솔창의 명령어를 그대로 사용하는 것이다.

보통 개발 할 때 IDE를 사용하므로 세밀하게 이해할 필요는 없지만 실제로 어떤 원리로 개발자의 편의를 도모하는지 확인하는 것도 재밌는 실험이다.

예를 들어, 이클립스에서는 구체적으로 어떻게 실행하는지 Run Configuration 창에서 아래와 같은 커맨드 명령어를 확인할 수 있다.

-classpath "E:\Java project\test\bin" 이 설정되어 있다. 메이븐이나 그레이들과 같은 빌드툴이 아니라 이클립스 자체의 프로젝트 빌드 스타일은 컴파일한 소스코드를 프로젝트 폴더의 [bin] 폴더에 클래스 파일들을 넣는 식이다. 따라서 루트 디렉토리는 bin이 된다. 바로 이 루트 경로를 classpath 옵션으로 지정해주고 있는 모습이다.

(※ Dfile.encoding 개념이 궁금하다면 여기로)

인텔리제이는 이클립스와 달리 콘솔 뷰어에 어떤 명령어를 썼는지 친절하게 직접 보여준다.