[C++ to Java] 4. Package와 Import
패키지와 임포트에 대해 알아보자. 2022-01-05

Package, Import

안녕하세요? Justkode 입니다. 오늘은 Package와 이를 Import 하는 것에 대해 알아 보겠습니다.

Package

패키지클래스의 묶음 입니다. 패키지 안에는 클래스인터페이스를 포함 시킬 수 있습니다. 우리는 관련된 클래스를 같은 패키지에 묶어 이를 효율적으로 관리할 수 있습니다. 사실 우리는 지금까지 클래스명만 사용하여 클래스를 관리 하였지만, "패키지는 다르지만, 클래스 명이 같은 경우"를 구분 하기 위해서, 실제 이름패키지 명을 포함한 풀 네임을 사용 합니다.

예를 들어, String 클래스는 java.lang 패키지에 존재하는 클래스로, 풀 네임은 java.lang.String 입니다. 그래서 우리는 풀 네임을 통해서 동일 이름의 클래스라도 구별 할 수 있습니다.

패키지는 어찌보면 물리적으로 하나의 디렉토리라고 볼 수 있습니다. 클래스들이 특정 패키지에 속해 있다면, 그 클래스들은 같은 디렉토리 안에 존재 하여야 합니다. 이런 식으로 말이죠.

우리가 만든 플레이어 관련 클래스들을 모아놓은 모습 입니다.

디렉토리가 하위 디렉토리를 가질 수 있는 것 처럼, 패키지도 다른 패키지를 포함 할 수 있습니다. 이는 점 '.' 으로 구분 할 수 있습니다. 패키지의 구조는 다음 규칙을 따릅니다.

  • 하나의 소스파일에는 첫 번째 문장에, 단 한 번의 패키지 선언만을 허용 합니다.
  • 모든 클래스는 반드시 하나의 패키지에 속해야 합니다. (만약, 패키지 선언이 없다면, 이는 이름 없는 패키지에 속하게 됩니다.)
  • 패키지는 점(.)을 구분으로 하여 계층구조를 구성 할 수 있습니다.
  • 패키지는 물리적으로 클래스 파일(.class)을 포함하는 하나의 디렉토리 입니다.

패키지의 선언

패키지를 선언 하는 방법은 다음과 같습니다.

package 패키지명

이는 주석과 공백을 제외 한 첫 번째 문장에 등장 하여야 합니다. 하나의 소스파일에는 단 한 번만 선언될 수 있으며, 해당 소스파일에 포함된 모든 클래스나 인터페이스는 선언된 패키지에 속하게 됩니다.

일단 한 번 컴파일 해보자.

일단 다음과 같이 우리가 파일 디렉토리를 구성 해 보겠습니다.

디렉토리 구조.

코드는 다음과 같습니다. 전 시간에도 했던 것들이지만 정리 한다는 생각으로 싹다 정리 하고 진행 하겠습니다. 저번 시간까지 했던 기존 코드에 package player를 추가 한 모습입니다.

player/Player.java

package player;

abstract public class Player {
    private String userName;
    private int healthPoint;
    private int attackPoint;
    private final int USER_ID;
    static int objectNum;

    static {
        objectNum = 0;
    }

    {
        USER_ID = objectNum++;
    }

    Player(String userName, int healthPoint, int attackPoint) {
        setUserName(userName);
        setHealthPoint(healthPoint);
        setAttackPoint(attackPoint);
        printUserInfo();
    }

    Player(Player p) {
        this(p.userName, p.healthPoint, p.attackPoint);
    }

    public String getUserName() {return this.userName;};
    public int getHealthPoint() {return this.healthPoint;};
    public int getAttackPoint() {return this.attackPoint;};

    public void setAttackPoint(int attackPoint) {
        if (attackPoint < 0)
            return;
        this.attackPoint = attackPoint;
    }

    public void setHealthPoint(int healthPoint) {
        if (healthPoint < 0)
            return;
        this.healthPoint = healthPoint;
    }

    public void setUserName(String userName) {
        if (userName.length() < 2)
            return;
        this.userName = userName;
    }

    public void printUserInfo() {
        System.out.println("========================");
        System.out.println("플레이어 이름: " + this.userName);
        System.out.println("체력: " + this.healthPoint);
        System.out.println("공격력: " + this.attackPoint);
        System.out.println("유저 아이디: " + this.USER_ID);
        System.out.println("========================");
    }

    abstract public void attackPlayer(Player p);

    public static void printCurrentObjectNum() {
        System.out.println("현재 생성된 객체의 갯수는 " + objectNum + "개 입니다.");
    }
}

