자바에서는 패키지를 활용해 관련있는 클래스, 인터페이스 또는 서브 패키지들을 묶을 수 있습니다. 이를 사용하면 다음과 같은 장점이 있습니다.

  • 연관된 타입들을 더욱 쉽게 찾을 수 있다.
  • naming confilct를 피할 수 있다.
  • access modifiers를 활용해 패키지 내의 타입에 대하여 visibility와 access를 제어할 수 있다.

Access modifiers

Access modifier은 public, private, protected, default 네 종류가 있으며 top-level class은 이 중에서 public과 default access modifier만 사용가능합니다. top-level class란 중첩되지 않은 클래스를 의미합니다.

// in Foo.java:

public class Foo { // top level class

    public static class NestedBar { // nested class
    }

}

또한 top-level public class는 file name과 일치해야하며 최대 한개까지 선언가능합니다.


default

명시적으로 어떠한 키워드도 선언하지 않으면 Java에서는 기본적으로 주어진 클래스, 메서드, 속성에 대해 default를 지정합니다. 또 다른 이름으로는 package-private라고도 부르는데 이름에서 알 수 있듯이 모든 멤버가 동일한 패키지 내에서는 보이지만 다른 패키지에서는 접근할 수 없습니다.

package io.devbelly;

public class Main {
    static void print(){
        System.out.println("this is io.devbelly package");
    }

}
package io.devbelly;

public class Other {
    public Other(){
        Main.print();
    }
}

Private

private으로 선언된 메서드나 속성에 대해서는 오직 동일한 클래스내에서만 접근이 가능합니다. access modifier중에 가장 restrictive하며 encapsulation의 핵심입니다.

package io.devbelly;

public class Main {
    private static void print(){
        System.out.println("this is io.devbelly package");
    }

    private void anotherPrivateMethod(){
        print();
    }
}

Protected

default와 public의 중간 level로 protected가 있습니다. 만일 메서드나 속성에 대해 protected를 사용한다면 동일 패키지내에 해당 멤버들에 접근가능한 특징을 갖고 있습니다. 이러한 default 범위에 추가적으로 해당 멤버를 포함하는 클래스를 상속했다면 다른 패키지일지라도 protected 멤버에 접근가능합니다.

동일 package에서 접근

package io.devbelly;

public class Main {
    protected static void print() {
        System.out.println("this is io.devbelly package");
    }
}
package io.devbelly; // same package

public class Other {
    public Other() {
        Main.print();
    }
}

다른 package에서 접근

package io.subbelly; // different pacakge
import io.devbelly.Main;

public class SubClass extends Main{
    public void SubMethod(){
        Main.print();
    }
}

정리하자면 아래 표와 같습니다.

image


Creating a Package

패키지를 생성하려면 코드의 첫줄에 package 키워드를 사용하면 됩니다.

package io.devbelly;

클래스 선언시 굳이 패키지를 선언하지 않아도 사용가능합니다. 이 경우 클래스들은 default package에 속하게 되는데 아래와 같은 단점이 있습니다.

  • 다른 패키지에서 default package안에 있는 클래스를 import할 수 없다.
  • access modifiers에서 protected와 package-private이 의미를 잃는다.

Naming Convention

동일한 이름의 패키지를 피하기 위해 다음과 같은 명명 규칙을 따릅니다.

  • 모든 이름을 lowercase로 작성하기
  • 마침표로 패키지 이름 구분하기
  • 패키지를 만든 company나 organization에 따라 결정(선택)

만약 company나 organization을 기반으로 package이름을 짓기로 결정했다면 회사의 URL를 거꾸로 한 후 부서 이름이나 프로젝트 이름을 추가합니다.

image

표시한 빨간색 사각형은 회사 URL인 apache.org을 거꾸로 한 후 프로젝트 이름을 추가한 것을 확인할 수 있습니다.


Import

패키지 생성 명명규칙을 확인했으니 이제 패키지내 멤버들을 사용할 차례입니다. import를 통해서 하나의 타입을 가져오거나 asterisk(*)를 통해서 패키지 전체를 가져올 수도 있습니다.

image

import 할 때 asterisk로 전체 패키지를 가져오는 것이 더 깔끔하고 편리하지만 이는 local namespace를 복잡하게 만들어 결국 오류의 원인이 되므로 지양해야합니다.


클래스패스

클래스패스란 자바 컴파일러나 JVM이 필요한 클래스들을 컴파일하거나 다른 클래스들을 실행하기 위한 set of paths입니다. 여러 path들을 구분하기 위해 콜론(윈도우에서는 세미콜론)을 사용하며 찾으려는 클래스 코드가 포함된 .class 파일을 찾으면 첫 번째로 찾은 파일을 사용합니다. classpath를 지정하는 방법은 두 가지가 있습니다.

  • 환경변수 CLASSPATH 지정
  • java runtime에 -classpath 플래그 사용

