Web/Java

[Java] 패키지

Pakage

패키지란, 클래스의 묶음입니다.

 

  1. 패키지에 클래스나 인터페이스들을 포함시킬 수 있으며, 서로 관련된 클래스끼리 그룹 단위로 묶어 놓음으로써 클래스를 효율적으로 관리할 수 있습니다.
  2. 같은 이름의 클래스 일지라도 서로 다른 패키지에 존재하는 것이 가능하므로, 다른 개발자가 개발한 클래스와 자신의 클래스의 이름이 충돌하는 것을 피할 수 있습니다.

 

클래스가 물리적으로 하나의 클래스파일(.class)인 것과 같이 패키지는 물리적으로 하나의 디렉토리입니다.

 

사실 클래스의 이름은 패키지까지 포함한 이름입니다.

그래서 String 클래스의 실제 이름은 java.lang.String 입니다.

 

 

패키지 특징 (주의사항)

  1. 하나의 소스파일에는 첫 번째 문장으로 단 한 번의 패키지 선언만을 허용한다.
  2. 모든 클래스는 반드시 하나의 패키지에 속해야 한다.
  3. 패키지는 점(.)을 구분자로 하여 계층구조로 구성할 수 있다.
  4. 패키지는 물리적으로 클래스 파일(.class)을 포함하는 하나의 디렉토리이다.

 

 

패키지의 선언

 

클래스나 인터페이스의 소스파일(.java)의 맨 위에 다음과 같이 한 줄만 적어주면 됩니다.

pakage 패키지명;

 

  1. 패키지 선언문은 반드시 소스파일에서 주석과 공백을 제외한 첫 번째 문장이어야 한다.
  2. 하나의 소스파일에 단 한번만 선언될 수 있다.
  3. 패키지명은 대소문자를 모두 허용하지만, 클래스명과 쉽게 구분하기 위해서 소문자로 하는 것을 원칙으로 하고 있다.

 

 

 

패키지 생성해보기

 

아래와 같이 PackageTest.java 를 생성해보겠습니다.

package com.code.chobo.book;

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

 

그리고 아래와 같이 javac의 -d 옵션을 이용해 PackageTest.java 파일을 컴파일 합니다.

 

(-d 옵션은 소스파일에 지정된 패키지 경로를 통해 패키지의 위치를 찾아서 클래스파일을 생성합니다. 만약 지정된 패키지와 일치하는 디렉토리가 존재하지 않는다면 자동적으로 생성합니다.)

 

지금과 같은 경우에는 -d 다음에 .(현재디렉토리)를 지정해주었고 현재 디렉토리에는 아무디렉토리도 없으므로 소스파일에 지정된 패키지 경로를 따라 디렉토리를 생성해주게 됩니다.

$ javac -d . PackageTest.java

 

 

아래와 같이 com부터시작해 패키지 경로대로 디렉토리가 생성되었습니다.

 

 

 

 

 

 

클래스패스

 

자바에서 클래스패스란 JVM이 클래스 파일을 찾는데 기준이 되는 파일경로입니다.

 

 

ClassPathTest.java 파일을 만들어 보겠습니다.

여기엔 ClassPathTest와 Item class 두개가 존재합니다.

그리고 ClassPathTest에서는 Item를 인스턴스화해 사용합니다.

class Item {
    public void print() {
        System.out.println("Hello world!");
    }
}

class ClassPathTest {
    public static void main(String[] args) {
        Item i = new Item();
        i.print();
    }
}

 

 

 

.java 파일은 javac 명령어로 컴파일이 가능합니다.

ClassPathTest.class , item.class 두개의 클래스파일이 생성되었습니다.

$ javac ClassPathTest.java

 

 

그리고 java 명령어를 이용해 class파일을 실행해보겠습니다.

$ java ClassPathTest

 

 

 

그렇다면,

여기서 생성된 Item.class 파일을 다른 디렉토리로 옮겨보겠습니다.

 

 

 

여기서 다시한번 java 명령어를 이용해 ClassPathTest.class를 실행해보겠습니다.

$ java ClassPathTest

 

 

 

에러발생

NoClassDefFoundError가 발생했습니다.

 

JVM이 현재 패키지에서 Item.class를 찾지 못해 로딩하지 못했고, ClassPathTest에서는 Item 클래스를 인스턴스화해 사용해야 하는데 해당 클래스가 로딩되어있지 않아서 발생한 것입니다.

