ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 7. 자바 패키지(Package)
    자바(Java) 강의 2019. 4. 1. 08:19

    여러분은 내 문서 폴더에 파일이 많아지면 어떻게 하는가? 아마도 파일을 카테고리별로 정리한 후 폴더를 만들어 관리할 것이다. 자바에서도 마찬가지이다. 어플리케이션의 규모가 커질수록 클래스 파일의 개수는 많아질 것이다. 자바에서는 이런 클래스 파일을 정리하기 위해 '패키지'를 제공한다. 6. 자바 오브젝트와 클래스 (1) built-in 오브젝트에서 6장 부터는 자바가 제공하는 특별한 문법들에 대한 이야기를 하겠다고 했다. 패키지도 이 특별한 문법들 중 하나이다. 단순히 코드를 작성하고 돌리는 데 패키지는 필요 없을 수 있다. 하지만 보다 큰 어플리케이션을 만들고, 확장하고, 유지 보수하기 위해서 패키지를 이용하는 것은 필수적이다.

    이전 포스트

    목표

    • 패키지(Package)
    • 패키지 만들기
    • 빌트-인 패키지(Built-in Package)

    패키지(Package)

    자바에서 패키지는 폴더 또는 디렉토리라고 했다. 우리는 기능이나 특징이 비슷한 클래스를 모아 하나의 패키지 안에 넣는다. 패키지의 이름은 최대한 그 패키지 안에 들어갈 클래스들을 포괄하는 이름이 좋다. 그렇다면 패키지는 어떤 문제를 해결하기 위해 사용할까?

    유지 보수

    패키지를 이용하면 특정 클래스나 다른 관련된 클래스를 찾기 쉽다. 예를 들어 현재 프로젝트에 100개의 클래스가 있다고 치자. 100개의 클래스가 한 폴더 안에 있다면 특정 클래스를 찾는데 시간이 더 소요될 것이다. 클래스가 패키지로 나눠져 있다면 패키지 이름으로 특정 클래스 파일 있을법한 패키지를 찾아보다 보면 더 빠르게 해당 클래스를 찾을 수 있다. 이런 점에서 패키지를 이용하면 유지보수를 더 쉽게 할 수 있다. 클래스 100개는 과장이 아니다. 실제로 100개가 넘는 클래스를 가진 프로젝트는 흔하다.  

    네임스페이스(Namespace)

    네임스페이스(Namespce)란 무엇인가? 우리는 어떤 파일 'A'을 사용하기 위해 그 파일의 이름인 'A'를 이용해 파일을 접근(참조)한다. 파일 A를 열어보기 위해서 우리는 파일 A의 디렉토리 '경로(path)'를 알아야 한다. 아주 간단히 메모장을 예로 들어보자. 메모장을 켜고 '파일 열기'를 누르면 클릭, 클릭해서 디렉토리로 들어간다. 이게 무엇을 의미하는가? 바로 파일 A가 존재하는 디렉토리의 '경로'를 따라 들어가는 것이다. 우리가 원하는 파일 A가 예를 들어 '/Users/dir_a/'에 존재한다고 치자. 그런데 '/Users/dir_b/'라는 디렉토리에 또 파일 A가 존재한다고 하자. 이 둘은 다른 파일이다. 그렇다면 이 두 파일을 우리는 어떻게 구분하는가? 바로 디렉토리 경로로 구분한다. 따라서 여기서는 dir_a, dir_b가 바로 네임스페이스(Namespace)의 역할을 한다.

    네임스페이스란 이름을 구분 할 수 있게 해주는 공간을 의미한다. 위의 파일의 예에서는 디렉토리가 바로 그 공간이고, 자바에서는 패키지(Package)가 바로 그 공간이다. 

    이러한 특성 덕분에 패키지는 이름 중복으로 인한 문제를 방지 할 수 있다. 패키지 안의 클래스 이름은 고유해야 한다. 다시 말해 패키지 안에는 같은 이름의 두개의 클래스를 만들 수 없다. 하지만 패키지가 다르다면 상관없다. 패키지 자체가 분리된 '다른 공간'이므로 다른 패키지에 같은 이름의 클래스가 있더라도, 내 패키지 안에 똑같은 이름을 쓸 수 있다. 이를 어떻게 자바가 구분하는지는 아래의 예에서 다뤄보도록 하겠다.

    패키지 만들기

    일단 첫번째로 나의 패키지를 만들어보도록 하자. 나는 이전 포스트에서 사용했던 Cat클래스를 이용해 실습을 하도록 하겠다. 클래스 이름이야 별 상관이 없으니 원하는 클래스를 사용하면 된다.

    패키지 만들기

     src에 마우스 오른쪽 클릭을 하고 New > Package를 누른다. 그러면 New Package라는 창이 뜬다. 원하는 패키지 이름을 적고 OK를 누른다. 참고로, 패키지의 이름을 보통 모두 소문자로 한다.

    패키지 안에 클래스 만들기

    패키지를 만들었으면 해당 패키지에 마우스 오른쪽 클릭 > New > Java Class를 한 후 새 클래스를 만들어 보자. 클래스가 성공적으로 만들어졌다면 위처럼 패키지를 열었을 때 해당 클래스가 보일 것이다. 이제 새로 만들어진 클래스를 살펴보자.

    package animal;
    
    public class Cat {
    
    }

    이전에 만들었던 클래스와는 다른 점이 보일 것이다. (이클립스의 경우 package default;였을 수도 있다.) 바로 package animal;부분이다. 무슨 뜻인가? 이 클래스는 animal이라는 package안에 만들어졌다는 뜻이다. 내 프로젝트의 경우 src아래 Cat이라는 클래스가 있고, animal이라는 패키지 안에 Cat이라는 클래스가 또 있다. 위에서 말했듯, 패키지는 Namespace의 역할을 하기 때문에 같은 이름의 클래스이지만 서로 다른 패키지(디렉토리) 안에 존재할 수 있다. 그리고 자바에서는 해당 클래스가 어떤 패키지의 클래스인지 어떻게 아는가? 바로 package animal;처럼 패키지를 명시해줘야 알 수 있다. 

    방금 만든 클래스 이름과 같은 클래스를 src아래 만들어 보고, 위의 package animal(또는 여러분의 패키지 이름)을 지워보자.

    이름 중복으로 인한 에러

    패키지의 이름을 지우면 자바는 두 클래스 중 어디에서 Cat이란 클래스를 찾아야 할 지 모르므로 위와 같은 에러가 난다. 그렇다면 Main에서 Cat오브젝트를 만들 때는 어떻게 될까?

    src바로 아래에 있는 Cat의 내부는 이렇게 생겼다.

    public class Cat {
        String name;
        int weight;
        String gender;
        String color;
    
        Cat(String catName, int catWeight, String catGender, String catColor) {
            name = catName;
            weight = catWeight;
            gender = catGender;
            color = catColor;
        }
    
        void eat(int portion) {
            String behavior = name + "(이)는 " + portion + " 만큼의 밥을 먹었다.";
            System.out.println(behavior);
        }
    
        void pee() {
            System.out.println(name + "(이)는 쉬했다.");
        }
    
        void poo() {
            System.out.println(name + "(이)는 응아했다.");
        }
    
        void lookOutTheWindow(int minutes) {
            System.out.println(name + "(이)는 " + minutes + "분간 창 밖을 바라보았다.");
        }
    
        void sleep(int minutes) {
            System.out.println(name + "(이)는 " + minutes + "분간 잤다.");
        }
    }

    그리고 메인에서는 아래처럼 사용했다.

    public class Main {
        public static void main(String[] args) {
            Cat myCat = new Cat("톰", 5, "수컷", "오렌지");
            myCat.eat(10);
            myCat.lookOutTheWindow(2);
        }
    }

    이제 main의 내용을 지우고 animal패키지 안의 Cat 클래스를 이용해 오브젝트를 생성하려면 어떻게 해야 할까?

    import animal.Cat;
    
    public class Main {
        public static void main(String[] args) {
            Cat myCat = new Cat("톰", 5, "수컷", "오렌지");
            myCat.eat(10);
            myCat.lookOutTheWindow(2);
        }
    }

    위처럼  import animal.Cat;을 이용한다. import는 반입하다/들여오다는 뜻이다. 따라서 import animal.Cat의 의미는, animal이라는 패키지에 있는 Cat이라는 클래스를 사용할 수 있도록 불러오라는 뜻이다. 이렇게 하면 에러가 나기 시작할 것이다. Cat("톰",..) 이 부분에서 말이다. 왜인가? 우리는 animal이라는 패키지에 있는 Cat클래스를 사용하겠다고 위에서 import로 명시 해 놓았다. animal안의 Cat에는 어떤 생성자가 존재하는가? 아무것도 존재하지 않으므로 아무 파라미터도 받지 않는 기본 생성자가 존재할 것이다. 그렇데 위처럼 생성자에 파라미터를 넘겨주고, 정의되지 않은 메서드를 부르므로 전부 에러가 나는 것이다.

    import animal.Cat;
    
    public class Main {
        public static void main(String[] args) {
            Cat cat = new Cat();
        }
    }

    따라서 animal.Cat형을 오브젝트를 생성하기 위해 animal.Cat클래스가 제공하는 생성자로 오브젝트를 생성해 주면 에러가 사라진다.

    import를 사용하고 않고 animal패키지의 Cat을 사용할 수 있을까? 예를 들어 src바로 아래에 있는 Cat클래스도 사용하고 animal패키지 안의 클래스도 사용하고 싶다면 어떻게 해야겠는가?

    public class Main {
        public static void main(String[] args) {
            Cat myCat = new Cat("톰", 5, "수컷", "오렌지");
            animal.Cat anotherCat = new animal.Cat();
        }
    }

    위처럼 <패키지_이름.클래스_이름>으로 어떤 클래스를 이용하는 변수이고 오브젝트인지 명시할 수 있다. 하지만 이런 식으로 코드를 짜는 것은 매우 좋지 못한 디자인 기법이므로 패키지 이름을 클래스나 메서드 내부에서 사용하는 것은 지양하는 것이 좋다.

    참고!

    패키지 안의 클래스를 전부 불러오려면 어떻게 해야 할까?

    import animal.*;

    이렇게 '.'이후에 클래스 이름이 아닌 *를 적어주면 된다. 하지만 패키지의 모든 클래스르 불러오는 것은 좋은 디자인이 아니므로 실제로 모든 클래스가 필요한 게 아니라면 되도록 사용하지 않는 것이 좋다.

    연습문제

    Dog라는 클래스는 anmal패키지 안에 만들어보도록 하자. 클래스의 내용은 다음과 같다. 꼭 같을 필요는 없고 원하는 변수와 메서드를 만들어 보도록 해라.

    package animal;
    
    public class Dog {
        String name;
        int weight;
        String gender;
        String color;
    }

    Main에 Dog 클래스의 형을 갖는 변수를 만들고 Dog형의 오브젝트를 생성 해 보자. <- 이게 무슨 뜻인지 모르겠다면 오브젝트와 클래스를 복습하길 바란다. 이제 해당 오브젝트의 name, weight과 같은 변수에 값을 할당해 보려 하자. 무슨 문제가 생기는가?

    빌트-인 패키지(Built-in Package)

    운 좋게도 자바에서는 자바 개발자들이 사용 할 수 있도록 여러 가지 많은 패키지 및 클래스를 제공한다. 가장 자주 쓰이는 패키지로는 java.lang과 java.util가 있다.

    여러분은 지금까지 java.lang을 써왔다. 나는 한번도 java.lang을 사용한 적이 없는 것 같은가? java.lang패키지는 자바의 아주 기본적인 것들이기 때문에 import로 불러오지 않아도 자바가 알아서 java.lang의 클래스들을 불러온다. 

    import java.lang.String;
    
    public class Main {
        public static void main(String[] args) {
            String str = "this is from java.lang.String";
        }
    }

    이전 포스트에서 String오브젝트는 누군가 String이라는 클래스를 만들어 놨기 때문에 사용할 수 있다고 했었다. 그 클래스는 어디에 존재하는가? 바로 java.lang패키지 안에 존재한다. 다만 위에서 말했듯 java.lang패키지는 매우 매우 자주 쓰이기 때문에, import를 하지 않아도 사용할 수 있도록 만들어져 있다. 그래서 지금까지 우리는 java.lang패키지를 사용하면서도 이 패키지를 사용하는지 몰랐던 것이다. 

    또 자주 사용했던 문법 중에 java.lang에 존재하는 클래스는 무엇이 있는가?

    import java.lang.String;
    import java.lang.System;
    
    public class Main {
        public static void main(String[] args) {
            String str = "this is from java.lang.String";
            System.out.println(str);
        }
    }

     이제 System.out.println이 무슨 뜻인지 알겠는가? System이라는 클래스가 java.lang이라는 패키지 안에 있고 우리는 System이라는 클래스 안의 변수(오브젝트)인 out. 그리고 그 out이라는 오브젝트에 존재하는 println이라는 메서드를 사용 해 왔던 것이다.

    java.util에는 자바에서 제공하는 컬렉션 프레임워크과 자료구조(리트스, 어레이리스트, 해시맵, 셋 등등)이 존재한다. java.util은 이후에 더 설명하도록 하겠다.

    연습문제에서 animal 패키지에 존재하는 Dog라는 클래스를 이용해 오브젝트를 만들었을 때, Dog 오브젝트의 변수에 값을 집어넣을 수 있었나? 아마 해당 변수를 찾을 수 없다는 에러가 났을 것이다. 이유는 클래스 내부의 변수와 메서드들은 기본적으로 해당 패키지 안에서만 사용할 수 있기 때문이다. 그렇다면 다른 패키지에서는 그 클래스의 아무것도 이용할 수 없다는 뜻인가? 아니다. 우리는 클래스의 어떤 변수와 메서드를 어떤 클래스/패키지에게 공개할지 지정할 수 있다. 이렇게 클래스/변수/메서드의 공개 범위를 설정하는 문법을 '접근 제어자(Access Modifier)'라고 부른다. 이에 대한 내용은 다음 포스트에서 다루도록 하겠다.

    다음 포스트:8. 자바 접근 제어자(Access Modifier)

    댓글

f.software engineer @ All Right Reserved