classpath에 사용할 수 있는 값은 다음과 같습니다.

  • Paths to the top of package hierarchies
  • myclasses.zip 와 같은 zip 파일
  • myclasses.jar 와 같은 jar 파일

아래 예시를 살펴보겠습니다.

package org.example;

public class Main {
    public static void main(String[] args){
        System.out.println("hello, world!");
    }
}

Main.java/c/study/java/src/org/example에 위치한다고 가정하겠습니다. 현재 directory 위치가 /c/study/java라면 Main.java를 컴파일하기 위해서 어떤 명령어를 사용해야할까요?

javac ./src/org/example/Main.java

-d 옵션을 사용하지 않았으므로 위 명령어를 실행하면 Main.java와 동일한 위치에 Main.class가 생성됩니다. JVM에서 해당 클래스를 사용하기 위해 classpath를 참조해야하므로 -cp를 사용했습니다.

java -cp ./src org.example.Main

위에서 설명했듯이, package hierarchies의 최상위 path를 classpath로 지정하므로 org 패키지를 포함하고 있는 ./src를 classpath로 지정했습니다. 그리고 실행할 클래스파일을 지정합니다.

자바에서는 모든 클래스에는 정의된 클래스 이름과 패키지 이름이 있습니다. 이 둘을 완전하게 합쳐야 한 클래스를 표현한다고 할 수 있으며 이를 FQCN(Fully Qualified Class Name) 이라고 합니다. 이에 따라 실행할 클래스는 org.example.Main이 됩니다.

Main.java에서 다른 클래스를 사용할 경우를 살펴보겠습니다. 이를 위해 /c/study/java/downloads에 있는 commons-lang3-3.10.jar을 사용해서 org.apache.commons.lang3.StringUtils 클래스를 import 했습니다.

package org.example;
import org.apache.commons.lang3.StringUtils;

public class Main {
    public static void main(String[] args) {
        System.out.println("hello, world!");
        System.out.println(StringUtils.equals("java", "java"));
    }
}

현재까지의 디렉토리 구조를 살펴보면

image

와 같은 형태입니다. 여기서 처음에 실행했던 명령어를 사용하면

$ javac ./src/org/example/Main.java
.\src\org\example\Main.java:2: error: package org.apache.commons.lang3 does not exist

lang3을 찾을 수 없다고 나옵니다. -cp를 명시하지 않았으므로 기본 classpath인 .이 설정되고 해당 classpath에서는 StringUtils.class를 찾을 수 없기 때문입니다. 즉 아래와 같이 -cp를 명시한다면 정상적으로 실행됨을 확인할 수 있습니다.

javac -cp ./downloads/commons-lang3-3.12.0.jar ./src/org/example/Main.java

생성된 Main.class를 사용하기 위해서는 Main.class가 위치하는 classpath뿐만 아니라 ./downloads/commons-lang3-3.12.0.jar또한 추가하면 됩니다.

java -cp "./src;./downloads/commons-lang3-3.12.0.jar"  org.example.Main

클래스패스 환경변수

환경변수는 프로세스가 컴퓨터에서 동작하는 방식에 영향을 미치는 값들을 의미합니다. 여러 환경변수 중 PathCLASSPATH에 대해 알아보겠습니다.

Path

CLI환경에서는 ipconfing, where와 같은 명령어들을 통해서 현재 디렉토리에 관계없이 원하는 명령어들을 실행할 수 있습니다. 이 명령어들 또한 프로그램이므로 실행 시 어디에 프로그램이 위치해 있는지 파악해야합니다.

이때, OS가 프로그램의 위치를 파악하기 위해 사용하는 환경변수가 Path입니다. 자바 프로그래밍을 할 때는 javac 명령어를 사용하기 위해 Path 환경변수에 jdk 경로를 설정해주어야합니다.

CLASSPATH

CLASSPATH는 자바의 class 파일들을 위한 환경변수입니다. 즉 JVM은 CLASSPATH경로를 확인하여 클래스들의 위치를 참조하게 됩니다.

아래 환경변수 설정들은 Eclipse와 Intellij와 같은 IDE를 사용하게 된다면 해줄 필요가 없지만 CLI 환경에서 자바 프로그래밍을 하기 위해서는 필수적으로 해야합니다.

환경 변수를 편집하기 위해서 윈도우 왼쪽 아래 검색창에서 시스템 환경 변수 편집을 검색합니다.

image

고급 탭에 환경 변수를 클릭합니다.

image

시스템 변수에서 Path를 찾아 편집을 클릭합니다.

image

새로 만들기를 클릭한 후 %JAVA_HOME%\bin을 입력하면 다음과 같이 Path에 새로운 경로가 추가되었음을 알 수 있습니다. 이는 JAVA_HOME으로 설정된 환경변수+\bin를 새로운 Path로 설정한다는 의미입니다.

image

JAVA_HOME 환경변수를 만들기 위해서는 시스템 변수-새로 만들기(W)를 클릭한 후 jdk가 설치된 경로를 입력하면 됩니다.

image


Reference