[Java] 개념노트 28 패키지와 import 이해하기

[Java 개념노트 28] 패키지와 import 이해하기

안녕하세요. Java 개념노트 시리즈 스물여덟 번째 글입니다.

지난 글에서는 객체의 데이터를 안전하게 보호하는 캡슐화getter/setter에 대해 정리했습니다. 이번 글에서는 자바에서 클래스를 분류하고 관리하는 패키지와 다른 패키지의 클래스를 가져올 때 사용하는 import에 대해 알아보겠습니다.

프로그램이 작을 때는 클래스가 몇 개 없기 때문에 한 곳에 모아두어도 괜찮습니다. 하지만 프로젝트가 커지면 회원 클래스, 상품 클래스, 주문 클래스, 유틸 클래스처럼 많은 클래스가 생깁니다. 이때 클래스를 역할별로 정리하기 위해 사용하는 것이 패키지입니다.


1. 패키지란?

패키지는 자바 클래스를 묶어서 관리하는 단위입니다. 쉽게 말하면 클래스를 정리하는 폴더 같은 개념입니다.

예를 들어 회원 관련 클래스는 member 패키지에 넣고, 상품 관련 클래스는 product 패키지에 넣을 수 있습니다. 이렇게 하면 클래스가 많아져도 역할별로 찾기 쉽습니다.

쉽게 말하면
패키지는 클래스를 기능별로 정리하는 폴더입니다. 클래스가 많아질수록 패키지를 잘 나누는 것이 중요합니다.

2. 패키지를 사용하는 이유

패키지를 사용하는 이유는 크게 다음과 같습니다.

  • 클래스를 역할별로 정리할 수 있습니다.
  • 같은 이름의 클래스 충돌을 줄일 수 있습니다.
  • 프로젝트 구조를 이해하기 쉬워집니다.
  • 접근 제어자와 함께 클래스 사용 범위를 관리할 수 있습니다.
  • 유지보수하기 쉬운 구조를 만들 수 있습니다.

예를 들어 User라는 이름의 클래스가 여러 곳에서 필요할 수 있습니다. 패키지를 사용하면 member.User, admin.User처럼 구분할 수 있습니다.

3. 패키지 선언하기

자바 파일에서 패키지를 선언할 때는 파일의 가장 위에 package 키워드를 작성합니다.

package member;

public class Member {
    private String name;
}

위 코드는 Member 클래스가 member 패키지에 속한다는 의미입니다.

주의
package 선언은 자바 파일의 가장 위에 작성해야 합니다. 주석을 제외하면 다른 코드보다 먼저 나와야 합니다.

4. 패키지와 폴더 구조

패키지는 실제 폴더 구조와 연결됩니다. 예를 들어 member 패키지에 있는 Member 클래스는 보통 아래와 같은 위치에 둡니다.

src
 └─ member
     └─ Member.java

패키지 이름이 member이면 폴더 이름도 member로 맞추는 것이 기본입니다.

패키지가 여러 단계라면 폴더도 여러 단계로 나뉩니다.

src
 └─ com
     └─ gwdevel
         └─ member
             └─ Member.java

이 경우 자바 파일의 package 선언은 다음과 같습니다.

package com.gwdevel.member;

public class Member {
}

5. 패키지 이름 작성 규칙

패키지 이름은 보통 소문자로 작성합니다. 회사나 서비스 도메인을 거꾸로 사용하는 방식도 많이 사용합니다.

형태 예시
간단한 패키지 member, product, order
계층형 패키지 com.gwdevel.member
기능별 패키지 service, repository, controller

처음 공부할 때는 member, product, order처럼 기능별로 나누는 것부터 익히면 충분합니다.

6. 같은 패키지의 클래스 사용하기

같은 패키지 안에 있는 클래스는 별도의 import 없이 사용할 수 있습니다.

package member;

public class Member {
    String name;
}
package member;

public class MemberMain {
    public static void main(String[] args) {
        Member member = new Member();

        member.name = "건";
        System.out.println(member.name);
    }
}

MemberMemberMain이 같은 member 패키지에 있기 때문에 import 없이 바로 사용할 수 있습니다.

7. 다른 패키지의 클래스 사용하기

다른 패키지에 있는 클래스를 사용하려면 보통 import를 사용합니다.

먼저 member 패키지에 Member 클래스가 있다고 가정해보겠습니다.

package member;

public class Member {
    private String name;

    public Member(String name) {
        this.name = name;
    }

    public void printName() {
        System.out.println(name);
    }
}

이제 다른 패키지에서 Member 클래스를 사용하려면 import가 필요합니다.

package app;

import member.Member;

public class Main {
    public static void main(String[] args) {
        Member member = new Member("건");

        member.printName();
    }
}