class Item {
    public void print() {
        System.out.println("Hello world!");
    }
}

class ClassPathTest {
    public static void main(String[] args) {
        Item i = new Item();
        i.print();
    }
}

 

 

 

이 오류를 해결하기 위해서는 두가지 방법이 존재합니다.

  1. ClassPathTest.class가 존재하는 현재 디렉토리에 Item.class를 가져온다.
  2. -classpath를 이용해 item.class가 있는 곳을 지정해준다.

 

-classpath

JVM이 클래스의 위치를 알 수 있도록 해당 클래스의 위치를 일시적으로 지정해주는 옵션입니다.

$ java -classpath ".:bin" ClassPathTest

 

-classpath 옵션 사용

. 현재 디렉토리
bin  bin 디렉토리
: 경로와 경로를 구분해주는 구분자

 

윈도우에선 ;

리눅스에선 : 로 경로와 경로를 구분해줄 수 있습니다.

 

 

 

 

CLASSPATH 환경변수

그렇다면, 항상 ClassPathTest 를 실행해줄때 -classpath 옵션을 사용해야 할까요?

 

아닙니다.

 

각 OS들 마다 환경변수를 이용해 CLASSPATH를 지정해줄 수 있습니다.

 

 

Mac 환경변수 설정방법

 

vi 에디터를 이용해 .zshrc를 엽니다. (저는 zsh 쉘을 이용하기 때문에 zshrc를 엽니다.)

$ vi ~/.zshrc

 

 

아래 명령어를 통해 클래스패스를 지정합니다.

(PATH 는 java javac등의 명령어를 가져올 위치를 지정하기 위해 사용합니다. PATH 또한 환경변수입니다.)

export PATH=${PATH}:/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/home/bin
export CLASSPATH=.:bin

 

변경된 .zshrc를 적용하기 위한 명령어를 쳐줍니다.

$ source ~/.zshrc

 

그리고 java를 실행하면 -classpath로 클래스패스 지정을 하지 않고도 Item 클래스와 ClassPathTest 클래스를 잘 찾아 실행하는 것을 볼 수 있습니다.

 

 

 

 

 

 

 

여기서 한가지 의문점

그동안은 classpath를 지정하지 않았는데 어떻게 java가 동작할 수 있었을까요?

 

사실 java는 classpath옵션을 지정해주지 않으면 자동으로 -classpath "." 를 추가합니다.

$ java ClassPathTest
$ java -classpath "." ClassPathTest

 

그래서 현재 디렉토리에서 실행하는 클래스에는 클래스패스를 지정해주지 않아도 동작했던 것입니다.

 

 

 

 

 

 

 

 

 

접근 지시자 (접근제어자)

 

제어자

제어자는 접근제어자와 그 외의 제어자로 나눌 수 있습니다.

접근제어자 : public, protected, default, private
그 외 : static, final, abstract, native, transient, synchronized, volatile, strictfp

 

단, 접근 제어자는 한번에 4가지 중 하나만 선택하여 사용할 수 있고, 그 외의 제어자는 여러 제어자를 조합하여 사용하는 것이 가능합니다.

참고. 제어자들 간의 순서는 관계없지만 주로 접근 제어자를 제일 왼쪽에 놓는 경향이 있습니다.

 

 

 

접근제어자

접근 제어자는 멤버 또는 클래스에 사용되어, 해당하는 멤버 또는 클래스를 외부에서 접근하지 못하도록 제한하는 역할을 합니다.

 

private 같은 클래스 내에서만 접근이 가능하다.
default 같은 패키지 내에서만 접근이 가능하다.
protected 같은 패키지 내에서, 그리고 다른 패키지의 자손클래스에서 접근이 가능하다.
public 접근 제한이 전혀 없다.

 

제어자 같은 클래스 같은 패키지 자손 클래스 전 체
public o o o o
protected o o o  
(default) o o    
private o      

 

접근 범위 넓은 순 나열

public > protected > (default) > private

 

접근 제어자가 사용될 수 있는 곳

  • 클래스
  • 멤버변수
  • 메서드
  • 생성자

 

 

캡슐화

클래스나 멤버, 주로 멤버에 접근 제어자를 사용하는 이유는 클래스의 내부에 선언된 데이터를 보호하기 위해서입니다.