player/Warrior.java

package player;

public class Warrior extends Player implements PlayerAct {
    package Player;

public class Warrior extends Player implements PlayerAct {
    public Warrior(String userName, int healthPoint, int attackPoint) {
        super(userName, healthPoint, attackPoint);
        System.out.println("직업 전사");
        System.out.println("========================");
    }

    public Warrior(Warrior p) {
        super(p);
        System.out.println("직업 전사");
        System.out.println("========================");
    }

    @Override
    public void attackPlayer(Player p) {
        System.out.println("========================");
        System.out.printf("Player %s가 Player %s에 강력한 물리 공격%n", this.getUserName(), p.getUserName());
        System.out.printf("Player %s는 %d의 피해를 입음%n", p.getUserName(), this.getAttackPoint());
        p.setHealthPoint(Math.max(0, p.getHealthPoint() - this.getAttackPoint()));
        System.out.printf("Player %s의 남은 체력: %d%n", p.getUserName(), p.getHealthPoint());
        System.out.println("========================");
    }

    @Override
    public void healing() {
        System.out.println("========================");
        System.out.printf("Player %s가 응급 키트를 이용하여 %d 포인트 만큼 회복을 시도 합니다.%n", this.getUserName(), this.HEALING_HEALTH_POINT);
        this.setHealthPoint(this.getHealthPoint() + this.HEALING_HEALTH_POINT);
        System.out.printf("체력이 %d가 되었습니다.%n", this.getHealthPoint());
        System.out.println("========================");
    }

    @Override
    public void buff() {
        System.out.println("========================");
        System.out.printf("Player %s가 극한의 정신력을 이용하여 %d 포인트 만큼 공격력을 증가 시킵니다.%n", this.getUserName(), this.BUFF_ATTACK_POINT);
        this.setAttackPoint(this.getAttackPoint() + this.BUFF_ATTACK_POINT);
        System.out.printf("공격력이 %d가 되었습니다.%n", this.getAttackPoint());
        System.out.println("========================");
    }

    @Override
    public void useAllSkill() {
        this.healing();
        this.buff();
    }
}

player/Magician.java

package player;

public class Magician extends Player implements PlayerAct {
    public Magician(String userName, int healthPoint, int attackPoint) {
        super(userName, healthPoint, attackPoint);
        System.out.println("직업 마법사");
        System.out.println("========================");
    }

    public Magician(Magician p) {
        super(p);
        System.out.println("직업 마법사");
        System.out.println("========================");
    }

    @Override
    public void attackPlayer(Player p) {
        System.out.println("========================");
        System.out.printf("Player %s가 Player %s에 치명적인 마법 공격%n", this.getUserName(), p.getUserName());
        System.out.printf("Player %s는 %d의 피해를 입음%n", p.getUserName(), this.getAttackPoint());
        p.setHealthPoint(Math.max(0, p.getHealthPoint() - this.getAttackPoint()));
        System.out.printf("Player %s의 남은 체력: %d%n", p.getUserName(), p.getHealthPoint());
        System.out.println("========================");
    }

    @Override
    public void healing() {
        System.out.println("========================");
        System.out.printf("Player %s가 마법을 사용하여 %d 포인트 만큼 회복을 시도 합니다.%n", this.getUserName(), this.HEALING_HEALTH_POINT);
        this.setHealthPoint(this.getHealthPoint() + this.HEALING_HEALTH_POINT);
        System.out.printf("체력이 %d가 되었습니다.%n", this.getHealthPoint());
        System.out.println("========================");
    }

    @Override
    public void buff() {
        System.out.println("========================");
        System.out.printf("Player %s가 마법의 힘을 증폭시켜 %d 포인트 만큼 공격력을 증가 시킵니다.%n", this.getUserName(), this.BUFF_ATTACK_POINT);
        this.setAttackPoint(this.getAttackPoint() + this.BUFF_ATTACK_POINT);
        System.out.printf("공격력이 %d가 되었습니다.%n", this.getAttackPoint());
        System.out.println("========================");
    }