import member.Member;를 작성했기 때문에 app 패키지에서도 Member 클래스를 사용할 수 있습니다.

8. import란?

import는 다른 패키지에 있는 클래스를 현재 파일에서 사용할 수 있도록 가져오는 문법입니다.

import 패키지명.클래스명;

예시는 다음과 같습니다.

import java.util.Scanner;
import java.util.ArrayList;
import member.Member;

핵심 포인트
import는 다른 패키지의 클래스를 짧은 이름으로 사용할 수 있게 해주는 문법입니다.

9. import 없이 전체 이름으로 사용하기

import를 사용하지 않고 패키지 이름까지 포함한 전체 이름으로 클래스를 사용할 수도 있습니다. 이것을 전체 경로 이름 또는 완전한 클래스 이름이라고 볼 수 있습니다.

public class Main {
    public static void main(String[] args) {
        java.util.Scanner scanner = new java.util.Scanner(System.in);
    }
}

하지만 매번 java.util.Scanner처럼 길게 작성하면 불편합니다. 그래서 보통 import를 사용합니다.

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
    }
}

10. import 위치

import는 package 선언 아래, 클래스 선언 위에 작성합니다.

package app;

import java.util.Scanner;
import member.Member;

public class Main {
    public static void main(String[] args) {
    }
}

정리하면 순서는 다음과 같습니다.

1. package 선언
2. import 선언
3. class 선언

11. 여러 클래스를 import하기

여러 클래스를 사용해야 한다면 import를 여러 줄 작성할 수 있습니다.

import java.util.Scanner;
import java.util.ArrayList;
import java.util.HashMap;

각 import는 한 줄씩 작성합니다. IDE를 사용하면 필요한 import를 자동으로 추가해주는 경우가 많습니다.

12. 와일드카드 import

패키지 안의 여러 클래스를 가져올 때 *를 사용할 수도 있습니다.

import java.util.*;

위 코드는 java.util 패키지 안의 여러 클래스를 사용할 수 있게 해줍니다. 예를 들어 Scanner, ArrayList, HashMap 등을 사용할 수 있습니다.

다만 처음 공부할 때는 어떤 클래스를 가져오는지 명확하게 보이도록 필요한 클래스만 직접 import하는 습관이 좋습니다.

import java.util.Scanner;
import java.util.ArrayList;

13. java.lang 패키지는 자동 import된다

자바에서 자주 사용하는 기본 클래스들이 들어 있는 java.lang 패키지는 자동으로 import됩니다.

예를 들어 String, System, Math 같은 클래스는 import 없이 사용할 수 있습니다.

public class Main {
    public static void main(String[] args) {
        String name = "Java";

        System.out.println(name);
        System.out.println(Math.max(10, 20));
    }
}

String, System, Math는 모두 java.lang 패키지에 있지만 별도 import 없이 사용할 수 있습니다.

14. import와 public 접근 제어자

다른 패키지에서 클래스를 사용하려면 import만으로는 부족할 수 있습니다. 클래스가 외부에서 접근 가능해야 합니다.

package member;

public class Member {
}

위처럼 클래스가 public이면 다른 패키지에서도 import해서 사용할 수 있습니다.

반대로 클래스에 public이 없으면 같은 패키지 안에서만 접근할 수 있습니다.

package member;

class Member {
}

이 경우 다른 패키지에서 Member를 import해서 사용하려고 하면 접근할 수 없습니다.

주의
다른 패키지에서 사용해야 하는 클래스는 보통 public으로 선언해야 합니다.

15. 같은 이름의 클래스가 있을 때

서로 다른 패키지에 같은 이름의 클래스가 있을 수 있습니다. 예를 들어 member.Useradmin.User가 있다고 가정해보겠습니다.

member.User
admin.User

이런 경우 두 클래스를 동시에 짧은 이름 User로 사용하면 충돌이 생길 수 있습니다. 필요하다면 한쪽은 전체 이름으로 작성할 수 있습니다.

import member.User;

public class Main {
    public static void main(String[] args) {
        User memberUser = new User();

        admin.User adminUser = new admin.User();
    }
}

이렇게 하면 member.User는 import로 사용하고, admin.User는 전체 이름으로 구분할 수 있습니다.

16. static import 맛보기

일반 import는 클래스를 가져오는 문법입니다. 그런데 static 멤버를 클래스 이름 없이 사용하고 싶을 때 static import를 사용할 수도 있습니다.

import static java.lang.Math.max;

public class Main {
    public static void main(String[] args) {
        System.out.println(max(10, 20));
    }
}

원래는 Math.max(10, 20)처럼 호출해야 하지만, static import를 사용하면 max(10, 20)처럼 사용할 수 있습니다.

다만 처음 공부할 때는 static import를 많이 쓰기보다 Math.max()처럼 클래스 이름을 함께 작성하는 방식이 더 명확합니다.