데이터가 유효한 값을 유지하도록, 또는 비밀번호와 같은 데이터를 외부에서 함부로 변경하지 못하도록 하기 위해서는 외부로부터의 접근을 제한하는 것이 필요합니다.

 

이것이 객체지향개념의 캡슐화(encapsulation) 입니다.

 

 

getter, setter

 

구체적인 예를 통해 캡슐화를 이용하면 어떻게 프로그래밍 할 수 있는지 알아봅니다.

 

Time 클래스가 있고 시간, 분, 초 멤버를 public으로 가지고 있습니다.

public class Time {
    public int hour;
    public int minute;
    public int second;
}

 

 

그리고 Time을 인스턴스화 해서 시간의 값을 25로 만들어줄 수 있습니다.

Time t = new Time();
t.hour = 25;

 

 

하지만 멤버변수 hour는 0보다는 같거나 크고 24보다는 작은 범위의 값을 가져야 하지만 위의 코드에서처럼 잘못된 값을 지정한다고 해도 이것을 막을 방법은 없습니다.

 

이런 경우 멤버변수를 private이나 protected로 제한하고 멤버변수의 값을 읽고 변경할 수 있는 public 메서드를 제공함으로써 간접적으로 멤버변수의 값을 다룰 수 있도록 하는 것이 바람직합니다.

 

 

 

수정된 Time 클래스

멤버변수의 접근은 private으로 같은 클래스에서만 접근할 수 있도록 제한하고, 멤버변수의 값을 얻거나 수정할 수 있는 get, set 메서드를 public으로 하여 간접적으로 멤버변수의 값을 다루게 합니다.

 

그리고, set메서드들에서는 매개변수에 지정된 값을 검사하여 조건에 맞는 값일 때만 멤버변수의 값을 변경하도록 작성되어있습니다.

 

  • get으로 시작하는 메서드를 getter
  • set으로 시작하는 메서드를 setter라고 부릅니다.
package javastudy.ch7;

public class Time {
    private int hour;
    private int minute;
    private int second;

    public int getHour() {
        return hour;
    }

    public void setHour(int hour) {
        if (hour < 0 || hour > 23) return;
        this.hour = hour;
    }

    public int getMinute() {
        return minute;
    }

    public void setMinute(int minute) {
        if (minute < 0 || minute > 59) return;
        this.minute = minute;
    }

    public int getSecond() {
        return second;
    }

    public void setSecond(int second) {
        if (second < 0 || second > 59) return;
        this.second = second;
    }
}

 

이로써 't.hour = 13;'과 같이 멤버변수로의 직접적인 접근은 허가되지 않고 메서드를 통한 접근만이 허용됩니다.

 

 

 

싱글톤 (Singleton) 패턴

싱글톤이란 어떤 클래스가 최초 한번만 메모리를 할당하고 (Static) 그 메모리에 객체를 만들어 사용하는 디자인 패턴입니다.

즉, 생성자의 호출이 반복적으로 이뤄져도 실제로 생성되는 객체는 최초 생성된 객체만을 반환해줍니다.

 

  • 생성자에 private 접근제어자를 두면 외부에서 생성자에 접근할 수 없으므로 인스턴스를 생성할 수 없게 됩니다.
    (하지만 클래스 내부에서는 인스턴스 생성이 가능합니다.)
  • 그래서 인스턴스를 생성해서 반환해주는 public 메서드를 제공함으로써 외부에서 이 클래스 인스턴스를 사용하도록 할 수 있습니다.
    (이 메서드는 public인 동시에 static 이어야 합니다.)
    (아래 예시에서는 생성된 인스턴스를 반환만 해줍니다.)
  • 그리고, 생성자가 private인 클래스는 다른 클래스의 조상이 될 수 없습니다. 자손클래스에서 인스턴스를 생성할 때 조상클래스의 생성자가 private이기 때문에 호출하는 것이 불가능합니다. 그래서 클래스 앞에 final을 추가하여 상속할 수 없는 클래스라는 것을 알리는 것이 좋습니다.

 

 

결국엔 최초 생성된 인스턴스만을 반환하므로 사용할 수 있는 인스턴스의 개수를 제한할 수 있습니다.

package javastudy.ch7;

final class Singleton {
    private static Singleton s = new Singleton();

    private Singleton() {}

    // 인스턴스를 생성하지 않고도 호출할 수 있어야 하므로 static 이어야 합니다.
    public static Singleton getInstance() {
        return s;
    }
}