    @Override
    public void useAllSkill() {
        this.healing();
        this.buff();
    }
}

Main.java 에는 import player.Magician, import player.Warrior를 이용하여 패키지 내에 있는 클래스들을 import 해 주면 됩니다.

Main.java

import player.Magician;
import player.Warrior;

public class Main {
    static public void main(String[] args) {
        Warrior warrior = new Warrior("S2지존전사S2", 100, 10);
        Magician magician = new Magician("S2최강법사S2", 50, 20);
        warrior.attackPlayer(magician);
        magician.attackPlayer(warrior);

        warrior.useAllSkill();
        magician.useAllSkill();

        warrior.attackPlayer(magician);
        magician.attackPlayer(warrior);
    }
}

player 패키지 내부의 모든 클래스를 가져오고 싶다면, 다음과 같이 player.* 를 사용 할 수 있습니다.

Main.java

import player.*;

public class Main {
    static public void main(String[] args) {
        Warrior warrior = new Warrior("S2지존전사S2", 100, 10);
        Magician magician = new Magician("S2최강법사S2", 50, 20);
        warrior.attackPlayer(magician);
        magician.attackPlayer(warrior);

        warrior.useAllSkill();
        magician.useAllSkill();

        warrior.attackPlayer(magician);
        magician.attackPlayer(warrior);
    }
}

다음 명령어를 src 폴더 위에서 터미널을 실행 하여 입력 해 보겠습니다.

$ javac -d . player/Player.java  
$ javac -d . player/Warrior.java
$ javac -d . player/Magician.java
$ javac Main.java

그러면 다음과 같이 class 파일 들이 만들어 지는 것을 확인 할 수 있습니다.

디렉토리 구조.

그 다음 해당 프로그램이 잘 돌아 가는지 테스트 해보겠습니다.

$ java Main
========================
플레이어 이름: S2지존전사S2
체력: 100
공격력: 10
유저 아이디: 0
========================
직업 전사
========================
========================
플레이어 이름: S2최강법사S2
체력: 50
공격력: 20
유저 아이디: 1
========================
직업 마법사
========================
========================
Player S2지존전사S2가 Player S2최강법사S2에 강력한 물리 공격
Player S2최강법사S2는 10의 피해를 입음
Player S2최강법사S2의 남은 체력: 40
========================
========================
Player S2최강법사S2가 Player S2지존전사S2에 치명적인 마법 공격
Player S2지존전사S2는 20의 피해를 입음
Player S2지존전사S2의 남은 체력: 80
========================
========================
Player S2지존전사S2가 응급 키트를 이용하여 10 포인트 만큼 회복을 시도 합니다.
체력이 90가 되었습니다.
========================
========================
Player S2지존전사S2가 극한의 정신력을 이용하여 10 포인트 만큼 공격력을 증가 시킵니다.
공격력이 20가 되었습니다.
========================
========================
Player S2최강법사S2가 마법을 사용하여 10 포인트 만큼 회복을 시도 합니다.
체력이 50가 되었습니다.
========================
========================
Player S2최강법사S2가 마법의 힘을 증폭시켜 10 포인트 만큼 공격력을 증가 시킵니다.
공격력이 30가 되었습니다.
========================
========================
Player S2지존전사S2가 Player S2최강법사S2에 강력한 물리 공격
Player S2최강법사S2는 20의 피해를 입음
Player S2최강법사S2의 남은 체력: 30
========================
========================
Player S2최강법사S2가 Player S2지존전사S2에 치명적인 마법 공격
Player S2지존전사S2는 30의 피해를 입음
Player S2지존전사S2의 남은 체력: 60
========================

Static Import문

static import 를 이용 하여, static 멤버를 호출할 때에, 다음과 같이 클래스 명을 입력 하는 것을 생략 할 수 있습니다.

import static java.lang.System.out;
import static java.lang.Math.*;

public class Main {
    static public void main(String[] args) {
        System.out.println(Math.random());
        out.println(random());
        
        System.out.println(Math.PI);
        out.println(PI);
    }
}
0.6151734216674172
0.9421767576044261
3.141592653589793
3.141592653589793

Sub Package

패키지디렉토리 구조를 가진다고 이야기 하였습니다. 그렇기 때문에, 다음과 같이 패키지내에 패키지가 존재 하는 것이 가능 합니다. 트리 구조 처럼 말이죠.

디렉토리 구조.

Battle.java

package player.battle;

import player.Player;

public class Battle {
    static public void battle(Player a, Player b) {
        a.attackPlayer(b);
        b.attackPlayer(a);
    }
}

Main.java

import static player.battle.Battle.*;
import player.Warrior;
import player.Magician;

public class Main {
    static public void main(String[] args) {
        Warrior warrior = new Warrior("S2지존전사S2", 100, 10);
        Magician magician = new Magician("S2최강법사S2", 50, 20);

        battle(warrior, magician);
    }
}

이를 작성한 후, 컴파일을 실시하면 됩니다.

$ javac -d . player/battle/Battle.java

마치며

이번 시간에는 Package에 대해서 배워 보았습니다. 다음 시간에는 예외 처리, Exception에 대해서 배워 보겠습니다.