17. 패키지 구조 예시

간단한 자바 프로젝트는 다음과 같은 패키지 구조로 나눌 수 있습니다.

src
 ├─ app
 │   └─ Main.java
 ├─ member
 │   └─ Member.java
 ├─ product
 │   └─ Product.java
 └─ util
     └─ PriceUtil.java

각 파일의 package 선언은 폴더 위치에 맞게 작성합니다.

// app/Main.java
package app;

// member/Member.java
package member;

// product/Product.java
package product;

// util/PriceUtil.java
package util;

18. 패키지와 import 전체 예제

이번에는 패키지와 import를 함께 사용하는 전체 예제를 보겠습니다.

member/Member.java

package member;

public class Member {
    private String name;

    public Member(String name) {
        this.name = name;
    }

    public void printInfo() {
        System.out.println("회원 이름: " + name);
    }
}

product/Product.java

package product;

public class Product {
    private String name;
    private int price;

    public Product(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public void printInfo() {
        System.out.println("상품명: " + name + ", 가격: " + price);
    }
}

app/Main.java

package app;

import member.Member;
import product.Product;

public class Main {
    public static void main(String[] args) {
        Member member = new Member("건");
        Product product = new Product("키보드", 50000);

        member.printInfo();
        product.printInfo();
    }
}

실행 결과는 다음과 같습니다.

회원 이름: 건
상품명: 키보드, 가격: 50000

Main 클래스는 app 패키지에 있고, MemberProduct는 각각 다른 패키지에 있습니다. 그래서 import member.Member, import product.Product를 사용했습니다.

19. 패키지와 import 사용 시 자주 하는 실수

패키지와 import를 처음 배울 때는 아래와 같은 실수를 자주 합니다.

실수 문제점 해결 방법
package 선언 위치가 잘못됨 컴파일 오류 발생 package는 파일 가장 위에 작성
폴더 구조와 package 이름 불일치 IDE나 실행 환경에서 오류 발생 가능 패키지 이름과 폴더 구조를 맞춤
다른 패키지 클래스 import 누락 클래스를 찾지 못함 import 패키지명.클래스명 작성
클래스가 public이 아님 다른 패키지에서 접근 불가 외부에서 사용할 클래스는 public 선언
같은 이름 클래스 충돌 어떤 클래스를 의미하는지 모호함 필요하면 전체 패키지 이름으로 구분

20. 직접 연습해보기

아래 구조를 직접 만들어보고 실행해보세요.

src
 ├─ app
 │   └─ Main.java
 ├─ user
 │   └─ User.java
 └─ calculator
     └─ Calculator.java

user/User.java

package user;

public class User {
    private String name;

    public User(String name) {
        this.name = name;
    }

    public void printName() {
        System.out.println("사용자: " + name);
    }
}

calculator/Calculator.java

package calculator;

public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}

app/Main.java

package app;

import user.User;
import calculator.Calculator;

public class Main {
    public static void main(String[] args) {
        User user = new User("건");
        Calculator calculator = new Calculator();

        user.printName();

        int result = calculator.add(10, 20);
        System.out.println("계산 결과: " + result);
    }
}

실행 결과는 다음과 같습니다.

사용자: 건
계산 결과: 30

21. 이번 글 정리

이번 글에서는 클래스를 역할별로 정리하는 패키지와 다른 패키지의 클래스를 가져오는 import에 대해 정리했습니다. 핵심 내용은 다음과 같습니다.

  • 패키지는 클래스를 묶어서 관리하는 단위이다.
  • 패키지는 클래스를 폴더처럼 기능별로 정리하는 역할을 한다.
  • package 선언은 자바 파일 가장 위에 작성한다.
  • 패키지 이름과 폴더 구조는 맞추는 것이 좋다.
  • 같은 패키지의 클래스는 import 없이 사용할 수 있다.
  • 다른 패키지의 클래스를 사용할 때는 보통 import가 필요하다.
  • import 패키지명.클래스명 형태로 작성한다.
  • java.lang 패키지는 자동으로 import된다.
  • 다른 패키지에서 사용할 클래스는 보통 public이어야 한다.
  • 같은 이름의 클래스가 있으면 전체 패키지 이름으로 구분할 수 있다.

한 줄 요약
패키지는 클래스를 기능별로 정리하는 폴더이고, import는 다른 패키지의 클래스를 현재 코드에서 사용할 수 있게 가져오는 문법입니다.

다음 글 예고
다음 글에서는 [Java 개념노트 29] 상속이란 무엇인가?라는 주제로 부모 클래스의 필드와 메서드를 자식 클래스가 물려받는 객체지향 핵심 개념을 정리해보겠습니다.

GWDEVELBlog Java 개념노트 시리즈